复制项目

This commit is contained in:
kim.dev.6789
2026-01-14 22:35:45 +08:00
parent 305d526110
commit b7f8db7d08
297 changed files with 81784 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
// Copyright © 2023 OpenIM open source community. 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 AdminLoginResp struct {
AdminAccount string `json:"adminAccount"`
AdminToken string `json:"adminToken"`
Nickname string `json:"nickname"`
FaceURL string `json:"faceURL"`
Level int32 `json:"level"`
AdminUserID string `json:"adminUserID"`
ImUserID string `json:"imUserID"`
ImToken string `json:"imToken"`
}
type SearchDefaultGroupResp struct {
Total uint32 `json:"total"`
Groups []*sdkws.GroupInfo `json:"groups"`
}
type NewUserCountResp struct {
Total int64 `json:"total"`
DateCount map[string]int64 `json:"date_count"`
}

View File

@@ -0,0 +1,133 @@
// Copyright © 2023 OpenIM open source community. 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 UserRegisterResp struct {
ImToken string `json:"imToken"`
ChatToken string `json:"chatToken"`
UserID string `json:"userID"`
}
type LoginResp struct {
ImToken string `json:"imToken"`
ChatToken string `json:"chatToken"`
UserID string `json:"userID"`
}
type UpdateUserInfoResp struct{}
type CallbackAfterSendSingleMsgReq struct {
CommonCallbackReq
RecvID string `json:"recvID"`
}
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"`
CreateTime int64 `json:"createTime"`
Content string `json:"content"`
Seq uint32 `json:"seq"`
AtUserIDList []string `json:"atUserList"`
SenderFaceURL string `json:"faceURL"`
Ex string `json:"ex"`
}
type CallbackAfterSendSingleMsgResp struct {
CommonCallbackResp
}
type CommonCallbackResp struct {
ActionCode int32 `json:"actionCode"`
ErrCode int32 `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
NextCode int32 `json:"nextCode"`
}
type TextElem struct {
Content string `json:"content" 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 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 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
}
// 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"`
}

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"`
}

View File

@@ -0,0 +1,49 @@
package cmd
import (
"context"
"git.imall.cloud/openim/chat/internal/api/admin"
"git.imall.cloud/openim/chat/pkg/common/config"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type AdminApiCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
apiConfig admin.Config
}
func NewAdminApiCmd() *AdminApiCmd {
ret := AdminApiCmd{apiConfig: admin.Config{
AllConfig: &config.AllConfig{},
}}
ret.configMap = map[string]any{
config.DiscoveryConfigFileName: &ret.apiConfig.Discovery,
config.LogConfigFileName: &ret.apiConfig.Log,
config.MongodbConfigFileName: &ret.apiConfig.Mongo,
config.ChatAPIAdminCfgFileName: &ret.apiConfig.AdminAPI,
config.ChatAPIChatCfgFileName: &ret.apiConfig.ChatAPI,
config.ChatRPCAdminCfgFileName: &ret.apiConfig.Admin,
config.ChatRPCChatCfgFileName: &ret.apiConfig.Chat,
config.RedisConfigFileName: &ret.apiConfig.Redis,
config.ShareFileName: &ret.apiConfig.Share,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
ret.apiConfig.ConfigPath = ret.configPath
return ret.runE()
}
return &ret
}
func (a *AdminApiCmd) Exec() error {
return a.Execute()
}
func (a *AdminApiCmd) runE() error {
return admin.Start(a.ctx, a.Index(), &a.apiConfig)
}

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 cmd
import (
"context"
"git.imall.cloud/openim/chat/internal/rpc/admin"
"git.imall.cloud/openim/chat/pkg/common/config"
"git.imall.cloud/openim/chat/pkg/common/startrpc"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type AdminRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
adminConfig admin.Config
}
func NewAdminRpcCmd() *AdminRpcCmd {
var ret AdminRpcCmd
ret.configMap = map[string]any{
config.ChatRPCAdminCfgFileName: &ret.adminConfig.RpcConfig,
config.RedisConfigFileName: &ret.adminConfig.RedisConfig,
config.DiscoveryConfigFileName: &ret.adminConfig.Discovery,
config.MongodbConfigFileName: &ret.adminConfig.MongodbConfig,
config.ShareFileName: &ret.adminConfig.Share,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return &ret
}
func (a *AdminRpcCmd) Exec() error {
return a.Execute()
}
func (a *AdminRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.adminConfig.Discovery, a.adminConfig.RpcConfig.RPC.ListenIP,
a.adminConfig.RpcConfig.RPC.RegisterIP, a.adminConfig.RpcConfig.RPC.Ports,
a.Index(), a.adminConfig.Discovery.RpcService.Admin, &a.adminConfig.Share, &a.adminConfig,
[]string{
config.ChatRPCAdminCfgFileName,
config.RedisConfigFileName,
config.DiscoveryConfigFileName,
config.MongodbConfigFileName,
config.ShareFileName,
config.LogConfigFileName,
}, nil,
admin.Start)
}

41
pkg/common/cmd/bot_api.go Normal file
View File

@@ -0,0 +1,41 @@
package cmd
import (
"context"
"git.imall.cloud/openim/chat/internal/api/bot"
"git.imall.cloud/openim/chat/pkg/common/config"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type BotApiCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
apiConfig bot.Config
}
func NewBotApiCmd() *BotApiCmd {
ret := BotApiCmd{apiConfig: bot.Config{}}
ret.configMap = map[string]any{
config.DiscoveryConfigFileName: &ret.apiConfig.Discovery,
config.ChatAPIBotCfgFileName: &ret.apiConfig.ApiConfig,
config.ShareFileName: &ret.apiConfig.Share,
config.RedisConfigFileName: &ret.apiConfig.Redis,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return &ret
}
func (a *BotApiCmd) Exec() error {
return a.Execute()
}
func (a *BotApiCmd) runE() error {
return bot.Start(a.ctx, a.Index(), &a.apiConfig)
}

54
pkg/common/cmd/bot_rpc.go Normal file
View File

@@ -0,0 +1,54 @@
package cmd
import (
"context"
"git.imall.cloud/openim/chat/internal/rpc/bot"
"git.imall.cloud/openim/chat/pkg/common/config"
"git.imall.cloud/openim/chat/pkg/common/startrpc"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type BotRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
botConfig bot.Config
}
func NewBotRpcCmd() *BotRpcCmd {
var ret BotRpcCmd
ret.configMap = map[string]any{
config.ChatRPCBotCfgFileName: &ret.botConfig.RpcConfig,
config.RedisConfigFileName: &ret.botConfig.RedisConfig,
config.DiscoveryConfigFileName: &ret.botConfig.Discovery,
config.MongodbConfigFileName: &ret.botConfig.MongodbConfig,
config.ShareFileName: &ret.botConfig.Share,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return &ret
}
func (a *BotRpcCmd) Exec() error {
return a.Execute()
}
func (a *BotRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.botConfig.Discovery, a.botConfig.RpcConfig.RPC.ListenIP,
a.botConfig.RpcConfig.RPC.RegisterIP, a.botConfig.RpcConfig.RPC.Ports,
a.Index(), a.botConfig.Discovery.RpcService.Bot, &a.botConfig.Share, &a.botConfig,
[]string{
config.ChatRPCBotCfgFileName,
config.RedisConfigFileName,
config.DiscoveryConfigFileName,
config.MongodbConfigFileName,
config.ShareFileName,
config.LogConfigFileName,
}, nil,
bot.Start)
}

View File

@@ -0,0 +1,41 @@
package cmd
import (
"context"
"git.imall.cloud/openim/chat/internal/api/chat"
"git.imall.cloud/openim/chat/pkg/common/config"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type ChatApiCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
apiConfig chat.Config
}
func NewChatApiCmd() *ChatApiCmd {
var ret ChatApiCmd
ret.configMap = map[string]any{
config.ShareFileName: &ret.apiConfig.Share,
config.ChatAPIChatCfgFileName: &ret.apiConfig.ApiConfig,
config.DiscoveryConfigFileName: &ret.apiConfig.Discovery,
config.RedisConfigFileName: &ret.apiConfig.Redis,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return &ret
}
func (a *ChatApiCmd) Exec() error {
return a.Execute()
}
func (a *ChatApiCmd) runE() error {
return chat.Start(a.ctx, a.Index(), &a.apiConfig)
}

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 cmd
import (
"context"
"git.imall.cloud/openim/chat/internal/rpc/chat"
"git.imall.cloud/openim/chat/pkg/common/config"
"git.imall.cloud/openim/chat/pkg/common/startrpc"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type ChatRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
chatConfig chat.Config
}
func NewChatRpcCmd() *ChatRpcCmd {
var ret ChatRpcCmd
ret.configMap = map[string]any{
config.ChatRPCChatCfgFileName: &ret.chatConfig.RpcConfig,
config.RedisConfigFileName: &ret.chatConfig.RedisConfig,
config.DiscoveryConfigFileName: &ret.chatConfig.Discovery,
config.MongodbConfigFileName: &ret.chatConfig.MongodbConfig,
config.ShareFileName: &ret.chatConfig.Share,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return &ret
}
func (a *ChatRpcCmd) Exec() error {
return a.Execute()
}
func (a *ChatRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.chatConfig.Discovery, a.chatConfig.RpcConfig.RPC.ListenIP,
a.chatConfig.RpcConfig.RPC.RegisterIP, a.chatConfig.RpcConfig.RPC.Ports,
a.Index(), a.chatConfig.Discovery.RpcService.Chat, &a.chatConfig.Share, &a.chatConfig,
[]string{
config.ChatRPCChatCfgFileName,
config.RedisConfigFileName,
config.DiscoveryConfigFileName,
config.MongodbConfigFileName,
config.ShareFileName,
config.LogConfigFileName,
}, nil,
chat.Start)
}

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

@@ -0,0 +1,266 @@
// 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/chat/pkg/common/config"
"git.imall.cloud/openim/chat/pkg/common/kdisc"
disetcd "git.imall.cloud/openim/chat/pkg/common/kdisc/etcd"
"git.imall.cloud/openim/chat/version"
"github.com/openimsdk/tools/discovery/etcd"
clientv3 "go.etcd.io/etcd/client/v3"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/runtimeenv"
"github.com/spf13/cobra"
)
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) Index() int {
return r.index
}
func (r *RootCmd) Port() int {
return r.port
}
type CmdOpts struct {
loggerPrefixName string
configMap map[string]any
}
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 chat 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{}
env := runtimeenv.PrintRuntimeEnvironment()
err = config.Load(configDirectory, config.DiscoveryConfigFileName, config.EnvPrefixMap[config.DiscoveryConfigFileName],
env, &disConfig)
if err != nil {
return err
}
if disConfig.Enable == kdisc.ETCDCONST {
discov, _ := kdisc.NewDiscoveryRegister(&disConfig, env, nil)
// 安全类型断言:仅当为 etcd 实现时才获取客户端,避免在 K8s 模式下崩溃
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
}
r.configPath = configDirectory
runtimeEnv := runtimeenv.PrintRuntimeEnvironment()
// 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], runtimeEnv, configStruct)
if err != nil {
return err
}
}
// Load common log configuration file
return config.Load(configDirectory, config.LogConfigFileName,
config.EnvPrefixMap[config.LogConfigFileName], runtimeEnv, &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, config.Version))
}
func defaultCmdOpts() *CmdOpts {
return &CmdOpts{
loggerPrefixName: "openim-chat-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)
}
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()
}

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

@@ -0,0 +1,247 @@
package config
import (
_ "embed"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
)
var (
//go:embed version
Version string
//go:embed template.xlsx
ImportTemplate []byte
)
type Share struct {
OpenIM struct {
ApiURL string `mapstructure:"apiURL"`
Secret string `mapstructure:"secret"`
AdminUserID string `mapstructure:"adminUserID"`
TokenRefreshInterval int `mapstructure:"tokenRefreshInterval"`
} `mapstructure:"openIM"`
ChatAdmin []string `mapstructure:"chatAdmin"`
ProxyHeader string `mapstructure:"proxyHeader"`
}
type RpcService struct {
Chat string `mapstructure:"chat"`
Admin string `mapstructure:"admin"`
Bot string `mapstructure:"bot"`
}
func (r *RpcService) GetServiceNames() []string {
return []string{
r.Chat,
r.Admin,
}
}
type API struct {
Api struct {
ListenIP string `mapstructure:"listenIP"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"api"`
}
type APIBot struct {
Api struct {
ListenIP string `mapstructure:"listenIP"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"api"`
}
type Mongo struct {
URI string `mapstructure:"uri"`
Address []string `mapstructure:"address"`
Database string `mapstructure:"database"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
AuthSource string `mapstructure:"authSource"`
MaxPoolSize int `mapstructure:"maxPoolSize"`
MaxRetry int `mapstructure:"maxRetry"`
}
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,
}
}
type Redis struct {
Address []string `mapstructure:"address"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
EnablePipeline bool `mapstructure:"enablePipeline"`
ClusterMode bool `mapstructure:"clusterMode"`
DB int `mapstructure:"db"`
MaxRetry int `mapstructure:"MaxRetry"`
}
func (r *Redis) Build() *redisutil.Config {
return &redisutil.Config{
ClusterMode: r.ClusterMode,
Address: r.Address,
Username: r.Username,
Password: r.Password,
DB: r.DB,
MaxRetry: r.MaxRetry,
}
}
type Discovery struct {
Enable string `mapstructure:"enable"`
Etcd Etcd `mapstructure:"etcd"`
Kubernetes Kubernetes `mapstructure:"kubernetes"`
RpcService RpcService `mapstructure:"rpcService"`
}
type Kubernetes struct {
Namespace string `mapstructure:"namespace"`
}
type Etcd struct {
RootDirectory string `mapstructure:"rootDirectory"`
Address []string `mapstructure:"address"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
type Chat struct {
RPC struct {
RegisterIP string `mapstructure:"registerIP"`
ListenIP string `mapstructure:"listenIP"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
VerifyCode VerifyCode `mapstructure:"verifyCode"`
LiveKit struct {
URL string `mapstructure:"url"`
Key string `mapstructure:"key"`
Secret string `mapstructure:"secret"`
} `mapstructure:"liveKit"`
AllowRegister bool `mapstructure:"allowRegister"`
}
type Bot struct {
RPC struct {
RegisterIP string `mapstructure:"registerIP"`
ListenIP string `mapstructure:"listenIP"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Timeout int `mapstructure:"timeout"`
}
type VerifyCode struct {
ValidTime int `mapstructure:"validTime"`
ValidCount int `mapstructure:"validCount"`
UintTime int `mapstructure:"uintTime"`
MaxCount int `mapstructure:"maxCount"`
SuperCode string `mapstructure:"superCode"`
Len int `mapstructure:"len"`
Phone struct {
Use string `mapstructure:"use"`
Ali struct {
Endpoint string `mapstructure:"endpoint"`
AccessKeyID string `mapstructure:"accessKeyId"`
AccessKeySecret string `mapstructure:"accessKeySecret"`
SignName string `mapstructure:"signName"`
VerificationCodeTemplateCode string `mapstructure:"verificationCodeTemplateCode"`
} `mapstructure:"ali"`
Bao struct {
Endpoint string `mapstructure:"endpoint"`
AccessKeyID string `mapstructure:"accessKeyId"`
AccessKeySecret string `mapstructure:"accessKeySecret"`
SignName string `mapstructure:"signName"`
VerificationCodeTemplateCode string `mapstructure:"verificationCodeTemplateCode"`
} `mapstructure:"bao"`
} `mapstructure:"phone"`
Mail struct {
Use string `mapstructure:"use"`
Title string `mapstructure:"title"`
SenderMail string `mapstructure:"senderMail"`
SenderAuthorizationCode string `mapstructure:"senderAuthorizationCode"`
SMTPAddr string `mapstructure:"smtpAddr"`
SMTPPort int `mapstructure:"smtpPort"`
} `mapstructure:"mail"`
}
type Admin struct {
RPC struct {
RegisterIP string `mapstructure:"registerIP"`
ListenIP string `mapstructure:"listenIP"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
TokenPolicy struct {
Expire int `mapstructure:"expire"`
} `mapstructure:"tokenPolicy"`
Secret string `mapstructure:"secret"`
}
type Log struct {
StorageLocation string `mapstructure:"storageLocation"`
RotationTime uint `mapstructure:"rotationTime"`
RemainRotationCount uint `mapstructure:"remainRotationCount"`
RemainLogLevel int `mapstructure:"remainLogLevel"`
IsStdout bool `mapstructure:"isStdout"`
IsJson bool `mapstructure:"isJson"`
IsSimplify bool `mapstructure:"isSimplify"`
WithStack bool `mapstructure:"withStack"`
}
type AllConfig struct {
AdminAPI API
ChatAPI API
Admin Admin
Chat Chat
Discovery Discovery
Log Log
Mongo Mongo
Redis Redis
Share Share
}
func (a *AllConfig) Name2Config(name string) any {
switch name {
case ChatAPIAdminCfgFileName:
return a.AdminAPI
case ChatAPIChatCfgFileName:
return a.ChatAPI
case ChatRPCAdminCfgFileName:
return a.Admin
case ChatRPCChatCfgFileName:
return a.Chat
case DiscoveryConfigFileName:
return a.Discovery
case LogConfigFileName:
return a.Log
case MongodbConfigFileName:
return a.Mongo
case RedisConfigFileName:
return a.Redis
case ShareFileName:
return a.Share
default:
return nil
}
}
func (a *AllConfig) GetConfigNames() []string {
return []string{
ShareFileName,
RedisConfigFileName,
DiscoveryConfigFileName,
MongodbConfigFileName,
LogConfigFileName,
ChatAPIAdminCfgFileName,
ChatAPIChatCfgFileName,
ChatRPCAdminCfgFileName,
ChatRPCChatCfgFileName,
}
}

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

@@ -0,0 +1,48 @@
package config
import (
"strings"
)
var (
ShareFileName = "share.yml"
RedisConfigFileName = "redis.yml"
DiscoveryConfigFileName = "discovery.yml"
MongodbConfigFileName = "mongodb.yml"
LogConfigFileName = "log.yml"
ChatAPIAdminCfgFileName = "chat-api-admin.yml"
ChatAPIChatCfgFileName = "chat-api-chat.yml"
ChatAPIBotCfgFileName = "chat-api-bot.yml"
ChatRPCAdminCfgFileName = "chat-rpc-admin.yml"
ChatRPCChatCfgFileName = "chat-rpc-chat.yml"
ChatRPCBotCfgFileName = "chat-rpc-bot.yml"
)
var EnvPrefixMap map[string]string
func init() {
EnvPrefixMap = make(map[string]string)
fileNames := []string{
ShareFileName,
RedisConfigFileName,
DiscoveryConfigFileName,
MongodbConfigFileName,
LogConfigFileName,
ChatAPIAdminCfgFileName,
ChatAPIChatCfgFileName,
ChatRPCAdminCfgFileName,
ChatRPCChatCfgFileName,
}
for _, fileName := range fileNames {
envKey := strings.TrimSuffix(strings.TrimSuffix(fileName, ".yml"), ".yaml")
envKey = "CHATENV_" + envKey
envKey = strings.ToUpper(strings.ReplaceAll(envKey, "-", "_"))
EnvPrefixMap[fileName] = envKey
}
}
const (
FlagConf = "config_folder_path"
FlagTransferIndex = "index"
)

45
pkg/common/config/load.go Normal file
View File

@@ -0,0 +1,45 @@
package config
import (
"os"
"path/filepath"
"strings"
"git.imall.cloud/openim/chat/pkg/common/constant"
"github.com/mitchellh/mapstructure"
"github.com/openimsdk/tools/errs"
"github.com/spf13/viper"
)
func Load(configDirectory string, configFileName string, envPrefix string, runtimeEnv string, config any) error {
if runtimeEnv == constant.KUBERNETES {
mountPath := os.Getenv(constant.MountConfigFilePath)
if mountPath == "" {
return errs.ErrArgs.WrapMsg(constant.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 = "mapstructure"
}); err != nil {
return errs.WrapMsg(err, "failed to unmarshal config", "path", path, "envPrefix", envPrefix)
}
return nil
}

Binary file not shown.

View File

@@ -0,0 +1 @@
v1.8.0

View File

@@ -0,0 +1,128 @@
package constant
import "git.imall.cloud/openim/protocol/constant"
const (
MountConfigFilePath = "CONFIG_PATH"
KUBERNETES = "kubernetes"
ETCD = "etcd"
)
const (
// verificationCode used for.
VerificationCodeForRegister = 1 // Register
VerificationCodeForResetPassword = 2 // Reset password
VerificationCodeForLogin = 3 // Login
VerificationCodeForH5Register = 4 // H5 register
)
const LogFileName = "chat.log"
// block unblock.
const (
BlockUser = 1
UnblockUser = 2
)
// AccountType.
const (
Email = "email"
Phone = "phone"
Account = "account"
)
// Mode.
const (
UserMode = "user"
AdminMode = "admin"
)
const DefaultAdminLevel = 100
// user level.
const (
NormalAdmin = 80
AdvancedUserLevel = 100
)
// AddFriendCtrl.
const (
OrdinaryUserAddFriendEnable = 1 // Allow ordinary users to add friends
OrdinaryUserAddFriendDisable = -1 // Do not allow ordinary users to add friends
)
const (
NormalUser = 1
AdminUser = 2
)
// mini-app
const (
StatusOnShelf = 1 // OnShelf
StatusUnShelf = 2 // UnShelf
)
const (
LimitNil = 0 // None
LimitEmpty = 1 // Neither are restricted
LimitOnlyLoginIP = 2 // Only login is restricted
LimitOnlyRegisterIP = 3 // Only registration is restricted
LimitLoginIP = 4 // Restrict login
LimitRegisterIP = 5 // Restrict registration
LimitLoginRegisterIP = 6 // Restrict both login and registration
)
const (
InvitationCodeAll = 0 // All
InvitationCodeUsed = 1 // Used
InvitationCodeUnused = 2 // Unused
)
const (
RpcOpUserID = constant.OpUserID
RpcOpUserType = "opUserType"
)
const RpcCustomHeader = constant.RpcCustomHeader
const NeedInvitationCodeRegisterConfigKey = "needInvitationCodeRegister"
const (
DefaultAllowVibration = 1
DefaultAllowBeep = 1
DefaultAllowAddFriend = 1
)
const (
FinDAllUser = 0
FindNormalUser = 1
)
const CtxApiToken = "api-token"
const (
AccountRegister = iota
EmailRegister
PhoneRegister
)
const (
GenderFemale = 0 // female
GenderMale = 1 // male
GenderUnknown = 2 // unknown
)
// Credential Type
const (
CredentialAccount = iota
CredentialPhone
CredentialEmail
)
// verifyCode use
const (
VerifySuperCode = "supercode"
VerifyALi = "ali"
VerifyBao = "bao"
VerifyMail = "mail"
)

View File

@@ -0,0 +1,21 @@
// Copyright © 2023 OpenIM open source community. 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 constant
const (
ShowNumber = 1000
StatisticsTimeInterval = 60
MaxNotificationNum = 500
)

View File

@@ -0,0 +1,5 @@
package constant
const (
AgentUserIDPrefix = "bot_"
)

View File

@@ -0,0 +1,41 @@
package convert
import (
"time"
"git.imall.cloud/openim/chat/pkg/common/db/table/bot"
pbbot "git.imall.cloud/openim/chat/pkg/protocol/bot"
"github.com/openimsdk/tools/utils/datautil"
)
func DB2PBAgent(a *bot.Agent) *pbbot.Agent {
return &pbbot.Agent{
UserID: a.UserID,
Nickname: a.NickName,
FaceURL: a.FaceURL,
Url: a.Url,
Key: a.Key,
Identity: a.Identity,
Model: a.Model,
Prompts: a.Prompts,
CreateTime: a.CreateTime.UnixMilli(),
}
}
func PB2DBAgent(a *pbbot.Agent) *bot.Agent {
return &bot.Agent{
UserID: a.UserID,
NickName: a.Nickname,
FaceURL: a.FaceURL,
Key: a.Key,
Url: a.Url,
Identity: a.Identity,
Model: a.Model,
Prompts: a.Prompts,
CreateTime: time.UnixMilli(a.CreateTime),
}
}
func BatchDB2PBAgent(a []*bot.Agent) []*pbbot.Agent {
return datautil.Batch(DB2PBAgent, a)
}

50
pkg/common/db/cache/imtoken.go vendored Normal file
View File

@@ -0,0 +1,50 @@
package cache
import (
"context"
"time"
"github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9"
)
const (
chatPrefix = "CHAT:"
imToken = chatPrefix + "IM_TOKEN:"
)
func getIMTokenKey(userID string) string {
return imToken + userID
}
type IMTokenInterface interface {
GetToken(ctx context.Context, userID string) (string, error)
SetToken(ctx context.Context, userID, token string) error
}
type imTokenCacheRedis struct {
rdb redis.UniversalClient
expire time.Duration
}
func NewIMTokenInterface(rdb redis.UniversalClient, expire int) IMTokenInterface {
return &imTokenCacheRedis{rdb: rdb, expire: time.Duration(expire) * time.Minute}
}
func (i *imTokenCacheRedis) GetToken(ctx context.Context, userID string) (string, error) {
key := getIMTokenKey(userID)
token, err := i.rdb.Get(ctx, key).Result()
if err != nil {
return "", errs.Wrap(err)
}
return token, nil
}
func (i *imTokenCacheRedis) SetToken(ctx context.Context, userID, token string) error {
key := getIMTokenKey(userID)
err := i.rdb.Set(ctx, key, token, i.expire).Err()
if err != nil {
return errs.Wrap(err)
}
return nil
}

84
pkg/common/db/cache/token.go vendored Normal file
View File

@@ -0,0 +1,84 @@
// Copyright © 2023 OpenIM open source community. 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"
"time"
"github.com/openimsdk/tools/utils/stringutil"
"github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9"
)
const (
chatToken = "CHAT_UID_TOKEN_STATUS:"
)
type TokenInterface interface {
AddTokenFlag(ctx context.Context, userID string, token string, flag int) error
AddTokenFlagNXEx(ctx context.Context, userID string, token string, flag int, expire time.Duration) (bool, error)
GetTokensWithoutError(ctx context.Context, userID string) (map[string]int32, error)
DeleteTokenByUid(ctx context.Context, userID string) error
}
type TokenCacheRedis struct {
rdb redis.UniversalClient
accessExpire int64
}
func NewTokenInterface(rdb redis.UniversalClient) *TokenCacheRedis {
return &TokenCacheRedis{rdb: rdb}
}
func (t *TokenCacheRedis) AddTokenFlag(ctx context.Context, userID string, token string, flag int) error {
key := chatToken + userID
return errs.Wrap(t.rdb.HSet(ctx, key, token, flag).Err())
}
func (t *TokenCacheRedis) AddTokenFlagNXEx(ctx context.Context, userID string, token string, flag int, expire time.Duration) (bool, error) {
key := chatToken + userID
isSet, err := t.rdb.HSetNX(ctx, key, token, flag).Result()
if err != nil {
return false, errs.Wrap(err)
}
if !isSet {
// key already exists
return false, nil
}
if err = t.rdb.Expire(ctx, key, expire).Err(); err != nil {
return false, errs.Wrap(err)
}
return isSet, nil
}
func (t *TokenCacheRedis) GetTokensWithoutError(ctx context.Context, userID string) (map[string]int32, error) {
key := chatToken + userID
m, err := t.rdb.HGetAll(ctx, key).Result()
if err != nil {
return nil, errs.Wrap(err)
}
mm := make(map[string]int32)
for k, v := range m {
mm[k] = stringutil.StringToInt32(v)
}
return mm, nil
}
func (t *TokenCacheRedis) DeleteTokenByUid(ctx context.Context, userID string) error {
key := chatToken + userID
return errs.Wrap(t.rdb.Del(ctx, key).Err())
}

86
pkg/common/db/cache/user_login_ip.go vendored Normal file
View File

@@ -0,0 +1,86 @@
// Copyright © 2023 OpenIM open source community. 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"
"time"
"github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9"
)
const (
userLoginIPPrefix = "CHAT:USER_LOGIN_IP:"
// 缓存过期时间7天用户登录IP变化不频繁可以设置较长的过期时间
userLoginIPExpire = 7 * 24 * time.Hour
)
func getUserLoginIPKey(userID string) string {
return userLoginIPPrefix + userID
}
type UserLoginIPInterface interface {
// GetLatestLoginIP 获取用户最新登录IP从缓存
// 返回值:(ip, found, error)
// - ip: 缓存的IP值如果found为false则ip为空字符串
// - found: 是否在缓存中找到true表示缓存命中false表示缓存未命中
// - error: 错误信息
GetLatestLoginIP(ctx context.Context, userID string) (string, bool, error)
// SetLatestLoginIP 设置用户最新登录IP到缓存
SetLatestLoginIP(ctx context.Context, userID, ip string) error
// DeleteLatestLoginIP 删除用户最新登录IP缓存用于保证缓存一致性
DeleteLatestLoginIP(ctx context.Context, userID string) error
}
type userLoginIPCacheRedis struct {
rdb redis.UniversalClient
}
func NewUserLoginIPInterface(rdb redis.UniversalClient) UserLoginIPInterface {
return &userLoginIPCacheRedis{rdb: rdb}
}
func (u *userLoginIPCacheRedis) GetLatestLoginIP(ctx context.Context, userID string) (string, bool, error) {
key := getUserLoginIPKey(userID)
ip, err := u.rdb.Get(ctx, key).Result()
if err != nil {
if err == redis.Nil {
// 缓存不存在,返回 false 表示缓存未命中
return "", false, nil
}
return "", false, errs.Wrap(err)
}
// 缓存命中,返回 true
return ip, true, nil
}
func (u *userLoginIPCacheRedis) SetLatestLoginIP(ctx context.Context, userID, ip string) error {
key := getUserLoginIPKey(userID)
err := u.rdb.Set(ctx, key, ip, userLoginIPExpire).Err()
if err != nil {
return errs.Wrap(err)
}
return nil
}
func (u *userLoginIPCacheRedis) DeleteLatestLoginIP(ctx context.Context, userID string) error {
key := getUserLoginIPKey(userID)
err := u.rdb.Del(ctx, key).Err()
if err != nil {
return errs.Wrap(err)
}
return nil
}

View File

@@ -0,0 +1,407 @@
// Copyright © 2023 OpenIM open source community. 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 database
import (
"context"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
"git.imall.cloud/openim/chat/pkg/common/db/cache"
"git.imall.cloud/openim/protocol/constant"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/db/tx"
"github.com/redis/go-redis/v9"
"git.imall.cloud/openim/chat/pkg/common/db/model/admin"
admindb "git.imall.cloud/openim/chat/pkg/common/db/table/admin"
)
type AdminDatabaseInterface interface {
GetAdmin(ctx context.Context, account string) (*admindb.Admin, error)
GetAdminUserID(ctx context.Context, userID string) (*admindb.Admin, error)
UpdateAdmin(ctx context.Context, userID string, update map[string]any) error
ChangePassword(ctx context.Context, userID string, newPassword string) error
ChangeOperationPassword(ctx context.Context, userID string, newPassword string) error
ClearGoogleAuthKey(ctx context.Context, userID string) error
AddAdminAccount(ctx context.Context, admin []*admindb.Admin) error
DelAdminAccount(ctx context.Context, userIDs []string) error
SearchAdminAccount(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.Admin, error)
CreateApplet(ctx context.Context, applets []*admindb.Applet) error
DelApplet(ctx context.Context, appletIDs []string) error
GetApplet(ctx context.Context, appletID string) (*admindb.Applet, error)
FindApplet(ctx context.Context, appletIDs []string) ([]*admindb.Applet, error)
SearchApplet(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.Applet, error)
FindOnShelf(ctx context.Context) ([]*admindb.Applet, error)
UpdateApplet(ctx context.Context, appletID string, update map[string]any) error
GetConfig(ctx context.Context) (map[string]string, error)
SetConfig(ctx context.Context, cs map[string]string) error
DelConfig(ctx context.Context, keys []string) error
FindInvitationRegister(ctx context.Context, codes []string) ([]*admindb.InvitationRegister, error)
DelInvitationRegister(ctx context.Context, codes []string) error
UpdateInvitationRegister(ctx context.Context, code string, fields map[string]any) error
CreatInvitationRegister(ctx context.Context, invitationRegisters []*admindb.InvitationRegister) error
SearchInvitationRegister(ctx context.Context, keyword string, state int32, userIDs []string, codes []string, pagination pagination.Pagination) (int64, []*admindb.InvitationRegister, error)
SearchIPForbidden(ctx context.Context, keyword string, state int32, pagination pagination.Pagination) (int64, []*admindb.IPForbidden, error)
AddIPForbidden(ctx context.Context, ms []*admindb.IPForbidden) error
FindIPForbidden(ctx context.Context, ms []string) ([]*admindb.IPForbidden, error)
DelIPForbidden(ctx context.Context, ips []string) error
FindDefaultFriend(ctx context.Context, userIDs []string) ([]string, error)
AddDefaultFriend(ctx context.Context, ms []*admindb.RegisterAddFriend) error
DelDefaultFriend(ctx context.Context, userIDs []string) error
SearchDefaultFriend(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.RegisterAddFriend, error)
FindDefaultGroup(ctx context.Context, groupIDs []string) ([]string, error)
AddDefaultGroup(ctx context.Context, ms []*admindb.RegisterAddGroup) error
DelDefaultGroup(ctx context.Context, groupIDs []string) error
SearchDefaultGroup(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.RegisterAddGroup, error)
CountTotalGroups(ctx context.Context) (int64, error) // 统计群组总数
CountTodayNewGroups(ctx context.Context) (int64, error) // 统计今天新建的群组数
CountTotalFriends(ctx context.Context) (int64, error) // 统计好友总数
FindBlockInfo(ctx context.Context, userIDs []string) ([]*admindb.ForbiddenAccount, error)
GetBlockInfo(ctx context.Context, userID string) (*admindb.ForbiddenAccount, error)
BlockUser(ctx context.Context, f []*admindb.ForbiddenAccount) error
DelBlockUser(ctx context.Context, userID []string) error
SearchBlockUser(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.ForbiddenAccount, error)
FindBlockUser(ctx context.Context, userIDs []string) ([]*admindb.ForbiddenAccount, error)
SearchUserLimitLogin(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.LimitUserLoginIP, error)
AddUserLimitLogin(ctx context.Context, ms []*admindb.LimitUserLoginIP) error
DelUserLimitLogin(ctx context.Context, ms []*admindb.LimitUserLoginIP) error
CountLimitUserLoginIP(ctx context.Context, userID string) (uint32, error)
GetLimitUserLoginIP(ctx context.Context, userID string, ip string) (*admindb.LimitUserLoginIP, error)
CacheToken(ctx context.Context, userID string, token string, expire time.Duration) error
GetTokens(ctx context.Context, userID string) (map[string]int32, error)
DeleteToken(ctx context.Context, userID string) error
LatestVersion(ctx context.Context, platform string) (*admindb.Application, error)
AddVersion(ctx context.Context, val *admindb.Application) error
UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error
DeleteVersion(ctx context.Context, id []primitive.ObjectID) error
PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*admindb.Application, error)
}
func NewAdminDatabase(cli *mongoutil.Client, rdb redis.UniversalClient) (AdminDatabaseInterface, error) {
a, err := admin.NewAdmin(cli.GetDB())
if err != nil {
return nil, err
}
forbidden, err := admin.NewIPForbidden(cli.GetDB())
if err != nil {
return nil, err
}
forbiddenAccount, err := admin.NewForbiddenAccount(cli.GetDB())
if err != nil {
return nil, err
}
limitUserLoginIP, err := admin.NewLimitUserLoginIP(cli.GetDB())
if err != nil {
return nil, err
}
invitationRegister, err := admin.NewInvitationRegister(cli.GetDB())
if err != nil {
return nil, err
}
registerAddFriend, err := admin.NewRegisterAddFriend(cli.GetDB())
if err != nil {
return nil, err
}
registerAddGroup, err := admin.NewRegisterAddGroup(cli.GetDB())
if err != nil {
return nil, err
}
applet, err := admin.NewApplet(cli.GetDB())
if err != nil {
return nil, err
}
clientConfig, err := admin.NewClientConfig(cli.GetDB())
if err != nil {
return nil, err
}
application, err := admin.NewApplication(cli.GetDB())
if err != nil {
return nil, err
}
return &AdminDatabase{
tx: cli.GetTx(),
admin: a,
ipForbidden: forbidden,
forbiddenAccount: forbiddenAccount,
limitUserLoginIP: limitUserLoginIP,
invitationRegister: invitationRegister,
registerAddFriend: registerAddFriend,
registerAddGroup: registerAddGroup,
applet: applet,
clientConfig: clientConfig,
application: application,
cache: cache.NewTokenInterface(rdb),
}, nil
}
type AdminDatabase struct {
tx tx.Tx
admin admindb.AdminInterface
ipForbidden admindb.IPForbiddenInterface
forbiddenAccount admindb.ForbiddenAccountInterface
limitUserLoginIP admindb.LimitUserLoginIPInterface
invitationRegister admindb.InvitationRegisterInterface
registerAddFriend admindb.RegisterAddFriendInterface
registerAddGroup admindb.RegisterAddGroupInterface
applet admindb.AppletInterface
clientConfig admindb.ClientConfigInterface
application admindb.ApplicationInterface
cache cache.TokenInterface
}
func (o *AdminDatabase) GetAdmin(ctx context.Context, account string) (*admindb.Admin, error) {
return o.admin.Take(ctx, account)
}
func (o *AdminDatabase) GetAdminUserID(ctx context.Context, userID string) (*admindb.Admin, error) {
return o.admin.TakeUserID(ctx, userID)
}
func (o *AdminDatabase) UpdateAdmin(ctx context.Context, userID string, update map[string]any) error {
return o.admin.Update(ctx, userID, update)
}
func (o *AdminDatabase) ChangePassword(ctx context.Context, userID string, newPassword string) error {
return o.admin.ChangePassword(ctx, userID, newPassword)
}
func (o *AdminDatabase) ChangeOperationPassword(ctx context.Context, userID string, newPassword string) error {
return o.admin.ChangeOperationPassword(ctx, userID, newPassword)
}
func (o *AdminDatabase) ClearGoogleAuthKey(ctx context.Context, userID string) error {
return o.admin.ClearGoogleAuthKey(ctx, userID)
}
func (o *AdminDatabase) AddAdminAccount(ctx context.Context, admins []*admindb.Admin) error {
return o.admin.Create(ctx, admins)
}
func (o *AdminDatabase) DelAdminAccount(ctx context.Context, userIDs []string) error {
return o.admin.Delete(ctx, userIDs)
}
func (o *AdminDatabase) SearchAdminAccount(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.Admin, error) {
return o.admin.Search(ctx, keyword, pagination)
}
func (o *AdminDatabase) CreateApplet(ctx context.Context, applets []*admindb.Applet) error {
return o.applet.Create(ctx, applets)
}
func (o *AdminDatabase) DelApplet(ctx context.Context, appletIDs []string) error {
return o.applet.Del(ctx, appletIDs)
}
func (o *AdminDatabase) GetApplet(ctx context.Context, appletID string) (*admindb.Applet, error) {
return o.applet.Take(ctx, appletID)
}
func (o *AdminDatabase) FindApplet(ctx context.Context, appletIDs []string) ([]*admindb.Applet, error) {
return o.applet.FindID(ctx, appletIDs)
}
func (o *AdminDatabase) SearchApplet(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.Applet, error) {
return o.applet.Search(ctx, keyword, pagination)
}
func (o *AdminDatabase) FindOnShelf(ctx context.Context) ([]*admindb.Applet, error) {
return o.applet.FindOnShelf(ctx)
}
func (o *AdminDatabase) UpdateApplet(ctx context.Context, appletID string, update map[string]any) error {
return o.applet.Update(ctx, appletID, update)
}
func (o *AdminDatabase) GetConfig(ctx context.Context) (map[string]string, error) {
return o.clientConfig.Get(ctx)
}
func (o *AdminDatabase) SetConfig(ctx context.Context, cs map[string]string) error {
return o.clientConfig.Set(ctx, cs)
}
func (o *AdminDatabase) DelConfig(ctx context.Context, keys []string) error {
return o.clientConfig.Del(ctx, keys)
}
func (o *AdminDatabase) FindInvitationRegister(ctx context.Context, codes []string) ([]*admindb.InvitationRegister, error) {
return o.invitationRegister.Find(ctx, codes)
}
func (o *AdminDatabase) DelInvitationRegister(ctx context.Context, codes []string) error {
return o.invitationRegister.Del(ctx, codes)
}
func (o *AdminDatabase) UpdateInvitationRegister(ctx context.Context, code string, fields map[string]any) error {
return o.invitationRegister.Update(ctx, code, fields)
}
func (o *AdminDatabase) CreatInvitationRegister(ctx context.Context, invitationRegisters []*admindb.InvitationRegister) error {
return o.invitationRegister.Create(ctx, invitationRegisters)
}
func (o *AdminDatabase) SearchInvitationRegister(ctx context.Context, keyword string, state int32, userIDs []string, codes []string, pagination pagination.Pagination) (int64, []*admindb.InvitationRegister, error) {
return o.invitationRegister.Search(ctx, keyword, state, userIDs, codes, pagination)
}
func (o *AdminDatabase) SearchIPForbidden(ctx context.Context, keyword string, state int32, pagination pagination.Pagination) (int64, []*admindb.IPForbidden, error) {
return o.ipForbidden.Search(ctx, keyword, state, pagination)
}
func (o *AdminDatabase) AddIPForbidden(ctx context.Context, ms []*admindb.IPForbidden) error {
return o.ipForbidden.Create(ctx, ms)
}
func (o *AdminDatabase) FindIPForbidden(ctx context.Context, ms []string) ([]*admindb.IPForbidden, error) {
return o.ipForbidden.Find(ctx, ms)
}
func (o *AdminDatabase) DelIPForbidden(ctx context.Context, ips []string) error {
return o.ipForbidden.Delete(ctx, ips)
}
func (o *AdminDatabase) FindDefaultFriend(ctx context.Context, userIDs []string) ([]string, error) {
return o.registerAddFriend.FindUserID(ctx, userIDs)
}
func (o *AdminDatabase) AddDefaultFriend(ctx context.Context, ms []*admindb.RegisterAddFriend) error {
return o.registerAddFriend.Add(ctx, ms)
}
func (o *AdminDatabase) DelDefaultFriend(ctx context.Context, userIDs []string) error {
return o.registerAddFriend.Del(ctx, userIDs)
}
func (o *AdminDatabase) SearchDefaultFriend(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.RegisterAddFriend, error) {
return o.registerAddFriend.Search(ctx, keyword, pagination)
}
func (o *AdminDatabase) FindDefaultGroup(ctx context.Context, groupIDs []string) ([]string, error) {
return o.registerAddGroup.FindGroupID(ctx, groupIDs)
}
func (o *AdminDatabase) AddDefaultGroup(ctx context.Context, ms []*admindb.RegisterAddGroup) error {
return o.registerAddGroup.Add(ctx, ms)
}
func (o *AdminDatabase) DelDefaultGroup(ctx context.Context, groupIDs []string) error {
return o.registerAddGroup.Del(ctx, groupIDs)
}
func (o *AdminDatabase) SearchDefaultGroup(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.RegisterAddGroup, error) {
return o.registerAddGroup.Search(ctx, keyword, pagination)
}
func (o *AdminDatabase) CountTotalGroups(ctx context.Context) (int64, error) {
return o.registerAddGroup.CountTotal(ctx)
}
func (o *AdminDatabase) CountTodayNewGroups(ctx context.Context) (int64, error) {
return o.registerAddGroup.CountToday(ctx)
}
func (o *AdminDatabase) CountTotalFriends(ctx context.Context) (int64, error) {
return o.registerAddFriend.CountTotal(ctx)
}
func (o *AdminDatabase) FindBlockInfo(ctx context.Context, userIDs []string) ([]*admindb.ForbiddenAccount, error) {
return o.forbiddenAccount.Find(ctx, userIDs)
}
func (o *AdminDatabase) GetBlockInfo(ctx context.Context, userID string) (*admindb.ForbiddenAccount, error) {
return o.forbiddenAccount.Take(ctx, userID)
}
func (o *AdminDatabase) BlockUser(ctx context.Context, f []*admindb.ForbiddenAccount) error {
return o.forbiddenAccount.Create(ctx, f)
}
func (o *AdminDatabase) DelBlockUser(ctx context.Context, userID []string) error {
return o.forbiddenAccount.Delete(ctx, userID)
}
func (o *AdminDatabase) SearchBlockUser(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.ForbiddenAccount, error) {
return o.forbiddenAccount.Search(ctx, keyword, pagination)
}
func (o *AdminDatabase) FindBlockUser(ctx context.Context, userIDs []string) ([]*admindb.ForbiddenAccount, error) {
return o.forbiddenAccount.Find(ctx, userIDs)
}
func (o *AdminDatabase) SearchUserLimitLogin(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.LimitUserLoginIP, error) {
return o.limitUserLoginIP.Search(ctx, keyword, pagination)
}
func (o *AdminDatabase) AddUserLimitLogin(ctx context.Context, ms []*admindb.LimitUserLoginIP) error {
return o.limitUserLoginIP.Create(ctx, ms)
}
func (o *AdminDatabase) DelUserLimitLogin(ctx context.Context, ms []*admindb.LimitUserLoginIP) error {
return o.limitUserLoginIP.Delete(ctx, ms)
}
func (o *AdminDatabase) CountLimitUserLoginIP(ctx context.Context, userID string) (uint32, error) {
return o.limitUserLoginIP.Count(ctx, userID)
}
func (o *AdminDatabase) GetLimitUserLoginIP(ctx context.Context, userID string, ip string) (*admindb.LimitUserLoginIP, error) {
return o.limitUserLoginIP.Take(ctx, userID, ip)
}
func (o *AdminDatabase) CacheToken(ctx context.Context, userID string, token string, expire time.Duration) error {
isSet, err := o.cache.AddTokenFlagNXEx(ctx, userID, token, constant.NormalToken, expire)
if err != nil {
return err
}
if !isSet {
// already exists, update
if err = o.cache.AddTokenFlag(ctx, userID, token, constant.NormalToken); err != nil {
return err
}
}
return nil
}
func (o *AdminDatabase) GetTokens(ctx context.Context, userID string) (map[string]int32, error) {
return o.cache.GetTokensWithoutError(ctx, userID)
}
func (o *AdminDatabase) DeleteToken(ctx context.Context, userID string) error {
return o.cache.DeleteTokenByUid(ctx, userID)
}
func (o *AdminDatabase) LatestVersion(ctx context.Context, platform string) (*admindb.Application, error) {
return o.application.LatestVersion(ctx, platform)
}
func (o *AdminDatabase) AddVersion(ctx context.Context, val *admindb.Application) error {
return o.application.AddVersion(ctx, val)
}
func (o *AdminDatabase) UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error {
return o.application.UpdateVersion(ctx, id, update)
}
func (o *AdminDatabase) DeleteVersion(ctx context.Context, id []primitive.ObjectID) error {
return o.application.DeleteVersion(ctx, id)
}
func (o *AdminDatabase) PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*admindb.Application, error) {
return o.application.PageVersion(ctx, platforms, page)
}

View File

@@ -0,0 +1,77 @@
package database
import (
"context"
"git.imall.cloud/openim/chat/pkg/common/db/model/bot"
tablebot "git.imall.cloud/openim/chat/pkg/common/db/table/bot"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/db/tx"
)
type BotDatabase interface {
CreateAgent(ctx context.Context, jobs ...*tablebot.Agent) error
TakeAgent(ctx context.Context, userID string) (*tablebot.Agent, error)
FindAgents(ctx context.Context, userIDs []string) ([]*tablebot.Agent, error)
UpdateAgent(ctx context.Context, userID string, data map[string]any) error
DeleteAgents(ctx context.Context, userIDs []string) error
PageAgents(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*tablebot.Agent, error)
TakeConversationRespID(ctx context.Context, convID, agentID string) (*tablebot.ConversationRespID, error)
UpdateConversationRespID(ctx context.Context, convID, agentID string, data map[string]any) error
}
type botDatabase struct {
tx tx.Tx
agent tablebot.AgentInterface
convRespID tablebot.ConversationRespIDInterface
}
func NewBotDatabase(cli *mongoutil.Client) (BotDatabase, error) {
agent, err := bot.NewAgent(cli.GetDB())
if err != nil {
return nil, err
}
convRespID, err := bot.NewConversationRespID(cli.GetDB())
if err != nil {
return nil, err
}
return &botDatabase{
tx: cli.GetTx(),
agent: agent,
convRespID: convRespID,
}, nil
}
func (a *botDatabase) CreateAgent(ctx context.Context, agents ...*tablebot.Agent) error {
return a.agent.Create(ctx, agents...)
}
func (a *botDatabase) TakeAgent(ctx context.Context, userID string) (*tablebot.Agent, error) {
return a.agent.Take(ctx, userID)
}
func (a *botDatabase) FindAgents(ctx context.Context, userIDs []string) ([]*tablebot.Agent, error) {
return a.agent.Find(ctx, userIDs)
}
func (a *botDatabase) UpdateAgent(ctx context.Context, userID string, data map[string]any) error {
return a.agent.Update(ctx, userID, data)
}
func (a *botDatabase) DeleteAgents(ctx context.Context, userIDs []string) error {
return a.agent.Delete(ctx, userIDs)
}
func (a *botDatabase) PageAgents(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*tablebot.Agent, error) {
return a.agent.Page(ctx, userIDs, pagination)
}
func (a *botDatabase) UpdateConversationRespID(ctx context.Context, convID, agentID string, data map[string]any) error {
return a.convRespID.Update(ctx, convID, agentID, data)
}
func (a *botDatabase) TakeConversationRespID(ctx context.Context, convID, agentID string) (*tablebot.ConversationRespID, error) {
return a.convRespID.Take(ctx, convID, agentID)
}

View File

@@ -0,0 +1,867 @@
// Copyright © 2023 OpenIM open source community. 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 database
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/db/tx"
"github.com/redis/go-redis/v9"
"git.imall.cloud/openim/chat/pkg/common/constant"
"git.imall.cloud/openim/chat/pkg/common/db/cache"
admindb "git.imall.cloud/openim/chat/pkg/common/db/model/admin"
"git.imall.cloud/openim/chat/pkg/common/db/model/chat"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
type ChatDatabaseInterface interface {
GetUser(ctx context.Context, userID string) (account *chatdb.Account, err error)
UpdateUseInfo(ctx context.Context, userID string, attribute map[string]any, updateCred, delCred []*chatdb.Credential) (err error)
FindAttribute(ctx context.Context, userIDs []string) ([]*chatdb.Attribute, error)
FindAttributeByAccount(ctx context.Context, accounts []string) ([]*chatdb.Attribute, error)
TakeAttributeByPhone(ctx context.Context, areaCode string, phoneNumber string) (*chatdb.Attribute, error)
TakeAttributeByEmail(ctx context.Context, Email string) (*chatdb.Attribute, error)
TakeAttributeByAccount(ctx context.Context, account string) (*chatdb.Attribute, error)
TakeAttributeByUserID(ctx context.Context, userID string) (*chatdb.Attribute, error)
TakeAccount(ctx context.Context, userID string) (*chatdb.Account, error)
TakeCredentialByAccount(ctx context.Context, account string) (*chatdb.Credential, error)
TakeCredentialsByUserID(ctx context.Context, userID string) ([]*chatdb.Credential, error)
TakeLastVerifyCode(ctx context.Context, account string) (*chatdb.VerifyCode, error)
Search(ctx context.Context, normalUser int32, keyword string, genders int32, startTime, endTime *time.Time, pagination pagination.Pagination) (int64, []*chatdb.Attribute, error)
SearchWithRealNameAuth(ctx context.Context, normalUser int32, keyword string, genders int32, startTime, endTime *time.Time, realNameKeyword string, idCardKeyword string, pagination pagination.Pagination) (total int64, attributes []*chatdb.Attribute, err error) // 搜索用户(支持实名信息搜索)
SearchUser(ctx context.Context, keyword string, userIDs []string, genders []int32, pagination pagination.Pagination) (int64, []*chatdb.Attribute, error)
CountVerifyCodeRange(ctx context.Context, account string, start time.Time, end time.Time) (int64, error)
AddVerifyCode(ctx context.Context, verifyCode *chatdb.VerifyCode, fn func() error) error
UpdateVerifyCodeIncrCount(ctx context.Context, id string) error
DelVerifyCode(ctx context.Context, id string) error
RegisterUser(ctx context.Context, register *chatdb.Register, account *chatdb.Account, attribute *chatdb.Attribute, credentials []*chatdb.Credential) error
LoginRecord(ctx context.Context, record *chatdb.UserLoginRecord, verifyCodeID *string) error
UpdatePassword(ctx context.Context, userID string, password string) error
UpdatePasswordAndDeleteVerifyCode(ctx context.Context, userID string, password string, codeID string) error
NewUserCountTotal(ctx context.Context, before *time.Time) (int64, error)
UserLoginCountTotal(ctx context.Context, before *time.Time) (int64, error)
UserLoginCountRangeEverydayTotal(ctx context.Context, start *time.Time, end *time.Time) (map[string]int64, int64, error)
CountTodayRegisteredUsers(ctx context.Context) (int64, error)
CountTodayActiveUsers(ctx context.Context) (int64, error)
GetLatestLoginIP(ctx context.Context, userID string) (string, error) // 获取用户最新登录IP
SearchUserLoginRecords(ctx context.Context, userID, ip string, pagination pagination.Pagination) (int64, []*chatdb.UserLoginRecord, error) // 查询用户登录记录支持按用户ID或IP查询
DelUserAccount(ctx context.Context, userIDs []string) error
// 敏感词相关方法
GetSensitiveWords(ctx context.Context) ([]*chatdb.SensitiveWord, error)
CheckSensitiveWords(ctx context.Context, content string) ([]*chatdb.SensitiveWord, bool, error)
FilterContent(ctx context.Context, content string) (string, []*chatdb.SensitiveWord, bool, error)
GetSensitiveWordConfig(ctx context.Context) (*chatdb.SensitiveWordConfig, error)
// 敏感词管理相关方法
CreateSensitiveWord(ctx context.Context, word *chatdb.SensitiveWord) error
UpdateSensitiveWord(ctx context.Context, id string, data map[string]any) error
DeleteSensitiveWord(ctx context.Context, ids []string) error
GetSensitiveWord(ctx context.Context, id string) (*chatdb.SensitiveWord, error)
SearchSensitiveWords(ctx context.Context, keyword string, action int32, status int32, pagination pagination.Pagination) (int64, []*chatdb.SensitiveWord, error)
BatchAddSensitiveWords(ctx context.Context, words []*chatdb.SensitiveWord) error
BatchUpdateSensitiveWords(ctx context.Context, updates map[string]map[string]any) error
BatchDeleteSensitiveWords(ctx context.Context, ids []string) error
// 敏感词分组管理
CreateSensitiveWordGroup(ctx context.Context, group *chatdb.SensitiveWordGroup) error
UpdateSensitiveWordGroup(ctx context.Context, id string, data map[string]any) error
DeleteSensitiveWordGroup(ctx context.Context, ids []string) error
GetSensitiveWordGroup(ctx context.Context, id string) (*chatdb.SensitiveWordGroup, error)
GetAllSensitiveWordGroups(ctx context.Context) ([]*chatdb.SensitiveWordGroup, error)
// 敏感词配置管理
UpdateSensitiveWordConfig(ctx context.Context, config *chatdb.SensitiveWordConfig) error
// 敏感词日志管理
GetSensitiveWordLogs(ctx context.Context, userID, groupID string, pagination pagination.Pagination) (int64, []*chatdb.SensitiveWordLog, error)
DeleteSensitiveWordLogs(ctx context.Context, ids []string) error
// 敏感词统计
GetSensitiveWordStats(ctx context.Context) (map[string]int64, error)
GetSensitiveWordLogStats(ctx context.Context, startTime, endTime time.Time) (map[string]int64, error)
// 收藏相关方法
CreateFavorite(ctx context.Context, favorite *chatdb.Favorite) error
GetFavorite(ctx context.Context, favoriteID string) (*chatdb.Favorite, error)
GetFavoritesByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error)
GetFavoritesByUserIDAndType(ctx context.Context, userID string, favoriteType int32, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error)
SearchFavoritesByKeyword(ctx context.Context, userID string, keyword string, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error)
UpdateFavorite(ctx context.Context, favoriteID string, data map[string]any) error
DeleteFavorite(ctx context.Context, favoriteIDs []string) error
DeleteFavoritesByUserID(ctx context.Context, userID string) error
CountFavoritesByUserID(ctx context.Context, userID string) (int64, error)
GetFavoritesByTags(ctx context.Context, userID string, tags []string, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error)
// 定时任务相关方法
CreateScheduledTask(ctx context.Context, task *chatdb.ScheduledTask) error
GetScheduledTask(ctx context.Context, taskID string) (*chatdb.ScheduledTask, error)
GetScheduledTasksByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.ScheduledTask, error)
GetAllScheduledTasks(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.ScheduledTask, error)
UpdateScheduledTask(ctx context.Context, taskID string, data map[string]any) error
DeleteScheduledTask(ctx context.Context, taskIDs []string) error
// 系统配置相关方法
CreateSystemConfig(ctx context.Context, config *chatdb.SystemConfig) error
GetSystemConfig(ctx context.Context, key string) (*chatdb.SystemConfig, error)
GetSystemConfigsByKeys(ctx context.Context, keys []string) ([]*chatdb.SystemConfig, error)
GetAllSystemConfigs(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.SystemConfig, error)
UpdateSystemConfig(ctx context.Context, key string, data map[string]any) error
UpdateSystemConfigValue(ctx context.Context, key string, value string) error
UpdateSystemConfigEnabled(ctx context.Context, key string, enabled bool) error
DeleteSystemConfig(ctx context.Context, keys []string) error
GetEnabledSystemConfigs(ctx context.Context) ([]*chatdb.SystemConfig, error)
GetAppSystemConfigs(ctx context.Context) ([]*chatdb.SystemConfig, error) // 获取所有 show_in_app=true 且 enabled=true 的配置
// 钱包相关方法
GetWallet(ctx context.Context, userID string) (*chatdb.Wallet, error) // 获取钱包信息
GetWalletsByUserIDs(ctx context.Context, userIDs []string) ([]*chatdb.Wallet, error) // 根据用户ID列表批量获取钱包
GetWalletsPage(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.Wallet, error) // 分页获取钱包列表
GetWalletsPageByRealNameAuthAuditStatus(ctx context.Context, auditStatus int32, userID string, pagination pagination.Pagination) (int64, []*chatdb.Wallet, error) // 按实名认证审核状态分页查询钱包支持用户ID搜索
CreateWallet(ctx context.Context, wallet *chatdb.Wallet) error // 创建钱包
UpdateWalletBalance(ctx context.Context, userID string, balance int64) error // 更新钱包余额
IncrementWalletBalance(ctx context.Context, userID string, amount int64) (beforeBalance int64, afterBalance int64, err error) // 原子更新余额,返回更新前后的余额
UpdateWalletPaymentPassword(ctx context.Context, userID string, paymentPassword string) error // 更新支付密码
UpdateWalletWithdrawAccount(ctx context.Context, userID string, withdrawAccount string) error // 更新提款账号(兼容旧接口)
UpdateWalletWithdrawAccountWithType(ctx context.Context, userID string, withdrawAccount string, accountType int32) error // 更新提款账号(带类型)
UpdateWalletRealNameAuth(ctx context.Context, userID string, realNameAuth chatdb.RealNameAuth) error // 更新实名认证信息
GetWalletBalanceRecords(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) // 获取钱包余额变动记录
GetWalletBalanceRecordsByUserIDAndType(ctx context.Context, userID string, recordType int32, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) // 根据类型获取钱包余额变动记录
CreateWalletBalanceRecord(ctx context.Context, record *chatdb.WalletBalanceRecord) error // 创建余额变动记录
// 提现相关方法
CreateWithdraw(ctx context.Context, withdraw *chatdb.Withdraw) error // 创建提现记录
GetWithdraw(ctx context.Context, withdrawID string) (*chatdb.Withdraw, error) // 获取提现记录
GetWithdrawsByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) // 获取用户的提现记录列表
GetWithdrawsByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) // 根据状态获取提现记录列表
UpdateWithdrawStatus(ctx context.Context, withdrawID string, status int32, auditorID string, auditRemark string) error // 更新提现状态(审核)
GetWithdrawsPage(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) // 获取提现记录列表(分页)
// 提现申请相关方法
CreateWithdrawApplication(ctx context.Context, application *chatdb.WithdrawApplication) error // 创建提现申请
GetWithdrawApplication(ctx context.Context, applicationID string) (*chatdb.WithdrawApplication, error) // 获取提现申请
GetWithdrawApplicationsByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) // 获取用户的提现申请列表
GetWithdrawApplicationsByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) // 根据状态获取提现申请列表
UpdateWithdrawApplicationStatus(ctx context.Context, applicationID string, status int32, auditorID string, auditRemark string) error // 更新提现申请状态(审核)
GetWithdrawApplicationsPage(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) // 获取提现申请列表(分页)
UpdateWithdrawApplication(ctx context.Context, applicationID string, data map[string]any) error // 更新提现申请
}
func NewChatDatabase(cli *mongoutil.Client, rdb redis.UniversalClient) (ChatDatabaseInterface, error) {
register, err := chat.NewRegister(cli.GetDB())
if err != nil {
return nil, err
}
account, err := chat.NewAccount(cli.GetDB())
if err != nil {
return nil, err
}
attribute, err := chat.NewAttribute(cli.GetDB())
if err != nil {
return nil, err
}
credential, err := chat.NewCredential(cli.GetDB())
if err != nil {
return nil, err
}
userLoginRecord, err := chat.NewUserLoginRecord(cli.GetDB())
if err != nil {
return nil, err
}
verifyCode, err := chat.NewVerifyCode(cli.GetDB())
if err != nil {
return nil, err
}
forbiddenAccount, err := admindb.NewForbiddenAccount(cli.GetDB())
if err != nil {
return nil, err
}
sensitiveWord, err := chat.NewSensitiveWord(cli.GetDB())
if err != nil {
return nil, err
}
favorite, err := chat.NewFavorite(cli.GetDB())
if err != nil {
return nil, err
}
scheduledTask, err := chat.NewScheduledTask(cli.GetDB())
if err != nil {
return nil, err
}
systemConfig, err := chat.NewSystemConfig(cli.GetDB())
if err != nil {
return nil, err
}
wallet, err := chat.NewWallet(cli.GetDB())
if err != nil {
return nil, err
}
walletBalanceRecord, err := chat.NewWalletBalanceRecord(cli.GetDB())
if err != nil {
return nil, err
}
withdraw, err := chat.NewWithdraw(cli.GetDB())
if err != nil {
return nil, err
}
withdrawApplication, err := chat.NewWithdrawApplication(cli.GetDB())
if err != nil {
return nil, err
}
var userLoginIPCache cache.UserLoginIPInterface
if rdb != nil {
userLoginIPCache = cache.NewUserLoginIPInterface(rdb)
}
return &ChatDatabase{
tx: cli.GetTx(),
register: register,
account: account,
attribute: attribute,
credential: credential,
userLoginRecord: userLoginRecord,
verifyCode: verifyCode,
forbiddenAccount: forbiddenAccount,
sensitiveWord: sensitiveWord,
favorite: favorite,
scheduledTask: scheduledTask,
systemConfig: systemConfig,
wallet: wallet,
walletBalanceRecord: walletBalanceRecord,
withdraw: withdraw,
withdrawApplication: withdrawApplication,
userLoginIPCache: userLoginIPCache,
}, nil
}
type ChatDatabase struct {
tx tx.Tx
register chatdb.RegisterInterface
account chatdb.AccountInterface
attribute chatdb.AttributeInterface
credential chatdb.CredentialInterface
userLoginRecord chatdb.UserLoginRecordInterface
verifyCode chatdb.VerifyCodeInterface
forbiddenAccount admin.ForbiddenAccountInterface
sensitiveWord chatdb.SensitiveWordInterface
favorite chatdb.FavoriteInterface
scheduledTask chatdb.ScheduledTaskInterface
systemConfig chatdb.SystemConfigInterface
wallet chatdb.WalletInterface
walletBalanceRecord chatdb.WalletBalanceRecordInterface
withdraw chatdb.WithdrawInterface
withdrawApplication chatdb.WithdrawApplicationInterface
userLoginIPCache cache.UserLoginIPInterface // 用户登录IP缓存可选如果为nil则不使用缓存
}
func (o *ChatDatabase) GetUser(ctx context.Context, userID string) (account *chatdb.Account, err error) {
return o.account.Take(ctx, userID)
}
func (o *ChatDatabase) UpdateUseInfo(ctx context.Context, userID string, attribute map[string]any, updateCred, delCred []*chatdb.Credential) (err error) {
return o.tx.Transaction(ctx, func(ctx context.Context) error {
if err = o.attribute.Update(ctx, userID, attribute); err != nil {
return err
}
for _, credential := range updateCred {
if err = o.credential.CreateOrUpdateAccount(ctx, credential); err != nil {
return err
}
}
if err = o.credential.DeleteByUserIDType(ctx, delCred...); err != nil {
return err
}
return nil
})
}
func (o *ChatDatabase) FindAttribute(ctx context.Context, userIDs []string) ([]*chatdb.Attribute, error) {
return o.attribute.Find(ctx, userIDs)
}
func (o *ChatDatabase) FindAttributeByAccount(ctx context.Context, accounts []string) ([]*chatdb.Attribute, error) {
return o.attribute.FindAccount(ctx, accounts)
}
func (o *ChatDatabase) TakeAttributeByPhone(ctx context.Context, areaCode string, phoneNumber string) (*chatdb.Attribute, error) {
return o.attribute.TakePhone(ctx, areaCode, phoneNumber)
}
func (o *ChatDatabase) TakeAttributeByEmail(ctx context.Context, email string) (*chatdb.Attribute, error) {
return o.attribute.TakeEmail(ctx, email)
}
func (o *ChatDatabase) TakeAttributeByAccount(ctx context.Context, account string) (*chatdb.Attribute, error) {
return o.attribute.TakeAccount(ctx, account)
}
func (o *ChatDatabase) TakeAttributeByUserID(ctx context.Context, userID string) (*chatdb.Attribute, error) {
return o.attribute.Take(ctx, userID)
}
func (o *ChatDatabase) TakeLastVerifyCode(ctx context.Context, account string) (*chatdb.VerifyCode, error) {
return o.verifyCode.TakeLast(ctx, account)
}
func (o *ChatDatabase) TakeAccount(ctx context.Context, userID string) (*chatdb.Account, error) {
return o.account.Take(ctx, userID)
}
func (o *ChatDatabase) TakeCredentialByAccount(ctx context.Context, account string) (*chatdb.Credential, error) {
return o.credential.TakeAccount(ctx, account)
}
func (o *ChatDatabase) TakeCredentialsByUserID(ctx context.Context, userID string) ([]*chatdb.Credential, error) {
return o.credential.Find(ctx, userID)
}
func (o *ChatDatabase) Search(ctx context.Context, normalUser int32, keyword string, genders int32, startTime, endTime *time.Time, pagination pagination.Pagination) (total int64, attributes []*chatdb.Attribute, err error) {
var forbiddenIDs []string
if int(normalUser) == constant.NormalUser {
forbiddenIDs, err = o.forbiddenAccount.FindAllIDs(ctx)
if err != nil {
return 0, nil, err
}
}
total, totalUser, err := o.attribute.SearchNormalUser(ctx, keyword, forbiddenIDs, genders, startTime, endTime, pagination)
if err != nil {
return 0, nil, err
}
return total, totalUser, nil
}
// SearchWithRealNameAuth 搜索用户(支持实名信息搜索)
func (o *ChatDatabase) SearchWithRealNameAuth(ctx context.Context, normalUser int32, keyword string, genders int32, startTime, endTime *time.Time, realNameKeyword string, idCardKeyword string, pagination pagination.Pagination) (total int64, attributes []*chatdb.Attribute, err error) {
// 如果提供了实名信息搜索关键词先查询钱包获取userIDs
var realNameAuthUserIDs []string
if realNameKeyword != "" || idCardKeyword != "" {
realNameAuthUserIDs, err = o.wallet.SearchByRealNameAuth(ctx, realNameKeyword, idCardKeyword)
if err != nil {
return 0, nil, err
}
// 如果没有找到匹配的用户,直接返回空结果
if len(realNameAuthUserIDs) == 0 {
return 0, []*chatdb.Attribute{}, nil
}
}
var forbiddenIDs []string
if int(normalUser) == constant.NormalUser {
forbiddenIDs, err = o.forbiddenAccount.FindAllIDs(ctx)
if err != nil {
return 0, nil, err
}
}
// 如果提供了实名信息搜索需要在用户ID列表中过滤
if len(realNameAuthUserIDs) > 0 {
// 修改 SearchNormalUser 方法,支持传入额外的 userIDs 过滤条件
// 或者创建一个新的方法
// 这里我们修改 SearchNormalUser 来支持这个功能
total, totalUser, err := o.attribute.SearchNormalUserWithUserIDs(ctx, keyword, forbiddenIDs, genders, startTime, endTime, realNameAuthUserIDs, pagination)
if err != nil {
return 0, nil, err
}
return total, totalUser, nil
}
// 没有实名信息搜索,使用原来的方法
return o.Search(ctx, normalUser, keyword, genders, startTime, endTime, pagination)
}
func (o *ChatDatabase) SearchUser(ctx context.Context, keyword string, userIDs []string, genders []int32, pagination pagination.Pagination) (int64, []*chatdb.Attribute, error) {
return o.attribute.SearchUser(ctx, keyword, userIDs, genders, pagination)
}
func (o *ChatDatabase) CountVerifyCodeRange(ctx context.Context, account string, start time.Time, end time.Time) (int64, error) {
return o.verifyCode.RangeNum(ctx, account, start, end)
}
func (o *ChatDatabase) AddVerifyCode(ctx context.Context, verifyCode *chatdb.VerifyCode, fn func() error) error {
return o.tx.Transaction(ctx, func(ctx context.Context) error {
if err := o.verifyCode.Add(ctx, []*chatdb.VerifyCode{verifyCode}); err != nil {
return err
}
if fn != nil {
return fn()
}
return nil
})
}
func (o *ChatDatabase) UpdateVerifyCodeIncrCount(ctx context.Context, id string) error {
return o.verifyCode.Incr(ctx, id)
}
func (o *ChatDatabase) DelVerifyCode(ctx context.Context, id string) error {
return o.verifyCode.Delete(ctx, id)
}
func (o *ChatDatabase) RegisterUser(ctx context.Context, register *chatdb.Register, account *chatdb.Account, attribute *chatdb.Attribute, credentials []*chatdb.Credential) error {
return o.tx.Transaction(ctx, func(ctx context.Context) error {
if err := o.register.Create(ctx, register); err != nil {
return err
}
if err := o.account.Create(ctx, account); err != nil {
return err
}
if err := o.attribute.Create(ctx, attribute); err != nil {
return err
}
if err := o.credential.Create(ctx, credentials...); err != nil {
return err
}
return nil
})
}
func (o *ChatDatabase) LoginRecord(ctx context.Context, record *chatdb.UserLoginRecord, verifyCodeID *string) error {
return o.tx.Transaction(ctx, func(ctx context.Context) error {
if err := o.userLoginRecord.Create(ctx, record); err != nil {
return err
}
// 创建登录记录后,更新缓存(保证缓存一致性)
if o.userLoginIPCache != nil && record.UserID != "" {
// 先删除旧缓存,然后设置新缓存
// 使用新IP更新缓存这样下次查询时可以直接从缓存获取
_ = o.userLoginIPCache.SetLatestLoginIP(ctx, record.UserID, record.IP)
}
if verifyCodeID != nil {
if err := o.verifyCode.Delete(ctx, *verifyCodeID); err != nil {
return err
}
}
return nil
})
}
func (o *ChatDatabase) UpdatePassword(ctx context.Context, userID string, password string) error {
return o.account.UpdatePassword(ctx, userID, password)
}
func (o *ChatDatabase) UpdatePasswordAndDeleteVerifyCode(ctx context.Context, userID string, password string, codeID string) error {
return o.tx.Transaction(ctx, func(ctx context.Context) error {
if err := o.account.UpdatePassword(ctx, userID, password); err != nil {
return err
}
if codeID == "" {
return nil
}
if err := o.verifyCode.Delete(ctx, codeID); err != nil {
return err
}
return nil
})
}
func (o *ChatDatabase) NewUserCountTotal(ctx context.Context, before *time.Time) (int64, error) {
return o.register.CountTotal(ctx, before)
}
func (o *ChatDatabase) UserLoginCountTotal(ctx context.Context, before *time.Time) (int64, error) {
return o.userLoginRecord.CountTotal(ctx, before)
}
func (o *ChatDatabase) UserLoginCountRangeEverydayTotal(ctx context.Context, start *time.Time, end *time.Time) (map[string]int64, int64, error) {
return o.userLoginRecord.CountRangeEverydayTotal(ctx, start, end)
}
func (o *ChatDatabase) CountTodayRegisteredUsers(ctx context.Context) (int64, error) {
return o.register.CountToday(ctx)
}
func (o *ChatDatabase) CountTodayActiveUsers(ctx context.Context) (int64, error) {
return o.userLoginRecord.CountTodayActiveUsers(ctx)
}
func (o *ChatDatabase) GetLatestLoginIP(ctx context.Context, userID string) (string, error) {
// 如果启用了缓存,先尝试从缓存获取
if o.userLoginIPCache != nil {
ip, found, err := o.userLoginIPCache.GetLatestLoginIP(ctx, userID)
if err != nil {
// 缓存查询出错,降级到数据库查询
return o.userLoginRecord.GetLatestLoginIP(ctx, userID)
}
// 如果缓存命中直接返回即使IP为空字符串也表示用户确实没有IP
if found {
return ip, nil
}
// 缓存未命中,从数据库查询
ip, err = o.userLoginRecord.GetLatestLoginIP(ctx, userID)
if err != nil {
return "", err
}
// 将查询结果写入缓存(即使为空也缓存,避免频繁查询数据库)
_ = o.userLoginIPCache.SetLatestLoginIP(ctx, userID, ip)
return ip, nil
}
// 未启用缓存,直接从数据库查询
return o.userLoginRecord.GetLatestLoginIP(ctx, userID)
}
func (o *ChatDatabase) SearchUserLoginRecords(ctx context.Context, userID, ip string, pagination pagination.Pagination) (int64, []*chatdb.UserLoginRecord, error) {
return o.userLoginRecord.Search(ctx, userID, ip, pagination)
}
func (o *ChatDatabase) DelUserAccount(ctx context.Context, userIDs []string) error {
return o.tx.Transaction(ctx, func(ctx context.Context) error {
if err := o.register.Delete(ctx, userIDs); err != nil {
return err
}
if err := o.account.Delete(ctx, userIDs); err != nil {
return err
}
if err := o.attribute.Delete(ctx, userIDs); err != nil {
return err
}
return nil
})
}
// ==================== 敏感词相关方法实现 ====================
// GetSensitiveWords 获取所有启用的敏感词
func (o *ChatDatabase) GetSensitiveWords(ctx context.Context) ([]*chatdb.SensitiveWord, error) {
return o.sensitiveWord.GetEnabledSensitiveWords(ctx)
}
// CheckSensitiveWords 检测敏感词
func (o *ChatDatabase) CheckSensitiveWords(ctx context.Context, content string) ([]*chatdb.SensitiveWord, bool, error) {
words, err := o.sensitiveWord.CheckSensitiveWords(ctx, content)
hasSensitive := len(words) > 0
return words, hasSensitive, err
}
// FilterContent 过滤内容
func (o *ChatDatabase) FilterContent(ctx context.Context, content string) (string, []*chatdb.SensitiveWord, bool, error) {
filteredContent, words, err := o.sensitiveWord.FilterContent(ctx, content)
hasSensitive := len(words) > 0
return filteredContent, words, hasSensitive, err
}
// GetSensitiveWordConfig 获取敏感词配置
func (o *ChatDatabase) GetSensitiveWordConfig(ctx context.Context) (*chatdb.SensitiveWordConfig, error) {
return o.sensitiveWord.GetSensitiveWordConfig(ctx)
}
// ==================== 敏感词管理相关方法实现 ====================
func (o *ChatDatabase) CreateSensitiveWord(ctx context.Context, word *chatdb.SensitiveWord) error {
return o.sensitiveWord.CreateSensitiveWord(ctx, word)
}
func (o *ChatDatabase) UpdateSensitiveWord(ctx context.Context, id string, data map[string]any) error {
return o.sensitiveWord.UpdateSensitiveWord(ctx, id, data)
}
func (o *ChatDatabase) DeleteSensitiveWord(ctx context.Context, ids []string) error {
return o.sensitiveWord.DeleteSensitiveWord(ctx, ids)
}
func (o *ChatDatabase) GetSensitiveWord(ctx context.Context, id string) (*chatdb.SensitiveWord, error) {
return o.sensitiveWord.GetSensitiveWord(ctx, id)
}
func (o *ChatDatabase) SearchSensitiveWords(ctx context.Context, keyword string, action int32, status int32, pagination pagination.Pagination) (int64, []*chatdb.SensitiveWord, error) {
return o.sensitiveWord.SearchSensitiveWords(ctx, keyword, action, status, pagination)
}
func (o *ChatDatabase) BatchAddSensitiveWords(ctx context.Context, words []*chatdb.SensitiveWord) error {
return o.sensitiveWord.BatchCreateSensitiveWords(ctx, words)
}
func (o *ChatDatabase) BatchUpdateSensitiveWords(ctx context.Context, updates map[string]map[string]any) error {
return o.sensitiveWord.BatchUpdateSensitiveWords(ctx, updates)
}
func (o *ChatDatabase) BatchDeleteSensitiveWords(ctx context.Context, ids []string) error {
return o.sensitiveWord.BatchDeleteSensitiveWords(ctx, ids)
}
// ==================== 敏感词分组管理实现 ====================
func (o *ChatDatabase) CreateSensitiveWordGroup(ctx context.Context, group *chatdb.SensitiveWordGroup) error {
return o.sensitiveWord.CreateSensitiveWordGroup(ctx, group)
}
func (o *ChatDatabase) UpdateSensitiveWordGroup(ctx context.Context, id string, data map[string]any) error {
return o.sensitiveWord.UpdateSensitiveWordGroup(ctx, id, data)
}
func (o *ChatDatabase) DeleteSensitiveWordGroup(ctx context.Context, ids []string) error {
return o.sensitiveWord.DeleteSensitiveWordGroup(ctx, ids)
}
func (o *ChatDatabase) GetSensitiveWordGroup(ctx context.Context, id string) (*chatdb.SensitiveWordGroup, error) {
return o.sensitiveWord.GetSensitiveWordGroup(ctx, id)
}
func (o *ChatDatabase) GetAllSensitiveWordGroups(ctx context.Context) ([]*chatdb.SensitiveWordGroup, error) {
return o.sensitiveWord.GetAllSensitiveWordGroups(ctx)
}
// ==================== 敏感词配置管理实现 ====================
func (o *ChatDatabase) UpdateSensitiveWordConfig(ctx context.Context, config *chatdb.SensitiveWordConfig) error {
return o.sensitiveWord.UpdateSensitiveWordConfig(ctx, config)
}
// ==================== 敏感词日志管理实现 ====================
func (o *ChatDatabase) GetSensitiveWordLogs(ctx context.Context, userID, groupID string, pagination pagination.Pagination) (int64, []*chatdb.SensitiveWordLog, error) {
return o.sensitiveWord.GetSensitiveWordLogs(ctx, userID, groupID, pagination)
}
func (o *ChatDatabase) DeleteSensitiveWordLogs(ctx context.Context, ids []string) error {
return o.sensitiveWord.DeleteSensitiveWordLogs(ctx, ids)
}
// ==================== 敏感词统计实现 ====================
func (o *ChatDatabase) GetSensitiveWordStats(ctx context.Context) (map[string]int64, error) {
return o.sensitiveWord.GetSensitiveWordStats(ctx)
}
func (o *ChatDatabase) GetSensitiveWordLogStats(ctx context.Context, startTime, endTime time.Time) (map[string]int64, error) {
return o.sensitiveWord.GetSensitiveWordLogStats(ctx, startTime, endTime)
}
// ==================== 收藏相关方法实现 ====================
func (o *ChatDatabase) CreateFavorite(ctx context.Context, favorite *chatdb.Favorite) error {
return o.favorite.Create(ctx, favorite)
}
func (o *ChatDatabase) GetFavorite(ctx context.Context, favoriteID string) (*chatdb.Favorite, error) {
return o.favorite.Take(ctx, favoriteID)
}
func (o *ChatDatabase) GetFavoritesByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error) {
return o.favorite.FindByUserID(ctx, userID, pagination)
}
func (o *ChatDatabase) GetFavoritesByUserIDAndType(ctx context.Context, userID string, favoriteType int32, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error) {
return o.favorite.FindByUserIDAndType(ctx, userID, favoriteType, pagination)
}
func (o *ChatDatabase) SearchFavoritesByKeyword(ctx context.Context, userID string, keyword string, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error) {
return o.favorite.SearchByKeyword(ctx, userID, keyword, pagination)
}
func (o *ChatDatabase) UpdateFavorite(ctx context.Context, favoriteID string, data map[string]any) error {
return o.favorite.Update(ctx, favoriteID, data)
}
func (o *ChatDatabase) DeleteFavorite(ctx context.Context, favoriteIDs []string) error {
return o.favorite.Delete(ctx, favoriteIDs)
}
func (o *ChatDatabase) DeleteFavoritesByUserID(ctx context.Context, userID string) error {
return o.favorite.DeleteByUserID(ctx, userID)
}
func (o *ChatDatabase) CountFavoritesByUserID(ctx context.Context, userID string) (int64, error) {
return o.favorite.CountByUserID(ctx, userID)
}
func (o *ChatDatabase) GetFavoritesByTags(ctx context.Context, userID string, tags []string, pagination pagination.Pagination) (int64, []*chatdb.Favorite, error) {
return o.favorite.FindByTags(ctx, userID, tags, pagination)
}
// ==================== 定时任务相关方法实现 ====================
func (o *ChatDatabase) CreateScheduledTask(ctx context.Context, task *chatdb.ScheduledTask) error {
return o.scheduledTask.Create(ctx, task)
}
func (o *ChatDatabase) GetScheduledTask(ctx context.Context, taskID string) (*chatdb.ScheduledTask, error) {
return o.scheduledTask.Take(ctx, taskID)
}
func (o *ChatDatabase) GetScheduledTasksByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.ScheduledTask, error) {
return o.scheduledTask.FindByUserID(ctx, userID, pagination)
}
func (o *ChatDatabase) GetAllScheduledTasks(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.ScheduledTask, error) {
return o.scheduledTask.FindAll(ctx, pagination)
}
func (o *ChatDatabase) UpdateScheduledTask(ctx context.Context, taskID string, data map[string]any) error {
return o.scheduledTask.Update(ctx, taskID, data)
}
func (o *ChatDatabase) DeleteScheduledTask(ctx context.Context, taskIDs []string) error {
return o.scheduledTask.Delete(ctx, taskIDs)
}
// ==================== 系统配置相关方法实现 ====================
func (o *ChatDatabase) CreateSystemConfig(ctx context.Context, config *chatdb.SystemConfig) error {
return o.systemConfig.Create(ctx, config)
}
func (o *ChatDatabase) GetSystemConfig(ctx context.Context, key string) (*chatdb.SystemConfig, error) {
return o.systemConfig.Take(ctx, key)
}
func (o *ChatDatabase) GetSystemConfigsByKeys(ctx context.Context, keys []string) ([]*chatdb.SystemConfig, error) {
return o.systemConfig.FindByKeys(ctx, keys)
}
func (o *ChatDatabase) GetAllSystemConfigs(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.SystemConfig, error) {
return o.systemConfig.FindAll(ctx, pagination)
}
func (o *ChatDatabase) UpdateSystemConfig(ctx context.Context, key string, data map[string]any) error {
return o.systemConfig.Update(ctx, key, data)
}
func (o *ChatDatabase) UpdateSystemConfigValue(ctx context.Context, key string, value string) error {
return o.systemConfig.UpdateValue(ctx, key, value)
}
func (o *ChatDatabase) UpdateSystemConfigEnabled(ctx context.Context, key string, enabled bool) error {
return o.systemConfig.UpdateEnabled(ctx, key, enabled)
}
func (o *ChatDatabase) DeleteSystemConfig(ctx context.Context, keys []string) error {
return o.systemConfig.Delete(ctx, keys)
}
func (o *ChatDatabase) GetEnabledSystemConfigs(ctx context.Context) ([]*chatdb.SystemConfig, error) {
return o.systemConfig.GetEnabledConfigs(ctx)
}
func (o *ChatDatabase) GetAppSystemConfigs(ctx context.Context) ([]*chatdb.SystemConfig, error) {
return o.systemConfig.GetAppConfigs(ctx)
}
func (o *ChatDatabase) GetWallet(ctx context.Context, userID string) (*chatdb.Wallet, error) {
return o.wallet.Take(ctx, userID)
}
func (o *ChatDatabase) GetWalletsByUserIDs(ctx context.Context, userIDs []string) ([]*chatdb.Wallet, error) {
return o.wallet.Find(ctx, userIDs)
}
func (o *ChatDatabase) GetWalletsPage(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.Wallet, error) {
return o.wallet.Page(ctx, pagination)
}
func (o *ChatDatabase) GetWalletsPageByRealNameAuthAuditStatus(ctx context.Context, auditStatus int32, userID string, pagination pagination.Pagination) (int64, []*chatdb.Wallet, error) {
return o.wallet.PageByRealNameAuthAuditStatus(ctx, auditStatus, userID, pagination)
}
func (o *ChatDatabase) UpdateWalletBalance(ctx context.Context, userID string, balance int64) error {
return o.wallet.UpdateBalance(ctx, userID, balance)
}
func (o *ChatDatabase) IncrementWalletBalance(ctx context.Context, userID string, amount int64) (beforeBalance int64, afterBalance int64, err error) {
return o.wallet.IncrementBalance(ctx, userID, amount)
}
func (o *ChatDatabase) UpdateWalletPaymentPassword(ctx context.Context, userID string, paymentPassword string) error {
return o.wallet.UpdatePaymentPassword(ctx, userID, paymentPassword)
}
func (o *ChatDatabase) UpdateWalletWithdrawAccount(ctx context.Context, userID string, withdrawAccount string) error {
return o.wallet.UpdateWithdrawAccount(ctx, userID, withdrawAccount)
}
func (o *ChatDatabase) UpdateWalletWithdrawAccountWithType(ctx context.Context, userID string, withdrawAccount string, accountType int32) error {
return o.wallet.UpdateWithdrawAccountWithType(ctx, userID, withdrawAccount, accountType)
}
func (o *ChatDatabase) UpdateWalletRealNameAuth(ctx context.Context, userID string, realNameAuth chatdb.RealNameAuth) error {
return o.wallet.UpdateRealNameAuth(ctx, userID, realNameAuth)
}
func (o *ChatDatabase) CreateWallet(ctx context.Context, wallet *chatdb.Wallet) error {
return o.wallet.Create(ctx, wallet)
}
func (o *ChatDatabase) GetWalletBalanceRecords(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) {
return o.walletBalanceRecord.FindByUserID(ctx, userID, pagination)
}
func (o *ChatDatabase) GetWalletBalanceRecordsByUserIDAndType(ctx context.Context, userID string, recordType int32, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) {
return o.walletBalanceRecord.FindByUserIDAndType(ctx, userID, recordType, pagination)
}
func (o *ChatDatabase) CreateWalletBalanceRecord(ctx context.Context, record *chatdb.WalletBalanceRecord) error {
return o.walletBalanceRecord.Create(ctx, record)
}
// ==================== 提现相关方法 ====================
func (o *ChatDatabase) CreateWithdraw(ctx context.Context, withdraw *chatdb.Withdraw) error {
return o.withdraw.Create(ctx, withdraw)
}
func (o *ChatDatabase) GetWithdraw(ctx context.Context, withdrawID string) (*chatdb.Withdraw, error) {
return o.withdraw.Take(ctx, withdrawID)
}
func (o *ChatDatabase) GetWithdrawsByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) {
return o.withdraw.FindByUserID(ctx, userID, pagination)
}
func (o *ChatDatabase) GetWithdrawsByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) {
return o.withdraw.FindByStatus(ctx, status, pagination)
}
func (o *ChatDatabase) UpdateWithdrawStatus(ctx context.Context, withdrawID string, status int32, auditorID string, auditRemark string) error {
return o.withdraw.UpdateStatus(ctx, withdrawID, status, auditorID, auditRemark)
}
func (o *ChatDatabase) GetWithdrawsPage(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) {
return o.withdraw.Page(ctx, pagination)
}
// ==================== 提现申请相关方法 ====================
func (o *ChatDatabase) CreateWithdrawApplication(ctx context.Context, application *chatdb.WithdrawApplication) error {
return o.withdrawApplication.Create(ctx, application)
}
func (o *ChatDatabase) GetWithdrawApplication(ctx context.Context, applicationID string) (*chatdb.WithdrawApplication, error) {
return o.withdrawApplication.Take(ctx, applicationID)
}
func (o *ChatDatabase) GetWithdrawApplicationsByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) {
return o.withdrawApplication.FindByUserID(ctx, userID, pagination)
}
func (o *ChatDatabase) GetWithdrawApplicationsByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) {
return o.withdrawApplication.FindByStatus(ctx, status, pagination)
}
func (o *ChatDatabase) UpdateWithdrawApplicationStatus(ctx context.Context, applicationID string, status int32, auditorID string, auditRemark string) error {
return o.withdrawApplication.UpdateStatus(ctx, applicationID, status, auditorID, auditRemark)
}
func (o *ChatDatabase) GetWithdrawApplicationsPage(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) {
return o.withdrawApplication.Page(ctx, pagination)
}
func (o *ChatDatabase) UpdateWithdrawApplication(ctx context.Context, applicationID string, data map[string]any) error {
return o.withdrawApplication.Update(ctx, applicationID, data)
}

View File

@@ -0,0 +1,24 @@
// Copyright © 2023 OpenIM open source community. 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 dbutil
import (
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/mongo"
)
func IsDBNotFound(err error) bool {
return errs.Unwrap(err) == mongo.ErrNoDocuments
}

View File

@@ -0,0 +1,103 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
admindb "git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewAdmin(db *mongo.Database) (admindb.AdminInterface, error) {
coll := db.Collection("admin")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "account", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Admin{
coll: coll,
}, nil
}
type Admin struct {
coll *mongo.Collection
}
func (o *Admin) Take(ctx context.Context, account string) (*admindb.Admin, error) {
return mongoutil.FindOne[*admindb.Admin](ctx, o.coll, bson.M{"account": account})
}
func (o *Admin) TakeUserID(ctx context.Context, userID string) (*admindb.Admin, error) {
return mongoutil.FindOne[*admindb.Admin](ctx, o.coll, bson.M{"user_id": userID})
}
func (o *Admin) Update(ctx context.Context, account string, update map[string]any) error {
if len(update) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": account}, bson.M{"$set": update}, false)
}
func (o *Admin) Create(ctx context.Context, admins []*admindb.Admin) error {
return mongoutil.InsertMany(ctx, o.coll, admins)
}
func (o *Admin) ChangePassword(ctx context.Context, userID string, newPassword string) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"password": newPassword}}, false)
}
func (o *Admin) ChangeOperationPassword(ctx context.Context, userID string, newPassword string) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"operation_password": newPassword}}, false)
}
func (o *Admin) ClearGoogleAuthKey(ctx context.Context, userID string) error {
// 使用 $unset 删除字段,确保字段被完全移除
// 注意:$unset 操作符的值可以是任意值通常使用空字符串或1MongoDB 会忽略值,只删除字段
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$unset": bson.M{"google_auth_key": 1}}, false)
}
func (o *Admin) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *Admin) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admindb.Admin, error) {
opt := options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}})
filter := bson.M{}
// 如果有关键词则进行模糊搜索账号、昵称、用户ID
if keyword != "" {
filter["$or"] = []bson.M{
{"account": bson.M{"$regex": keyword, "$options": "i"}},
{"nickname": bson.M{"$regex": keyword, "$options": "i"}},
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*admindb.Admin](ctx, o.coll, filter, pagination, opt)
}

View File

@@ -0,0 +1,95 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/constant"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewApplet(db *mongo.Database) (admin.AppletInterface, error) {
coll := db.Collection("applet")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Applet{
coll: coll,
}, nil
}
type Applet struct {
coll *mongo.Collection
}
func (o *Applet) Create(ctx context.Context, applets []*admin.Applet) error {
return mongoutil.InsertMany(ctx, o.coll, applets)
}
func (o *Applet) Del(ctx context.Context, ids []string) error {
if len(ids) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"id": bson.M{"$in": ids}})
}
func (o *Applet) Update(ctx context.Context, id string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"id": id}, bson.M{"$set": data}, false)
}
func (o *Applet) Take(ctx context.Context, id string) (*admin.Applet, error) {
return mongoutil.FindOne[*admin.Applet](ctx, o.coll, bson.M{"id": id})
}
func (o *Applet) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admin.Applet, error) {
filter := bson.M{}
if keyword != "" {
filter = bson.M{
"$or": []bson.M{
{"name": bson.M{"$regex": keyword, "$options": "i"}},
{"id": bson.M{"$regex": keyword, "$options": "i"}},
{"app_id": bson.M{"$regex": keyword, "$options": "i"}},
{"version": bson.M{"$regex": keyword, "$options": "i"}},
},
}
}
return mongoutil.FindPage[*admin.Applet](ctx, o.coll, filter, pagination)
}
func (o *Applet) FindOnShelf(ctx context.Context) ([]*admin.Applet, error) {
return mongoutil.Find[*admin.Applet](ctx, o.coll, bson.M{"status": constant.StatusOnShelf})
}
func (o *Applet) FindID(ctx context.Context, ids []string) ([]*admin.Applet, error) {
return mongoutil.Find[*admin.Applet](ctx, o.coll, bson.M{"id": bson.M{"$in": ids}})
}

View File

@@ -0,0 +1,84 @@
package admin
import (
"context"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
admindb "git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewApplication(db *mongo.Database) (admindb.ApplicationInterface, error) {
coll := db.Collection("application")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "platform", Value: 1},
{Key: "version", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "latest", Value: -1},
},
},
})
if err != nil {
return nil, err
}
return &ApplicationMgo{coll: coll}, nil
}
type ApplicationMgo struct {
coll *mongo.Collection
}
func (a *ApplicationMgo) sort() any {
return bson.D{{"latest", -1}, {"_id", -1}}
}
func (a *ApplicationMgo) LatestVersion(ctx context.Context, platform string) (*admin.Application, error) {
return mongoutil.FindOne[*admin.Application](ctx, a.coll, bson.M{"platform": platform}, options.FindOne().SetSort(a.sort()))
}
func (a *ApplicationMgo) AddVersion(ctx context.Context, val *admin.Application) error {
if val.ID.IsZero() {
val.ID = primitive.NewObjectID()
}
return mongoutil.InsertMany(ctx, a.coll, []*admin.Application{val})
}
func (a *ApplicationMgo) UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error {
if len(update) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, a.coll, bson.M{"_id": id}, bson.M{"$set": update}, true)
}
func (a *ApplicationMgo) DeleteVersion(ctx context.Context, id []primitive.ObjectID) error {
if len(id) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, a.coll, bson.M{"_id": bson.M{"$in": id}})
}
func (a *ApplicationMgo) PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*admin.Application, error) {
filter := bson.M{}
if len(platforms) > 0 {
filter["platform"] = bson.M{"$in": platforms}
}
return mongoutil.FindPage[*admin.Application](ctx, a.coll, filter, page, options.Find().SetSort(a.sort()))
}
func (a *ApplicationMgo) FindPlatform(ctx context.Context, id []primitive.ObjectID) ([]string, error) {
if len(id) == 0 {
return nil, nil
}
return mongoutil.Find[string](ctx, a.coll, bson.M{"_id": bson.M{"$in": id}}, options.Find().SetProjection(bson.M{"_id": 0, "platform": 1}))
}

View File

@@ -0,0 +1,77 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewClientConfig(db *mongo.Database) (admin.ClientConfigInterface, error) {
coll := db.Collection("client_config")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "key", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &ClientConfig{
coll: coll,
}, nil
}
type ClientConfig struct {
coll *mongo.Collection
}
func (o *ClientConfig) Set(ctx context.Context, config map[string]string) error {
for key, value := range config {
filter := bson.M{"key": key}
update := bson.M{
"value": value,
}
err := mongoutil.UpdateOne(ctx, o.coll, filter, bson.M{"$set": update}, false, options.Update().SetUpsert(true))
if err != nil {
return err
}
}
return nil
}
func (o *ClientConfig) Del(ctx context.Context, keys []string) error {
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"key": bson.M{"$in": keys}})
}
func (o *ClientConfig) Get(ctx context.Context) (map[string]string, error) {
cs, err := mongoutil.Find[*admin.ClientConfig](ctx, o.coll, bson.M{})
if err != nil {
return nil, err
}
cm := make(map[string]string)
for _, config := range cs {
cm[config.Key] = config.Value
}
return cm, nil
}

View File

@@ -0,0 +1,86 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewForbiddenAccount(db *mongo.Database) (admin.ForbiddenAccountInterface, error) {
coll := db.Collection("forbidden_account")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &ForbiddenAccount{
coll: coll,
}, nil
}
type ForbiddenAccount struct {
coll *mongo.Collection
}
func (o *ForbiddenAccount) Create(ctx context.Context, ms []*admin.ForbiddenAccount) error {
return mongoutil.InsertMany(ctx, o.coll, ms)
}
func (o *ForbiddenAccount) Take(ctx context.Context, userID string) (*admin.ForbiddenAccount, error) {
return mongoutil.FindOne[*admin.ForbiddenAccount](ctx, o.coll, bson.M{"user_id": userID})
}
func (o *ForbiddenAccount) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *ForbiddenAccount) Find(ctx context.Context, userIDs []string) ([]*admin.ForbiddenAccount, error) {
return mongoutil.Find[*admin.ForbiddenAccount](ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *ForbiddenAccount) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admin.ForbiddenAccount, error) {
filter := bson.M{}
if keyword != "" {
filter = bson.M{
"$or": []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"reason": bson.M{"$regex": keyword, "$options": "i"}},
{"operator_user_id": bson.M{"$regex": keyword, "$options": "i"}},
},
}
}
return mongoutil.FindPage[*admin.ForbiddenAccount](ctx, o.coll, filter, pagination)
}
func (o *ForbiddenAccount) FindAllIDs(ctx context.Context) ([]string, error) {
return mongoutil.Find[string](ctx, o.coll, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
}

View File

@@ -0,0 +1,99 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/constant"
admindb "git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewInvitationRegister(db *mongo.Database) (admindb.InvitationRegisterInterface, error) {
coll := db.Collection("invitation_register")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "invitation_code", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &InvitationRegister{
coll: coll,
}, nil
}
type InvitationRegister struct {
coll *mongo.Collection
}
func (o *InvitationRegister) Find(ctx context.Context, codes []string) ([]*admindb.InvitationRegister, error) {
return mongoutil.Find[*admindb.InvitationRegister](ctx, o.coll, bson.M{"invitation_code": bson.M{"$in": codes}})
}
func (o *InvitationRegister) Del(ctx context.Context, codes []string) error {
if len(codes) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"invitation_code": bson.M{"$in": codes}})
}
func (o *InvitationRegister) Create(ctx context.Context, v []*admindb.InvitationRegister) error {
return mongoutil.InsertMany(ctx, o.coll, v)
}
func (o *InvitationRegister) Take(ctx context.Context, code string) (*admindb.InvitationRegister, error) {
return mongoutil.FindOne[*admindb.InvitationRegister](ctx, o.coll, bson.M{"code": code})
}
func (o *InvitationRegister) Update(ctx context.Context, code string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"invitation_code": code}, bson.M{"$set": data}, false)
}
func (o *InvitationRegister) Search(ctx context.Context, keyword string, state int32, userIDs []string, codes []string, pagination pagination.Pagination) (int64, []*admindb.InvitationRegister, error) {
filter := bson.M{}
switch state {
case constant.InvitationCodeUsed:
filter = bson.M{"user_id": bson.M{"$ne": ""}}
case constant.InvitationCodeUnused:
filter = bson.M{"user_id": ""}
}
if len(userIDs) > 0 {
filter["user_id"] = bson.M{"$in": userIDs}
}
if len(codes) > 0 {
filter["invitation_code"] = bson.M{"$in": codes}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"invitation_code": bson.M{"$regex": keyword, "$options": "i"}},
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*admindb.InvitationRegister](ctx, o.coll, filter, pagination)
}

View File

@@ -0,0 +1,95 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/constant"
admindb "git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewIPForbidden(db *mongo.Database) (admindb.IPForbiddenInterface, error) {
coll := db.Collection("ip_forbidden")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "ip", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &IPForbidden{
coll: coll,
}, nil
}
type IPForbidden struct {
coll *mongo.Collection
}
func (o *IPForbidden) Take(ctx context.Context, ip string) (*admindb.IPForbidden, error) {
return mongoutil.FindOne[*admindb.IPForbidden](ctx, o.coll, bson.M{"ip": ip})
}
func (o *IPForbidden) Find(ctx context.Context, ips []string) ([]*admindb.IPForbidden, error) {
return mongoutil.Find[*admindb.IPForbidden](ctx, o.coll, bson.M{"ip": bson.M{"$in": ips}})
}
func (o *IPForbidden) Search(ctx context.Context, keyword string, state int32, pagination pagination.Pagination) (int64, []*admindb.IPForbidden, error) {
filter := bson.M{}
switch state {
case constant.LimitNil:
case constant.LimitEmpty:
filter = bson.M{"limit_register": 0, "limit_login": 0}
case constant.LimitOnlyRegisterIP:
filter = bson.M{"limit_register": 1, "limit_login": 0}
case constant.LimitOnlyLoginIP:
filter = bson.M{"limit_register": 0, "limit_login": 1}
case constant.LimitRegisterIP:
filter = bson.M{"limit_register": 1}
case constant.LimitLoginIP:
filter = bson.M{"limit_login": 1}
case constant.LimitLoginRegisterIP:
filter = bson.M{"limit_register": 1, "limit_login": 1}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"ip": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*admindb.IPForbidden](ctx, o.coll, filter, pagination)
}
func (o *IPForbidden) Create(ctx context.Context, ms []*admindb.IPForbidden) error {
return mongoutil.InsertMany(ctx, o.coll, ms)
}
func (o *IPForbidden) Delete(ctx context.Context, ips []string) error {
if len(ips) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"ip": bson.M{"$in": ips}})
}

View File

@@ -0,0 +1,93 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewLimitUserLoginIP(db *mongo.Database) (admin.LimitUserLoginIPInterface, error) {
coll := db.Collection("limit_user_login_ip")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "ip", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &LimitUserLoginIP{
coll: coll,
}, nil
}
type LimitUserLoginIP struct {
coll *mongo.Collection
}
func (o *LimitUserLoginIP) Create(ctx context.Context, ms []*admin.LimitUserLoginIP) error {
return mongoutil.InsertMany(ctx, o.coll, ms)
}
func (o *LimitUserLoginIP) Delete(ctx context.Context, ms []*admin.LimitUserLoginIP) error {
return mongoutil.DeleteMany(ctx, o.coll, o.limitUserLoginIPFilter(ms))
}
func (o *LimitUserLoginIP) Count(ctx context.Context, userID string) (uint32, error) {
count, err := mongoutil.Count(ctx, o.coll, bson.M{"user_id": userID})
if err != nil {
return 0, err
}
return uint32(count), nil
}
func (o *LimitUserLoginIP) Take(ctx context.Context, userID string, ip string) (*admin.LimitUserLoginIP, error) {
return mongoutil.FindOne[*admin.LimitUserLoginIP](ctx, o.coll, bson.M{"user_id": userID, "ip": ip})
}
func (o *LimitUserLoginIP) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admin.LimitUserLoginIP, error) {
filter := bson.M{
"$or": []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"ip": bson.M{"$regex": keyword, "$options": "i"}},
},
}
return mongoutil.FindPage[*admin.LimitUserLoginIP](ctx, o.coll, filter, pagination)
}
func (o *LimitUserLoginIP) limitUserLoginIPFilter(ips []*admin.LimitUserLoginIP) bson.M {
if len(ips) == 0 {
return nil
}
or := make(bson.A, 0, len(ips))
for _, ip := range ips {
or = append(or, bson.M{
"user_id": ip.UserID,
"ip": ip.IP,
})
}
return bson.M{"$or": or}
}

View File

@@ -0,0 +1,76 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewRegisterAddFriend(db *mongo.Database) (admin.RegisterAddFriendInterface, error) {
coll := db.Collection("register_add_friend")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &RegisterAddFriend{
coll: coll,
}, nil
}
type RegisterAddFriend struct {
coll *mongo.Collection
}
func (o *RegisterAddFriend) Add(ctx context.Context, registerAddFriends []*admin.RegisterAddFriend) error {
return mongoutil.InsertMany(ctx, o.coll, registerAddFriends)
}
func (o *RegisterAddFriend) Del(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *RegisterAddFriend) FindUserID(ctx context.Context, userIDs []string) ([]string, error) {
filter := bson.M{}
if len(userIDs) > 0 {
filter["user_id"] = bson.M{"$in": userIDs}
}
return mongoutil.Find[string](ctx, o.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
}
func (o *RegisterAddFriend) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admin.RegisterAddFriend, error) {
filter := bson.M{"user_id": bson.M{"$regex": keyword, "$options": "i"}}
return mongoutil.FindPage[*admin.RegisterAddFriend](ctx, o.coll, filter, pagination)
}
func (o *RegisterAddFriend) CountTotal(ctx context.Context) (int64, error) {
return mongoutil.Count(ctx, o.coll, bson.M{})
}

View File

@@ -0,0 +1,90 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"github.com/openimsdk/tools/errs"
)
func NewRegisterAddGroup(db *mongo.Database) (admin.RegisterAddGroupInterface, error) {
coll := db.Collection("register_add_group")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "group_id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &RegisterAddGroup{
coll: coll,
}, nil
}
type RegisterAddGroup struct {
coll *mongo.Collection
}
func (o *RegisterAddGroup) Add(ctx context.Context, registerAddGroups []*admin.RegisterAddGroup) error {
return mongoutil.InsertMany(ctx, o.coll, registerAddGroups)
}
func (o *RegisterAddGroup) Del(ctx context.Context, groupIDs []string) error {
if len(groupIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"group_id": bson.M{"$in": groupIDs}})
}
func (o *RegisterAddGroup) FindGroupID(ctx context.Context, groupIDs []string) ([]string, error) {
filter := bson.M{}
if len(groupIDs) > 0 {
filter["group_id"] = bson.M{"$in": groupIDs}
}
return mongoutil.Find[string](ctx, o.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1}))
}
func (o *RegisterAddGroup) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*admin.RegisterAddGroup, error) {
filter := bson.M{"group_id": bson.M{"$regex": keyword, "$options": "i"}}
return mongoutil.FindPage[*admin.RegisterAddGroup](ctx, o.coll, filter, pagination)
}
func (o *RegisterAddGroup) CountTotal(ctx context.Context) (int64, error) {
return mongoutil.Count(ctx, o.coll, bson.M{})
}
func (o *RegisterAddGroup) CountToday(ctx context.Context) (int64, error) {
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
todayEnd := todayStart.Add(24 * time.Hour)
filter := bson.M{
"create_time": bson.M{
"$gte": todayStart,
"$lt": todayEnd,
},
}
return mongoutil.Count(ctx, o.coll, filter)
}

View File

@@ -0,0 +1,65 @@
package bot
import (
"context"
"git.imall.cloud/openim/chat/pkg/common/db/table/bot"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewAgent(db *mongo.Database) (bot.AgentInterface, error) {
coll := db.Collection("agent")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Agent{coll: coll}, nil
}
type Agent struct {
coll *mongo.Collection
}
func (o *Agent) Create(ctx context.Context, elems ...*bot.Agent) error {
return mongoutil.InsertMany(ctx, o.coll, elems)
}
func (o *Agent) Take(ctx context.Context, userId string) (*bot.Agent, error) {
return mongoutil.FindOne[*bot.Agent](ctx, o.coll, bson.M{"user_id": userId})
}
func (o *Agent) Find(ctx context.Context, userIDs []string) ([]*bot.Agent, error) {
return mongoutil.Find[*bot.Agent](ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *Agent) Update(ctx context.Context, userID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": data}, false)
}
func (o *Agent) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *Agent) Page(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*bot.Agent, error) {
filter := bson.M{}
if len(userIDs) > 0 {
filter["user_id"] = bson.M{"$in": userIDs}
}
return mongoutil.FindPage[*bot.Agent](ctx, o.coll, filter, pagination)
}

View File

@@ -0,0 +1,50 @@
package bot
import (
"context"
"git.imall.cloud/openim/chat/pkg/common/db/table/bot"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewConversationRespID(db *mongo.Database) (bot.ConversationRespIDInterface, error) {
coll := db.Collection("conversation_resp_id")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "conversation_id", Value: 1},
{Key: "agent_id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &ConversationRespID{coll: coll}, nil
}
type ConversationRespID struct {
coll *mongo.Collection
}
func (o *ConversationRespID) Create(ctx context.Context, elems ...*bot.ConversationRespID) error {
return mongoutil.InsertMany(ctx, o.coll, elems)
}
func (o *ConversationRespID) Take(ctx context.Context, convID, agentID string) (*bot.ConversationRespID, error) {
return mongoutil.FindOne[*bot.ConversationRespID](ctx, o.coll, bson.M{"conversation_id": convID, "agent_id": agentID})
}
func (o *ConversationRespID) Update(ctx context.Context, convID, agentID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"conversation_id": convID, "agent_id": agentID}, bson.M{"$set": data}, false, options.Update().SetUpsert(true))
}
func (o *ConversationRespID) Delete(ctx context.Context, convID, agentID string) error {
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"conversation_id": convID, "agent_id": agentID})
}

View File

@@ -0,0 +1,72 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewAccount(db *mongo.Database) (chat.AccountInterface, error) {
coll := db.Collection("account")
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Account{coll: coll}, nil
}
type Account struct {
coll *mongo.Collection
}
func (o *Account) Create(ctx context.Context, accounts ...*chat.Account) error {
return mongoutil.InsertMany(ctx, o.coll, accounts)
}
func (o *Account) Take(ctx context.Context, userId string) (*chat.Account, error) {
return mongoutil.FindOne[*chat.Account](ctx, o.coll, bson.M{"user_id": userId})
}
func (o *Account) Update(ctx context.Context, userID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": data}, false)
}
func (o *Account) UpdatePassword(ctx context.Context, userId string, password string) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userId}, bson.M{"$set": bson.M{"password": password, "change_time": time.Now()}}, false)
}
func (o *Account) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}

View File

@@ -0,0 +1,247 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewAttribute(db *mongo.Database) (chat.AttributeInterface, error) {
coll := db.Collection("attribute")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "account", Value: 1},
},
},
{
Keys: bson.D{
{Key: "email", Value: 1},
},
},
{
Keys: bson.D{
{Key: "area_code", Value: 1},
{Key: "phone_number", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Attribute{coll: coll}, nil
}
type Attribute struct {
coll *mongo.Collection
}
func (o *Attribute) Create(ctx context.Context, attribute ...*chat.Attribute) error {
return mongoutil.InsertMany(ctx, o.coll, attribute)
}
func (o *Attribute) Update(ctx context.Context, userID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": data}, false)
}
func (o *Attribute) Find(ctx context.Context, userIds []string) ([]*chat.Attribute, error) {
return mongoutil.Find[*chat.Attribute](ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIds}})
}
func (o *Attribute) FindAccount(ctx context.Context, accounts []string) ([]*chat.Attribute, error) {
return mongoutil.Find[*chat.Attribute](ctx, o.coll, bson.M{"account": bson.M{"$in": accounts}})
}
func (o *Attribute) FindPhone(ctx context.Context, phoneNumbers []string) ([]*chat.Attribute, error) {
return mongoutil.Find[*chat.Attribute](ctx, o.coll, bson.M{"phone_number": bson.M{"$in": phoneNumbers}})
}
func (o *Attribute) Search(ctx context.Context, keyword string, genders []int32, pagination pagination.Pagination) (int64, []*chat.Attribute, error) {
filter := bson.M{}
if len(genders) > 0 {
filter["gender"] = bson.M{
"$in": genders,
}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"account": bson.M{"$regex": keyword, "$options": "i"}},
{"nickname": bson.M{"$regex": keyword, "$options": "i"}},
{"phone_number": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*chat.Attribute](ctx, o.coll, filter, pagination)
}
func (o *Attribute) TakePhone(ctx context.Context, areaCode string, phoneNumber string) (*chat.Attribute, error) {
return mongoutil.FindOne[*chat.Attribute](ctx, o.coll, bson.M{"area_code": areaCode, "phone_number": phoneNumber})
}
func (o *Attribute) TakeEmail(ctx context.Context, email string) (*chat.Attribute, error) {
return mongoutil.FindOne[*chat.Attribute](ctx, o.coll, bson.M{"email": email})
}
func (o *Attribute) TakeAccount(ctx context.Context, account string) (*chat.Attribute, error) {
return mongoutil.FindOne[*chat.Attribute](ctx, o.coll, bson.M{"account": account})
}
func (o *Attribute) Take(ctx context.Context, userID string) (*chat.Attribute, error) {
return mongoutil.FindOne[*chat.Attribute](ctx, o.coll, bson.M{"user_id": userID})
}
func (o *Attribute) SearchNormalUser(ctx context.Context, keyword string, forbiddenIDs []string, gender int32, startTime, endTime *time.Time, pagination pagination.Pagination) (int64, []*chat.Attribute, error) {
filter := bson.M{}
if gender == 0 {
filter["gender"] = bson.M{
"$in": []int32{0, 1, 2},
}
} else {
filter["gender"] = gender
}
if len(forbiddenIDs) > 0 {
filter["user_id"] = bson.M{
"$nin": forbiddenIDs,
}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"account": bson.M{"$regex": keyword, "$options": "i"}},
{"nickname": bson.M{"$regex": keyword, "$options": "i"}},
{"phone_number": bson.M{"$regex": keyword, "$options": "i"}},
{"email": bson.M{"$regex": keyword, "$options": "i"}},
}
}
// 注册时间范围查询
if startTime != nil || endTime != nil {
timeFilter := bson.M{}
if startTime != nil {
timeFilter["$gte"] = *startTime
}
if endTime != nil {
// 使用 $lt小于而不是 $lte确保不包含结束时间当天的数据
// 例如endTime="2025-11-02 00:00:00" 应该查询到 2025-11-01 23:59:59不包含 11月2日的数据
timeFilter["$lt"] = *endTime
}
if len(timeFilter) > 0 {
filter["create_time"] = timeFilter
}
}
return mongoutil.FindPage[*chat.Attribute](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
// SearchNormalUserWithUserIDs 按条件搜索用户支持额外的userIDs过滤
func (o *Attribute) SearchNormalUserWithUserIDs(ctx context.Context, keyword string, forbiddenIDs []string, gender int32, startTime, endTime *time.Time, userIDs []string, pagination pagination.Pagination) (int64, []*chat.Attribute, error) {
filter := bson.M{}
if gender == 0 {
filter["gender"] = bson.M{
"$in": []int32{0, 1, 2},
}
} else {
filter["gender"] = gender
}
// 构建user_id过滤条件需要同时满足在userIDs中且不在forbiddenIDs中
userIDConditions := []bson.M{}
if len(userIDs) > 0 {
userIDConditions = append(userIDConditions, bson.M{"user_id": bson.M{"$in": userIDs}})
}
if len(forbiddenIDs) > 0 {
userIDConditions = append(userIDConditions, bson.M{"user_id": bson.M{"$nin": forbiddenIDs}})
}
if len(userIDConditions) > 0 {
if len(userIDConditions) == 1 {
filter["user_id"] = userIDConditions[0]["user_id"]
} else {
// 需要同时满足多个条件,使用 $and
filter["$and"] = userIDConditions
}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"account": bson.M{"$regex": keyword, "$options": "i"}},
{"nickname": bson.M{"$regex": keyword, "$options": "i"}},
{"phone_number": bson.M{"$regex": keyword, "$options": "i"}},
{"email": bson.M{"$regex": keyword, "$options": "i"}},
}
}
// 注册时间范围查询
if startTime != nil || endTime != nil {
timeFilter := bson.M{}
if startTime != nil {
timeFilter["$gte"] = *startTime
}
if endTime != nil {
timeFilter["$lt"] = *endTime
}
if len(timeFilter) > 0 {
filter["create_time"] = timeFilter
}
}
return mongoutil.FindPage[*chat.Attribute](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *Attribute) SearchUser(ctx context.Context, keyword string, userIDs []string, genders []int32, pagination pagination.Pagination) (int64, []*chat.Attribute, error) {
filter := bson.M{}
if len(genders) > 0 {
filter["gender"] = bson.M{
"$in": genders,
}
}
if len(userIDs) > 0 {
filter["user_id"] = bson.M{
"$in": userIDs,
}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"account": bson.M{"$regex": keyword, "$options": "i"}},
{"nickname": bson.M{"$regex": keyword, "$options": "i"}},
{"phone_number": bson.M{"$regex": keyword, "$options": "i"}},
{"email": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*chat.Attribute](ctx, o.coll, filter, pagination)
}
func (o *Attribute) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}

View File

@@ -0,0 +1,145 @@
package chat
import (
"context"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewCredential(db *mongo.Database) (chat.CredentialInterface, error) {
coll := db.Collection("credential")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "type", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "account", Value: 1},
},
Options: options.Index().SetUnique(true),
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Credential{coll: coll}, nil
}
type Credential struct {
coll *mongo.Collection
}
func (o *Credential) Create(ctx context.Context, credential ...*chat.Credential) error {
return mongoutil.InsertMany(ctx, o.coll, credential)
}
func (o *Credential) CreateOrUpdateAccount(ctx context.Context, credential *chat.Credential) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{
"user_id": credential.UserID,
"type": credential.Type,
}, bson.M{
"$set": bson.M{
"account": credential.Account,
},
"$setOnInsert": bson.M{
"user_id": credential.UserID,
"type": credential.Type,
"allow_change": credential.AllowChange,
},
}, false, options.Update().SetUpsert(true))
}
func (o *Credential) Update(ctx context.Context, userID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": data}, false)
}
func (o *Credential) Find(ctx context.Context, userID string) ([]*chat.Credential, error) {
return mongoutil.Find[*chat.Credential](ctx, o.coll, bson.M{"user_id": userID})
}
func (o *Credential) FindAccount(ctx context.Context, accounts []string) ([]*chat.Credential, error) {
return mongoutil.Find[*chat.Credential](ctx, o.coll, bson.M{"account": bson.M{"$in": accounts}})
}
func (o *Credential) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*chat.Credential, error) {
return o.SearchUser(ctx, keyword, nil, pagination)
}
func (o *Credential) TakeAccount(ctx context.Context, account string) (*chat.Credential, error) {
return mongoutil.FindOne[*chat.Credential](ctx, o.coll, bson.M{"account": account})
}
func (o *Credential) Take(ctx context.Context, userID string) (*chat.Credential, error) {
return mongoutil.FindOne[*chat.Credential](ctx, o.coll, bson.M{"user_id": userID})
}
func (o *Credential) SearchNormalUser(ctx context.Context, keyword string, forbiddenIDs []string, pagination pagination.Pagination) (int64, []*chat.Credential, error) {
filter := bson.M{}
if len(forbiddenIDs) > 0 {
filter["user_id"] = bson.M{
"$nin": forbiddenIDs,
}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"account": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*chat.Credential](ctx, o.coll, filter, pagination)
}
func (o *Credential) SearchUser(ctx context.Context, keyword string, userIDs []string, pagination pagination.Pagination) (int64, []*chat.Credential, error) {
filter := bson.M{}
if len(userIDs) > 0 {
filter["user_id"] = bson.M{
"$in": userIDs,
}
}
if keyword != "" {
filter["$or"] = []bson.M{
{"user_id": bson.M{"$regex": keyword, "$options": "i"}},
{"account": bson.M{"$regex": keyword, "$options": "i"}},
}
}
return mongoutil.FindPage[*chat.Credential](ctx, o.coll, filter, pagination)
}
func (o *Credential) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *Credential) DeleteByUserIDType(ctx context.Context, credentials ...*chat.Credential) error {
if len(credentials) == 0 {
return nil
}
var filters []bson.M
for _, credential := range credentials {
filters = append(filters, bson.M{
"user_id": credential.UserID,
"type": credential.Type,
})
}
query := bson.M{"$or": filters}
return mongoutil.DeleteMany(ctx, o.coll, query)
}

View File

@@ -0,0 +1,151 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewFavorite(db *mongo.Database) (chat.FavoriteInterface, error) {
coll := db.Collection("favorite")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "type", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "status", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Favorite{coll: coll}, nil
}
type Favorite struct {
coll *mongo.Collection
}
func (o *Favorite) Create(ctx context.Context, favorites ...*chat.Favorite) error {
for _, favorite := range favorites {
if favorite.ID == "" {
favorite.ID = primitive.NewObjectID().Hex()
}
if favorite.CreateTime.IsZero() {
favorite.CreateTime = time.Now()
}
if favorite.UpdateTime.IsZero() {
favorite.UpdateTime = time.Now()
}
if favorite.Status == 0 {
favorite.Status = 1 // 默认为正常状态
}
}
return mongoutil.InsertMany(ctx, o.coll, favorites)
}
func (o *Favorite) Take(ctx context.Context, favoriteID string) (*chat.Favorite, error) {
return mongoutil.FindOne[*chat.Favorite](ctx, o.coll, bson.M{"_id": favoriteID, "status": 1})
}
func (o *Favorite) FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chat.Favorite, error) {
filter := bson.M{
"user_id": userID,
"status": 1,
}
return mongoutil.FindPage[*chat.Favorite](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *Favorite) FindByUserIDAndType(ctx context.Context, userID string, favoriteType int32, pagination pagination.Pagination) (int64, []*chat.Favorite, error) {
filter := bson.M{
"user_id": userID,
"type": favoriteType,
"status": 1,
}
return mongoutil.FindPage[*chat.Favorite](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *Favorite) SearchByKeyword(ctx context.Context, userID string, keyword string, pagination pagination.Pagination) (int64, []*chat.Favorite, error) {
filter := bson.M{
"user_id": userID,
"status": 1,
"$or": []bson.M{
{"title": bson.M{"$regex": keyword, "$options": "i"}},
{"content": bson.M{"$regex": keyword, "$options": "i"}},
{"description": bson.M{"$regex": keyword, "$options": "i"}},
},
}
return mongoutil.FindPage[*chat.Favorite](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *Favorite) Update(ctx context.Context, favoriteID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
data["update_time"] = time.Now()
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": favoriteID}, bson.M{"$set": data}, false)
}
func (o *Favorite) Delete(ctx context.Context, favoriteIDs []string) error {
if len(favoriteIDs) == 0 {
return nil
}
// 软删除将状态设置为0
_, err := o.coll.UpdateMany(ctx, bson.M{"_id": bson.M{"$in": favoriteIDs}}, bson.M{"$set": bson.M{"status": 0, "update_time": time.Now()}})
return errs.Wrap(err)
}
func (o *Favorite) DeleteByUserID(ctx context.Context, userID string) error {
// 软删除将状态设置为0
_, err := o.coll.UpdateMany(ctx, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"status": 0, "update_time": time.Now()}})
return errs.Wrap(err)
}
func (o *Favorite) CountByUserID(ctx context.Context, userID string) (int64, error) {
return mongoutil.Count(ctx, o.coll, bson.M{"user_id": userID, "status": 1})
}
func (o *Favorite) FindByTags(ctx context.Context, userID string, tags []string, pagination pagination.Pagination) (int64, []*chat.Favorite, error) {
filter := bson.M{
"user_id": userID,
"status": 1,
"tags": bson.M{"$in": tags},
}
return mongoutil.FindPage[*chat.Favorite](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}

View File

@@ -0,0 +1,81 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
"github.com/openimsdk/tools/errs"
)
func NewRegister(db *mongo.Database) (chat.RegisterInterface, error) {
coll := db.Collection("register")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
},
Options: options.Index().SetUnique(true),
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Register{coll: coll}, nil
}
type Register struct {
coll *mongo.Collection
}
func (o *Register) Create(ctx context.Context, registers ...*chat.Register) error {
return mongoutil.InsertMany(ctx, o.coll, registers)
}
func (o *Register) CountTotal(ctx context.Context, before *time.Time) (int64, error) {
filter := bson.M{}
if before != nil {
filter["create_time"] = bson.M{"$lt": before}
}
return mongoutil.Count(ctx, o.coll, filter)
}
func (o *Register) CountToday(ctx context.Context) (int64, error) {
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
endOfDay := startOfDay.Add(24 * time.Hour)
filter := bson.M{
"create_time": bson.M{
"$gte": startOfDay,
"$lt": endOfDay,
},
}
return mongoutil.Count(ctx, o.coll, filter)
}
func (o *Register) Delete(ctx context.Context, userIDs []string) error {
if len(userIDs) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}

View File

@@ -0,0 +1,106 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewScheduledTask(db *mongo.Database) (chat.ScheduledTaskInterface, error) {
coll := db.Collection("scheduled_tasks")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "status", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &ScheduledTask{coll: coll}, nil
}
type ScheduledTask struct {
coll *mongo.Collection
}
func (o *ScheduledTask) Create(ctx context.Context, tasks ...*chat.ScheduledTask) error {
for _, task := range tasks {
if task.ID == "" {
task.ID = primitive.NewObjectID().Hex()
}
if task.CreateTime.IsZero() {
task.CreateTime = time.Now()
}
if task.UpdateTime.IsZero() {
task.UpdateTime = time.Now()
}
if task.Status == 0 {
task.Status = 1 // 默认为启用状态
}
}
return mongoutil.InsertMany(ctx, o.coll, tasks)
}
func (o *ScheduledTask) Take(ctx context.Context, taskID string) (*chat.ScheduledTask, error) {
return mongoutil.FindOne[*chat.ScheduledTask](ctx, o.coll, bson.M{"_id": taskID})
}
func (o *ScheduledTask) FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chat.ScheduledTask, error) {
filter := bson.M{
"user_id": userID,
}
return mongoutil.FindPage[*chat.ScheduledTask](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *ScheduledTask) FindAll(ctx context.Context, pagination pagination.Pagination) (int64, []*chat.ScheduledTask, error) {
filter := bson.M{}
return mongoutil.FindPage[*chat.ScheduledTask](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *ScheduledTask) Update(ctx context.Context, taskID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
data["update_time"] = time.Now()
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": taskID}, bson.M{"$set": data}, false)
}
func (o *ScheduledTask) Delete(ctx context.Context, taskIDs []string) error {
if len(taskIDs) == 0 {
return nil
}
_, err := o.coll.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": taskIDs}})
return errs.Wrap(err)
}

View File

@@ -0,0 +1,422 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"fmt"
"strings"
"time"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewSensitiveWord(db *mongo.Database) (chat.SensitiveWordInterface, error) {
coll := db.Collection("sensitive_words")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "word", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "status", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
// 创建配置集合
configColl := db.Collection("sensitive_word_configs")
_, err = configColl.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{
{Key: "enable_filter", Value: 1},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
// 检查并初始化默认配置
ctx := context.Background()
count, err := configColl.CountDocuments(ctx, bson.M{})
if err != nil {
return nil, errs.Wrap(err)
}
if count == 0 {
// 创建默认配置
defaultConfig := &chat.SensitiveWordConfig{
ID: "default",
EnableFilter: true,
FilterMode: 1,
ReplaceChar: "***",
WhitelistUsers: []string{},
WhitelistGroups: []string{},
LogEnabled: true,
AutoApprove: false,
UpdateTime: time.Now(),
}
_, err = configColl.InsertOne(ctx, defaultConfig)
if err != nil {
return nil, errs.Wrap(err)
}
}
return &SensitiveWord{coll: coll, configColl: configColl}, nil
}
type SensitiveWord struct {
coll *mongo.Collection
configColl *mongo.Collection
}
// ==================== 敏感词管理 ====================
func (o *SensitiveWord) CreateSensitiveWord(ctx context.Context, word *chat.SensitiveWord) error {
word.CreateTime = time.Now()
word.UpdateTime = time.Now()
return mongoutil.InsertMany(ctx, o.coll, []*chat.SensitiveWord{word})
}
func (o *SensitiveWord) UpdateSensitiveWord(ctx context.Context, id string, data map[string]any) error {
data["update_time"] = time.Now()
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": id}, bson.M{"$set": data}, false)
}
func (o *SensitiveWord) DeleteSensitiveWord(ctx context.Context, ids []string) error {
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"_id": bson.M{"$in": ids}})
}
func (o *SensitiveWord) GetSensitiveWord(ctx context.Context, id string) (*chat.SensitiveWord, error) {
return mongoutil.FindOne[*chat.SensitiveWord](ctx, o.coll, bson.M{"_id": id})
}
func (o *SensitiveWord) SearchSensitiveWords(ctx context.Context, keyword string, action int32, status int32, pagination pagination.Pagination) (int64, []*chat.SensitiveWord, error) {
filter := bson.M{}
if keyword != "" {
filter["word"] = bson.M{"$regex": keyword, "$options": "i"}
}
if action > 0 {
filter["action"] = action
}
if status > 0 {
filter["status"] = status
}
return mongoutil.FindPage[*chat.SensitiveWord](ctx, o.coll, filter, pagination)
}
func (o *SensitiveWord) GetAllSensitiveWords(ctx context.Context) ([]*chat.SensitiveWord, error) {
return mongoutil.Find[*chat.SensitiveWord](ctx, o.coll, bson.M{})
}
func (o *SensitiveWord) GetEnabledSensitiveWords(ctx context.Context) ([]*chat.SensitiveWord, error) {
return mongoutil.Find[*chat.SensitiveWord](ctx, o.coll, bson.M{"status": chat.SensitiveStatusEnabled})
}
// ==================== 敏感词检测和过滤 ====================
func (o *SensitiveWord) CheckSensitiveWords(ctx context.Context, content string) ([]*chat.SensitiveWord, error) {
words, err := o.GetEnabledSensitiveWords(ctx)
if err != nil {
return nil, err
}
var matchedWords []*chat.SensitiveWord
contentLower := strings.ToLower(content)
for _, word := range words {
if strings.Contains(contentLower, strings.ToLower(word.Word)) {
matchedWords = append(matchedWords, word)
}
}
return matchedWords, nil
}
func (o *SensitiveWord) FilterContent(ctx context.Context, content string) (string, []*chat.SensitiveWord, error) {
words, err := o.CheckSensitiveWords(ctx, content)
if err != nil {
return content, nil, err
}
if len(words) == 0 {
return content, words, nil
}
filteredContent := content
for _, word := range words {
replaceChar := "***"
filteredContent = strings.ReplaceAll(filteredContent, word.Word, replaceChar)
}
return filteredContent, words, nil
}
// ==================== 敏感词日志管理 ====================
func (o *SensitiveWord) CreateSensitiveWordLog(ctx context.Context, log *chat.SensitiveWordLog) error {
log.CreateTime = time.Now()
return mongoutil.InsertMany(ctx, o.coll.Database().Collection("sensitive_word_logs"), []*chat.SensitiveWordLog{log})
}
func (o *SensitiveWord) GetSensitiveWordLogs(ctx context.Context, userID, groupID string, pagination pagination.Pagination) (int64, []*chat.SensitiveWordLog, error) {
filter := bson.M{}
if userID != "" {
filter["user_id"] = userID
}
if groupID != "" {
filter["group_id"] = groupID
}
coll := o.coll.Database().Collection("sensitive_word_logs")
return mongoutil.FindPage[*chat.SensitiveWordLog](ctx, coll, filter, pagination)
}
func (o *SensitiveWord) DeleteSensitiveWordLogs(ctx context.Context, ids []string) error {
coll := o.coll.Database().Collection("sensitive_word_logs")
return mongoutil.DeleteMany(ctx, coll, bson.M{"_id": bson.M{"$in": ids}})
}
// ==================== 敏感词分组管理 ====================
func (o *SensitiveWord) CreateSensitiveWordGroup(ctx context.Context, group *chat.SensitiveWordGroup) error {
group.CreateTime = time.Now()
group.UpdateTime = time.Now()
return mongoutil.InsertMany(ctx, o.coll.Database().Collection("sensitive_word_groups"), []*chat.SensitiveWordGroup{group})
}
func (o *SensitiveWord) UpdateSensitiveWordGroup(ctx context.Context, id string, data map[string]any) error {
data["update_time"] = time.Now()
coll := o.coll.Database().Collection("sensitive_word_groups")
return mongoutil.UpdateOne(ctx, coll, bson.M{"_id": id}, bson.M{"$set": data}, false)
}
func (o *SensitiveWord) DeleteSensitiveWordGroup(ctx context.Context, ids []string) error {
coll := o.coll.Database().Collection("sensitive_word_groups")
return mongoutil.DeleteMany(ctx, coll, bson.M{"_id": bson.M{"$in": ids}})
}
func (o *SensitiveWord) GetSensitiveWordGroup(ctx context.Context, id string) (*chat.SensitiveWordGroup, error) {
coll := o.coll.Database().Collection("sensitive_word_groups")
return mongoutil.FindOne[*chat.SensitiveWordGroup](ctx, coll, bson.M{"_id": id})
}
func (o *SensitiveWord) GetAllSensitiveWordGroups(ctx context.Context) ([]*chat.SensitiveWordGroup, error) {
coll := o.coll.Database().Collection("sensitive_word_groups")
return mongoutil.Find[*chat.SensitiveWordGroup](ctx, coll, bson.M{})
}
// ==================== 敏感词配置管理 ====================
func (o *SensitiveWord) GetSensitiveWordConfig(ctx context.Context) (*chat.SensitiveWordConfig, error) {
// 查找配置(默认配置已在初始化时创建)
config, err := mongoutil.FindOne[*chat.SensitiveWordConfig](ctx, o.configColl, bson.M{})
if err != nil {
return nil, err
}
return config, nil
}
func (o *SensitiveWord) UpdateSensitiveWordConfig(ctx context.Context, config *chat.SensitiveWordConfig) error {
config.UpdateTime = time.Now()
if config.ID == "" {
config.ID = "default"
}
filter := bson.M{"_id": config.ID}
fmt.Println("UpdateSensitiveWordConfig", "_________55", config, o.configColl.Name())
err := mongoutil.UpdateOne(ctx, o.configColl, filter, bson.M{"$set": config}, true)
fmt.Println("UpdateSensitiveWordConfig", "_________44", config)
if err != nil {
return err
}
return nil
}
func (o *SensitiveWord) IsFilterEnabled(ctx context.Context) (bool, error) {
config, err := o.GetSensitiveWordConfig(ctx)
if err != nil {
return false, err
}
return config.EnableFilter, nil
}
func (o *SensitiveWord) GetFilterMode(ctx context.Context) (int32, error) {
// 简化实现,返回默认值
return 1, nil
}
func (o *SensitiveWord) GetReplaceChar(ctx context.Context) (string, error) {
config, err := o.GetSensitiveWordConfig(ctx)
if err != nil {
return "***", err
}
if config.ReplaceChar == "" {
return "***", nil
}
return config.ReplaceChar, nil
}
func (o *SensitiveWord) IsUserInWhitelist(ctx context.Context, userID string) (bool, error) {
config, err := o.GetSensitiveWordConfig(ctx)
if err != nil {
return false, err
}
for _, id := range config.WhitelistUsers {
if id == userID {
return true, nil
}
}
return false, nil
}
func (o *SensitiveWord) IsGroupInWhitelist(ctx context.Context, groupID string) (bool, error) {
config, err := o.GetSensitiveWordConfig(ctx)
if err != nil {
return false, err
}
for _, id := range config.WhitelistGroups {
if id == groupID {
return true, nil
}
}
return false, nil
}
// ==================== 批量操作 ====================
func (o *SensitiveWord) BatchCreateSensitiveWords(ctx context.Context, words []*chat.SensitiveWord) error {
now := time.Now()
for _, word := range words {
word.CreateTime = now
word.UpdateTime = now
}
return mongoutil.InsertMany(ctx, o.coll, words)
}
func (o *SensitiveWord) BatchUpdateSensitiveWords(ctx context.Context, updates map[string]map[string]any) error {
for id, data := range updates {
data["update_time"] = time.Now()
err := mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": id}, bson.M{"$set": data}, false)
if err != nil {
return err
}
}
return nil
}
func (o *SensitiveWord) BatchDeleteSensitiveWords(ctx context.Context, ids []string) error {
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"_id": bson.M{"$in": ids}})
}
// ==================== 统计信息 ====================
func (o *SensitiveWord) GetSensitiveWordStats(ctx context.Context) (map[string]int64, error) {
stats := make(map[string]int64)
// 总数
total, err := mongoutil.Count(ctx, o.coll, bson.M{})
if err != nil {
return nil, err
}
stats["total"] = total
// 启用数量
enabled, err := mongoutil.Count(ctx, o.coll, bson.M{"status": chat.SensitiveStatusEnabled})
if err != nil {
return nil, err
}
stats["enabled"] = enabled
// 禁用数量
disabled, err := mongoutil.Count(ctx, o.coll, bson.M{"status": chat.SensitiveStatusDisabled})
if err != nil {
return nil, err
}
stats["disabled"] = disabled
// 替换模式数量
replace, err := mongoutil.Count(ctx, o.coll, bson.M{"action": chat.SensitiveActionReplace})
if err != nil {
return nil, err
}
stats["replace"] = replace
// 拦截模式数量
block, err := mongoutil.Count(ctx, o.coll, bson.M{"action": chat.SensitiveActionBlock})
if err != nil {
return nil, err
}
stats["block"] = block
return stats, nil
}
func (o *SensitiveWord) GetSensitiveWordLogStats(ctx context.Context, startTime, endTime time.Time) (map[string]int64, error) {
stats := make(map[string]int64)
coll := o.coll.Database().Collection("sensitive_word_logs")
filter := bson.M{
"create_time": bson.M{
"$gte": startTime,
"$lte": endTime,
},
}
// 总数
total, err := mongoutil.Count(ctx, coll, filter)
if err != nil {
return nil, err
}
stats["total"] = total
// 替换数量
replaceFilter := bson.M{}
for k, v := range filter {
replaceFilter[k] = v
}
replaceFilter["action"] = chat.SensitiveActionReplace
replace, err := mongoutil.Count(ctx, coll, replaceFilter)
if err != nil {
return nil, err
}
stats["replace"] = replace
// 拦截数量
blockFilter := bson.M{}
for k, v := range filter {
blockFilter[k] = v
}
blockFilter["action"] = chat.SensitiveActionBlock
block, err := mongoutil.Count(ctx, coll, blockFilter)
if err != nil {
return nil, err
}
stats["block"] = block
return stats, nil
}

View File

@@ -0,0 +1,121 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewSystemConfig(db *mongo.Database) (chat.SystemConfigInterface, error) {
coll := db.Collection("system_configs")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "key", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "enabled", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &SystemConfig{coll: coll}, nil
}
type SystemConfig struct {
coll *mongo.Collection
}
func (o *SystemConfig) Create(ctx context.Context, configs ...*chat.SystemConfig) error {
for _, config := range configs {
if config.CreateTime.IsZero() {
config.CreateTime = time.Now()
}
if config.UpdateTime.IsZero() {
config.UpdateTime = time.Now()
}
}
return mongoutil.InsertMany(ctx, o.coll, configs)
}
func (o *SystemConfig) Take(ctx context.Context, key string) (*chat.SystemConfig, error) {
return mongoutil.FindOne[*chat.SystemConfig](ctx, o.coll, bson.M{"key": key})
}
func (o *SystemConfig) FindByKeys(ctx context.Context, keys []string) ([]*chat.SystemConfig, error) {
if len(keys) == 0 {
return []*chat.SystemConfig{}, nil
}
return mongoutil.Find[*chat.SystemConfig](ctx, o.coll, bson.M{"key": bson.M{"$in": keys}}, options.Find().SetSort(bson.D{{Key: "key", Value: 1}}))
}
func (o *SystemConfig) FindAll(ctx context.Context, pagination pagination.Pagination) (int64, []*chat.SystemConfig, error) {
filter := bson.M{}
return mongoutil.FindPage[*chat.SystemConfig](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "key", Value: 1}}))
}
func (o *SystemConfig) Update(ctx context.Context, key string, data map[string]any) error {
if len(data) == 0 {
return nil
}
data["update_time"] = time.Now()
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"key": key}, bson.M{"$set": data}, false)
}
func (o *SystemConfig) UpdateValue(ctx context.Context, key string, value string) error {
return o.Update(ctx, key, map[string]any{"value": value})
}
func (o *SystemConfig) UpdateEnabled(ctx context.Context, key string, enabled bool) error {
return o.Update(ctx, key, map[string]any{"enabled": enabled})
}
func (o *SystemConfig) Delete(ctx context.Context, keys []string) error {
if len(keys) == 0 {
return nil
}
_, err := o.coll.DeleteMany(ctx, bson.M{"key": bson.M{"$in": keys}})
return errs.Wrap(err)
}
func (o *SystemConfig) GetEnabledConfigs(ctx context.Context) ([]*chat.SystemConfig, error) {
filter := bson.M{
"enabled": true,
}
return mongoutil.Find[*chat.SystemConfig](ctx, o.coll, filter, options.Find().SetSort(bson.D{{Key: "key", Value: 1}}))
}
func (o *SystemConfig) GetAppConfigs(ctx context.Context) ([]*chat.SystemConfig, error) {
filter := bson.M{
"show_in_app": true,
"enabled": true, // 同时要求 enabled=true
}
return mongoutil.Find[*chat.SystemConfig](ctx, o.coll, filter, options.Find().SetSort(bson.D{{Key: "key", Value: 1}}))
}

View File

@@ -0,0 +1,198 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"errors"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewUserLoginRecord(db *mongo.Database) (chat.UserLoginRecordInterface, error) {
coll := db.Collection("user_login_record")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
// 用于 CountTotal 查询:根据 create_time 范围查询
{
Keys: bson.D{
{Key: "create_time", Value: 1},
},
},
// 用于 CountTodayActiveUsers 和 CountRangeEverydayTotal根据 login_time 范围查询
{
Keys: bson.D{
{Key: "login_time", Value: 1},
},
},
// 用于 GetLatestLoginIP根据 user_id 查询,按 login_time 降序排序
// 同时优化聚合查询中的 user_id 分组操作
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "login_time", Value: -1},
},
},
})
if err != nil {
return nil, err
}
return &UserLoginRecord{
coll: coll,
}, nil
}
type UserLoginRecord struct {
coll *mongo.Collection
}
func (o *UserLoginRecord) Create(ctx context.Context, records ...*chat.UserLoginRecord) error {
return mongoutil.InsertMany(ctx, o.coll, records)
}
func (o *UserLoginRecord) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
filter := bson.M{}
if before != nil {
filter["create_time"] = bson.M{"$lt": before}
}
return mongoutil.Count(ctx, o.coll, filter)
}
func (o *UserLoginRecord) CountTodayActiveUsers(ctx context.Context) (int64, error) {
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
endOfDay := startOfDay.Add(24 * time.Hour)
filter := bson.M{
"login_time": bson.M{
"$gte": startOfDay,
"$lt": endOfDay,
},
}
// 使用聚合管道统计不同的用户数
pipeline := []bson.M{
{"$match": filter},
{"$group": bson.M{
"_id": "$user_id",
}},
{"$count": "count"},
}
type Result struct {
Count int64 `bson:"count"`
}
results, err := mongoutil.Aggregate[Result](ctx, o.coll, pipeline)
if err != nil {
return 0, err
}
if len(results) == 0 {
return 0, nil
}
return results[0].Count, nil
}
func (o *UserLoginRecord) CountRangeEverydayTotal(ctx context.Context, start *time.Time, end *time.Time) (map[string]int64, int64, error) {
pipeline := make([]bson.M, 0, 4)
if start != nil || end != nil {
filter := bson.M{}
if start != nil {
filter["$gte"] = start
}
if end != nil {
filter["$lt"] = end
}
pipeline = append(pipeline, bson.M{"$match": bson.M{"login_time": filter}})
}
pipeline = append(pipeline,
bson.M{
"$project": bson.M{
"_id": 0,
"user_id": 1,
"login_time": bson.M{
"$dateToString": bson.M{
"format": "%Y-%m-%d",
"date": "$login_time",
},
},
},
},
bson.M{
"$group": bson.M{
"_id": bson.M{
"user_id": "$user_id",
"login_time": "$login_time",
},
},
},
bson.M{
"$group": bson.M{
"_id": "$_id.login_time",
"count": bson.M{
"$sum": 1,
},
},
},
)
type Temp struct {
ID string `bson:"_id"`
Count int64 `bson:"count"`
}
res, err := mongoutil.Aggregate[Temp](ctx, o.coll, pipeline)
if err != nil {
return nil, 0, err
}
var loginCount int64
countMap := make(map[string]int64, len(res))
for _, r := range res {
loginCount += r.Count
countMap[r.ID] = r.Count
}
return countMap, loginCount, nil
}
func (o *UserLoginRecord) GetLatestLoginIP(ctx context.Context, userID string) (string, error) {
filter := bson.M{"user_id": userID}
opts := options.FindOne().SetSort(bson.D{{Key: "login_time", Value: -1}})
record, err := mongoutil.FindOne[chat.UserLoginRecord](ctx, o.coll, filter, opts)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return "", nil // 用户没有登录记录,返回空字符串
}
return "", err
}
return record.IP, nil
}
func (o *UserLoginRecord) Search(ctx context.Context, userID, ip string, pagination pagination.Pagination) (int64, []*chat.UserLoginRecord, error) {
filter := bson.M{}
if userID != "" {
filter["user_id"] = userID
}
if ip != "" {
filter["ip"] = ip
}
return mongoutil.FindPage[*chat.UserLoginRecord](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "login_time", Value: -1}}))
}

View File

@@ -0,0 +1,146 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
"github.com/openimsdk/tools/errs"
)
type mongoVerifyCode struct {
ID primitive.ObjectID `bson:"_id"`
Account string `bson:"account"`
Platform string `bson:"platform"`
Code string `bson:"code"`
Duration uint `bson:"duration"`
Count int `bson:"count"`
Used bool `bson:"used"`
CreateTime time.Time `bson:"create_time"`
}
func NewVerifyCode(db *mongo.Database) (chat.VerifyCodeInterface, error) {
coll := db.Collection("verify_code")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "account", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &VerifyCode{
coll: coll,
}, nil
}
type VerifyCode struct {
coll *mongo.Collection
}
func (o *VerifyCode) parseID(s string) (primitive.ObjectID, error) {
objID, err := primitive.ObjectIDFromHex(s)
if err != nil {
var zero primitive.ObjectID
return zero, errs.Wrap(err)
}
return objID, nil
}
func (o *VerifyCode) Add(ctx context.Context, ms []*chat.VerifyCode) error {
tmp := make([]mongoVerifyCode, 0, len(ms))
for i, m := range ms {
var objID primitive.ObjectID
if m.ID == "" {
objID = primitive.NewObjectID()
ms[i].ID = objID.Hex()
} else {
var err error
objID, err = o.parseID(m.ID)
if err != nil {
return err
}
}
tmp = append(tmp, mongoVerifyCode{
ID: objID,
Account: m.Account,
Platform: m.Platform,
Code: m.Code,
Duration: m.Duration,
Count: m.Count,
Used: m.Used,
CreateTime: m.CreateTime,
})
}
return mongoutil.InsertMany(ctx, o.coll, tmp)
}
func (o *VerifyCode) RangeNum(ctx context.Context, account string, start time.Time, end time.Time) (int64, error) {
filter := bson.M{
"account": account,
"create_time": bson.M{
"$gte": start,
"$lte": end,
},
}
return mongoutil.Count(ctx, o.coll, filter)
}
func (o *VerifyCode) TakeLast(ctx context.Context, account string) (*chat.VerifyCode, error) {
filter := bson.M{
"account": account,
}
opt := options.FindOne().SetSort(bson.M{"_id": -1})
last, err := mongoutil.FindOne[*mongoVerifyCode](ctx, o.coll, filter, opt)
if err != nil {
return nil, err
}
return &chat.VerifyCode{
ID: last.ID.Hex(),
Account: last.Account,
Platform: last.Platform,
Code: last.Code,
Duration: last.Duration,
Count: last.Count,
Used: last.Used,
CreateTime: last.CreateTime,
}, nil
}
func (o *VerifyCode) Incr(ctx context.Context, id string) error {
objID, err := o.parseID(id)
if err != nil {
return err
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": objID}, bson.M{"$inc": bson.M{"count": 1}}, false)
}
func (o *VerifyCode) Delete(ctx context.Context, id string) error {
objID, err := o.parseID(id)
if err != nil {
return err
}
return mongoutil.DeleteOne(ctx, o.coll, bson.M{"_id": objID})
}

View File

@@ -0,0 +1,217 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"fmt"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewWallet(db *mongo.Database) (chatdb.WalletInterface, error) {
coll := db.Collection("wallets")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{{Key: "user_id", Value: 1}},
Options: options.Index().SetUnique(true),
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Wallet{coll: coll}, nil
}
type Wallet struct {
coll *mongo.Collection
}
func (o *Wallet) Create(ctx context.Context, wallets ...*chatdb.Wallet) error {
return mongoutil.InsertMany(ctx, o.coll, wallets)
}
func (o *Wallet) Take(ctx context.Context, userID string) (*chatdb.Wallet, error) {
return mongoutil.FindOne[*chatdb.Wallet](ctx, o.coll, bson.M{"user_id": userID})
}
func (o *Wallet) Find(ctx context.Context, userIDs []string) ([]*chatdb.Wallet, error) {
return mongoutil.Find[*chatdb.Wallet](ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *Wallet) Update(ctx context.Context, userID string, data map[string]any) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": data}, true)
}
func (o *Wallet) UpdateBalance(ctx context.Context, userID string, balance int64) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"balance": balance}}, true)
}
// IncrementBalance 原子更新余额,使用 $inc 操作符防止并发问题
// 返回更新前后的余额
// 如果 amount 是负数(扣款),会检查余额是否足够,余额不足时返回错误
func (o *Wallet) IncrementBalance(ctx context.Context, userID string, amount int64) (beforeBalance int64, afterBalance int64, err error) {
// 如果 amount 是负数(扣款),需要确保余额不会变为负数
filter := bson.M{"user_id": userID}
if amount < 0 {
// 扣款时确保余额足够balance >= -amount即 balance + amount >= 0
// 例如:余额 100扣款 -150则 balance >= 150 不满足,更新失败
filter["balance"] = bson.M{"$gte": -amount}
}
update := bson.M{
"$inc": bson.M{"balance": amount},
"$set": bson.M{"update_time": time.Now()},
}
opts := options.FindOneAndUpdate().
SetReturnDocument(options.After). // 返回更新后的文档
SetUpsert(true) // 如果不存在则创建
var wallet chatdb.Wallet
err = o.coll.FindOneAndUpdate(ctx, filter, update, opts).Decode(&wallet)
if err != nil {
if err == mongo.ErrNoDocuments {
// 如果是因为余额不足导致更新失败filter 条件不满足)
if amount < 0 {
// 获取当前余额用于错误提示
currentWallet, takeErr := o.Take(ctx, userID)
if takeErr == nil && currentWallet != nil {
return currentWallet.Balance, currentWallet.Balance, errs.NewCodeError(errs.ErrArgs.Code(),
fmt.Sprintf("余额不足:当前余额为 %d 分,需要 %d 分", currentWallet.Balance, -amount))
}
// 如果钱包不存在说明余额为0无法扣款
return 0, 0, errs.NewCodeError(errs.ErrArgs.Code(), fmt.Sprintf("余额不足钱包不存在或余额为0需要 %d 分", -amount))
}
// 如果是增加余额但钱包不存在,应该由 upsert 创建,不应该到这里
// 如果到这里说明有其他问题
return 0, 0, errs.NewCodeError(errs.ErrArgs.Code(), "更新钱包余额失败")
}
return 0, 0, errs.Wrap(err)
}
// 计算更新前的余额
beforeBalance = wallet.Balance - amount
afterBalance = wallet.Balance
// 双重检查:确保余额不为负数(虽然 filter 已经保证,但为了安全再加一次检查)
if afterBalance < 0 {
// 如果余额为负数,回滚操作
rollbackUpdate := bson.M{
"$inc": bson.M{"balance": -amount}, // 回滚
"$set": bson.M{"update_time": time.Now()},
}
_ = o.coll.FindOneAndUpdate(ctx, bson.M{"user_id": userID}, rollbackUpdate, options.FindOneAndUpdate().SetReturnDocument(options.After))
return beforeBalance, beforeBalance, errs.NewCodeError(errs.ErrArgs.Code(), "余额更新后不能为负数")
}
return beforeBalance, afterBalance, nil
}
func (o *Wallet) UpdatePaymentPassword(ctx context.Context, userID string, paymentPassword string) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"payment_password": paymentPassword, "update_time": time.Now()}}, true)
}
func (o *Wallet) UpdateWithdrawAccount(ctx context.Context, userID string, withdrawAccount string) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"withdraw_account": withdrawAccount, "update_time": time.Now()}}, true)
}
func (o *Wallet) UpdateWithdrawAccountWithType(ctx context.Context, userID string, withdrawAccount string, accountType int32) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"withdraw_account": withdrawAccount, "withdraw_account_type": accountType, "update_time": time.Now()}}, true)
}
func (o *Wallet) UpdateRealNameAuth(ctx context.Context, userID string, realNameAuth chatdb.RealNameAuth) error {
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": bson.M{"real_name_auth": realNameAuth, "update_time": time.Now()}}, true)
}
func (o *Wallet) Delete(ctx context.Context, userIDs []string) error {
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}})
}
func (o *Wallet) Page(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.Wallet, error) {
return mongoutil.FindPage[*chatdb.Wallet](ctx, o.coll, bson.M{}, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
// PageByRealNameAuthAuditStatus 按实名认证审核状态分页查询钱包
// auditStatus: 0-所有审核状态1-审核通过2-审核拒绝
// userID: 用户ID搜索可选为空时不过滤
func (o *Wallet) PageByRealNameAuthAuditStatus(ctx context.Context, auditStatus int32, userID string, pagination pagination.Pagination) (int64, []*chatdb.Wallet, error) {
filter := bson.M{
"real_name_auth.id_card": bson.M{"$ne": ""}, // 过滤身份证号不为空的(已完成实名认证)
"$expr": bson.M{ // 身份证长度至少 6null/非字符串时按空字符串处理,避免 count 报错
"$gte": []any{
bson.M{"$strLenCP": bson.M{"$ifNull": []any{"$real_name_auth.id_card", ""}}},
6,
},
},
}
// 支持按审核状态筛选0-待审核1-审核通过2-审核拒绝auditStatus < 0 表示不过滤状态
if auditStatus >= 0 {
filter["real_name_auth.audit_status"] = auditStatus
}
// 支持按用户ID搜索
if userID != "" {
filter["user_id"] = userID
}
return mongoutil.FindPage[*chatdb.Wallet](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "update_time", Value: -1}}))
}
// SearchByRealNameAuth 按实名认证信息搜索钱包返回userIDs
// realNameKeyword: 真实姓名搜索关键词(可选)
// idCardKeyword: 身份证号搜索关键词(可选)
func (o *Wallet) SearchByRealNameAuth(ctx context.Context, realNameKeyword string, idCardKeyword string) ([]string, error) {
filter := bson.M{
"real_name_auth.id_card": bson.M{"$ne": ""}, // 过滤身份证号不为空的(已完成实名认证)
"$expr": bson.M{ // 身份证长度至少 6null/非字符串时按空字符串处理,避免 count 报错
"$gte": []any{
bson.M{"$strLenCP": bson.M{"$ifNull": []any{"$real_name_auth.id_card", ""}}},
6,
},
},
}
// 构建搜索条件
orConditions := []bson.M{}
if realNameKeyword != "" {
orConditions = append(orConditions, bson.M{"real_name_auth.name": bson.M{"$regex": realNameKeyword, "$options": "i"}})
}
if idCardKeyword != "" {
orConditions = append(orConditions, bson.M{"real_name_auth.id_card": bson.M{"$regex": idCardKeyword, "$options": "i"}})
}
if len(orConditions) > 0 {
filter["$or"] = orConditions
}
// 只查询 user_id 字段
opts := options.Find().SetProjection(bson.M{"user_id": 1})
wallets, err := mongoutil.Find[*chatdb.Wallet](ctx, o.coll, filter, opts)
if err != nil {
return nil, err
}
userIDs := make([]string, 0, len(wallets))
for _, wallet := range wallets {
userIDs = append(userIDs, wallet.UserID)
}
return userIDs, nil
}

View File

@@ -0,0 +1,123 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewWalletBalanceRecord(db *mongo.Database) (chatdb.WalletBalanceRecordInterface, error) {
coll := db.Collection("wallet_balance_records")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "type", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "order_id", Value: 1},
},
},
{
Keys: bson.D{
{Key: "transaction_id", Value: 1},
},
},
{
Keys: bson.D{
{Key: "red_packet_id", Value: 1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &WalletBalanceRecord{coll: coll}, nil
}
type WalletBalanceRecord struct {
coll *mongo.Collection
}
func (o *WalletBalanceRecord) Create(ctx context.Context, records ...*chatdb.WalletBalanceRecord) error {
return mongoutil.InsertMany(ctx, o.coll, records)
}
func (o *WalletBalanceRecord) Take(ctx context.Context, recordID string) (*chatdb.WalletBalanceRecord, error) {
return mongoutil.FindOne[*chatdb.WalletBalanceRecord](ctx, o.coll, bson.M{"_id": recordID})
}
func (o *WalletBalanceRecord) FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) {
filter := bson.M{"user_id": userID}
return mongoutil.FindPage[*chatdb.WalletBalanceRecord](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WalletBalanceRecord) FindByUserIDAndType(ctx context.Context, userID string, recordType int32, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) {
filter := bson.M{
"user_id": userID,
"type": recordType,
}
return mongoutil.FindPage[*chatdb.WalletBalanceRecord](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WalletBalanceRecord) FindByOrderID(ctx context.Context, orderID string) (*chatdb.WalletBalanceRecord, error) {
return mongoutil.FindOne[*chatdb.WalletBalanceRecord](ctx, o.coll, bson.M{"order_id": orderID})
}
func (o *WalletBalanceRecord) FindByTransactionID(ctx context.Context, transactionID string) (*chatdb.WalletBalanceRecord, error) {
return mongoutil.FindOne[*chatdb.WalletBalanceRecord](ctx, o.coll, bson.M{"transaction_id": transactionID})
}
func (o *WalletBalanceRecord) FindByRedPacketID(ctx context.Context, redPacketID string) ([]*chatdb.WalletBalanceRecord, error) {
return mongoutil.Find[*chatdb.WalletBalanceRecord](ctx, o.coll, bson.M{"red_packet_id": redPacketID}, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WalletBalanceRecord) GetUserBalanceHistory(ctx context.Context, userID string, startTime, endTime *time.Time, pagination pagination.Pagination) (int64, []*chatdb.WalletBalanceRecord, error) {
filter := bson.M{"user_id": userID}
if startTime != nil || endTime != nil {
timeFilter := bson.M{}
if startTime != nil {
timeFilter["$gte"] = *startTime
}
if endTime != nil {
timeFilter["$lte"] = *endTime
}
filter["create_time"] = timeFilter
}
return mongoutil.FindPage[*chatdb.WalletBalanceRecord](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WalletBalanceRecord) CountByUserID(ctx context.Context, userID string) (int64, error) {
return mongoutil.Count(ctx, o.coll, bson.M{"user_id": userID})
}

View File

@@ -0,0 +1,95 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewWithdraw(db *mongo.Database) (chatdb.WithdrawInterface, error) {
coll := db.Collection("withdraws")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "status", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "create_time", Value: -1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &Withdraw{coll: coll}, nil
}
type Withdraw struct {
coll *mongo.Collection
}
func (o *Withdraw) Create(ctx context.Context, withdraws ...*chatdb.Withdraw) error {
return mongoutil.InsertMany(ctx, o.coll, withdraws)
}
func (o *Withdraw) Take(ctx context.Context, withdrawID string) (*chatdb.Withdraw, error) {
return mongoutil.FindOne[*chatdb.Withdraw](ctx, o.coll, bson.M{"_id": withdrawID})
}
func (o *Withdraw) FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) {
filter := bson.M{"user_id": userID}
return mongoutil.FindPage[*chatdb.Withdraw](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *Withdraw) FindByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) {
filter := bson.M{"status": status}
return mongoutil.FindPage[*chatdb.Withdraw](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *Withdraw) UpdateStatus(ctx context.Context, withdrawID string, status int32, auditorID string, auditRemark string) error {
update := bson.M{
"$set": bson.M{
"status": status,
"auditor_id": auditorID,
"audit_time": time.Now(),
"audit_remark": auditRemark,
"update_time": time.Now(),
},
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": withdrawID}, update, false)
}
func (o *Withdraw) Page(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.Withdraw, error) {
return mongoutil.FindPage[*chatdb.Withdraw](ctx, o.coll, bson.M{}, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}

View File

@@ -0,0 +1,103 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat"
)
func NewWithdrawApplication(db *mongo.Database) (chatdb.WithdrawApplicationInterface, error) {
coll := db.Collection("withdraw_applications")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "user_id", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "status", Value: 1},
{Key: "create_time", Value: -1},
},
},
{
Keys: bson.D{
{Key: "create_time", Value: -1},
},
},
})
if err != nil {
return nil, errs.Wrap(err)
}
return &WithdrawApplication{coll: coll}, nil
}
type WithdrawApplication struct {
coll *mongo.Collection
}
func (o *WithdrawApplication) Create(ctx context.Context, applications ...*chatdb.WithdrawApplication) error {
return mongoutil.InsertMany(ctx, o.coll, applications)
}
func (o *WithdrawApplication) Take(ctx context.Context, applicationID string) (*chatdb.WithdrawApplication, error) {
return mongoutil.FindOne[*chatdb.WithdrawApplication](ctx, o.coll, bson.M{"_id": applicationID})
}
func (o *WithdrawApplication) FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) {
filter := bson.M{"user_id": userID}
return mongoutil.FindPage[*chatdb.WithdrawApplication](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WithdrawApplication) FindByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) {
filter := bson.M{"status": status}
return mongoutil.FindPage[*chatdb.WithdrawApplication](ctx, o.coll, filter, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WithdrawApplication) UpdateStatus(ctx context.Context, applicationID string, status int32, auditorID string, auditRemark string) error {
update := bson.M{
"$set": bson.M{
"status": status,
"auditor_id": auditorID,
"audit_time": time.Now(),
"audit_remark": auditRemark,
"update_time": time.Now(),
},
}
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": applicationID}, update, false)
}
func (o *WithdrawApplication) Page(ctx context.Context, pagination pagination.Pagination) (int64, []*chatdb.WithdrawApplication, error) {
return mongoutil.FindPage[*chatdb.WithdrawApplication](ctx, o.coll, bson.M{}, pagination, options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}}))
}
func (o *WithdrawApplication) Update(ctx context.Context, applicationID string, data map[string]any) error {
if len(data) == 0 {
return nil
}
data["update_time"] = time.Now()
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": applicationID}, bson.M{"$set": data}, false)
}

View File

@@ -0,0 +1,51 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// Admin user
type Admin struct {
Account string `bson:"account"`
Password string `bson:"password"`
OperationPassword string `bson:"operation_password"`
FaceURL string `bson:"face_url"`
Nickname string `bson:"nickname"`
UserID string `bson:"user_id"`
Level int32 `bson:"level"`
GoogleAuthKey string `bson:"google_auth_key"`
CreateTime time.Time `bson:"create_time"`
}
func (Admin) TableName() string {
return "admins"
}
type AdminInterface interface {
Create(ctx context.Context, admins []*Admin) error
Take(ctx context.Context, account string) (*Admin, error)
TakeUserID(ctx context.Context, userID string) (*Admin, error)
Update(ctx context.Context, account string, update map[string]any) error
ChangePassword(ctx context.Context, userID string, newPassword string) error
ChangeOperationPassword(ctx context.Context, userID string, newPassword string) error
ClearGoogleAuthKey(ctx context.Context, userID string) error
Delete(ctx context.Context, userIDs []string) error
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*Admin, error)
}

View File

@@ -0,0 +1,49 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/pagination"
"time"
)
type Applet struct {
ID string `bson:"id"`
Name string `bson:"name"`
AppID string `bson:"app_id"`
Icon string `bson:"icon"`
URL string `bson:"url"`
MD5 string `bson:"md5"`
Size int64 `bson:"size"`
Version string `bson:"version"`
Priority uint32 `bson:"priority"`
Status uint8 `bson:"status"`
CreateTime time.Time `bson:"create_time"`
}
func (Applet) TableName() string {
return "applets"
}
type AppletInterface interface {
Create(ctx context.Context, applets []*Applet) error
Del(ctx context.Context, ids []string) error
Update(ctx context.Context, id string, data map[string]any) error
Take(ctx context.Context, id string) (*Applet, error)
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*Applet, error)
FindOnShelf(ctx context.Context) ([]*Applet, error)
FindID(ctx context.Context, ids []string) ([]*Applet, error)
}

View File

@@ -0,0 +1,29 @@
package admin
import (
"context"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
type Application struct {
ID primitive.ObjectID `bson:"_id"`
Platform string `bson:"platform"`
Hot bool `bson:"hot"`
Version string `bson:"version"`
Url string `bson:"url"`
Text string `bson:"text"`
Force bool `bson:"force"`
Latest bool `bson:"latest"`
CreateTime time.Time `bson:"create_time"`
}
type ApplicationInterface interface {
LatestVersion(ctx context.Context, platform string) (*Application, error)
AddVersion(ctx context.Context, val *Application) error
UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error
DeleteVersion(ctx context.Context, id []primitive.ObjectID) error
PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*Application, error)
FindPlatform(ctx context.Context, id []primitive.ObjectID) ([]string, error)
}

View File

@@ -0,0 +1,33 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import "context"
// ClientConfig config
type ClientConfig struct {
Key string `bson:"key"`
Value string `bson:"value"`
}
func (ClientConfig) TableName() string {
return "client_config"
}
type ClientConfigInterface interface {
Set(ctx context.Context, config map[string]string) error
Get(ctx context.Context) (map[string]string, error)
Del(ctx context.Context, keys []string) error
}

View File

@@ -0,0 +1,42 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/pagination"
"time"
)
// ForbiddenAccount table
type ForbiddenAccount struct {
UserID string `bson:"user_id"`
Reason string `bson:"reason"`
OperatorUserID string `bson:"operator_user_id"`
CreateTime time.Time `bson:"create_time"`
}
func (ForbiddenAccount) TableName() string {
return "forbidden_accounts"
}
type ForbiddenAccountInterface interface {
Create(ctx context.Context, ms []*ForbiddenAccount) error
Take(ctx context.Context, userID string) (*ForbiddenAccount, error)
Delete(ctx context.Context, userIDs []string) error
Find(ctx context.Context, userIDs []string) ([]*ForbiddenAccount, error)
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*ForbiddenAccount, error)
FindAllIDs(ctx context.Context) ([]string, error)
}

View File

@@ -0,0 +1,40 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/pagination"
"time"
)
type InvitationRegister struct {
InvitationCode string `bson:"invitation_code"`
UsedByUserID string `bson:"used_by_user_id"`
CreateTime time.Time `bson:"create_time"`
}
func (InvitationRegister) TableName() string {
return "invitation_registers"
}
type InvitationRegisterInterface interface {
Find(ctx context.Context, codes []string) ([]*InvitationRegister, error)
Del(ctx context.Context, codes []string) error
Create(ctx context.Context, v []*InvitationRegister) error
Take(ctx context.Context, code string) (*InvitationRegister, error)
Update(ctx context.Context, code string, data map[string]any) error
Search(ctx context.Context, keyword string, state int32, userIDs []string, codes []string, pagination pagination.Pagination) (int64, []*InvitationRegister, error)
}

View File

@@ -0,0 +1,40 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/pagination"
"time"
)
type IPForbidden struct {
IP string `bson:"ip"`
LimitRegister bool `bson:"limit_register"`
LimitLogin bool `bson:"limit_login"`
CreateTime time.Time `bson:"create_time"`
}
func (IPForbidden) IPForbidden() string {
return "ip_forbiddens"
}
type IPForbiddenInterface interface {
Take(ctx context.Context, ip string) (*IPForbidden, error)
Find(ctx context.Context, ips []string) ([]*IPForbidden, error)
Search(ctx context.Context, keyword string, state int32, pagination pagination.Pagination) (int64, []*IPForbidden, error)
Create(ctx context.Context, ms []*IPForbidden) error
Delete(ctx context.Context, ips []string) error
}

View File

@@ -0,0 +1,39 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"github.com/openimsdk/tools/db/pagination"
"time"
)
type LimitUserLoginIP struct {
UserID string `bson:"user_id"`
IP string `bson:"ip"`
CreateTime time.Time `bson:"create_time"`
}
func (LimitUserLoginIP) TableName() string {
return "limit_user_login_ips"
}
type LimitUserLoginIPInterface interface {
Create(ctx context.Context, ms []*LimitUserLoginIP) error
Delete(ctx context.Context, ms []*LimitUserLoginIP) error
Count(ctx context.Context, userID string) (uint32, error)
Take(ctx context.Context, userID string, ip string) (*LimitUserLoginIP, error)
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*LimitUserLoginIP, error)
}

View File

@@ -0,0 +1,39 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
type RegisterAddFriend struct {
UserID string `bson:"user_id"`
CreateTime time.Time `bson:"create_time"`
}
func (RegisterAddFriend) TableName() string {
return "register_add_friends"
}
type RegisterAddFriendInterface interface {
Add(ctx context.Context, registerAddFriends []*RegisterAddFriend) error
Del(ctx context.Context, userIDs []string) error
FindUserID(ctx context.Context, userIDs []string) ([]string, error)
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*RegisterAddFriend, error)
CountTotal(ctx context.Context) (int64, error) // 统计好友总数
}

View File

@@ -0,0 +1,40 @@
// Copyright © 2023 OpenIM open source community. 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 admin
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
type RegisterAddGroup struct {
GroupID string `bson:"group_id"`
CreateTime time.Time `bson:"create_time"`
}
func (RegisterAddGroup) TableName() string {
return "register_add_groups"
}
type RegisterAddGroupInterface interface {
Add(ctx context.Context, registerAddGroups []*RegisterAddGroup) error
Del(ctx context.Context, groupIDs []string) error
FindGroupID(ctx context.Context, groupIDs []string) ([]string, error)
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*RegisterAddGroup, error)
CountTotal(ctx context.Context) (int64, error) // 统计群组总数
CountToday(ctx context.Context) (int64, error) // 统计今天新建的群组数
}

View File

@@ -0,0 +1,33 @@
package bot
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
type Agent struct {
UserID string `bson:"user_id"`
NickName string `bson:"nick_name"`
FaceURL string `bson:"face_url"`
Key string `bson:"key"`
Url string `bson:"url"`
Identity string `bson:"identity"`
Model string `bson:"model"`
Prompts string `bson:"prompts"`
CreateTime time.Time `bson:"create_time"`
}
func (Agent) TableName() string {
return "agent"
}
type AgentInterface interface {
Create(ctx context.Context, elems ...*Agent) error
Take(ctx context.Context, userID string) (*Agent, error)
Find(ctx context.Context, userIDs []string) ([]*Agent, error)
Update(ctx context.Context, userID string, data map[string]any) error
Delete(ctx context.Context, userIDs []string) error
Page(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*Agent, error)
}

View File

@@ -0,0 +1,22 @@
package bot
import (
"context"
)
type ConversationRespID struct {
ConversationID string `bson:"conversation_id"`
AgentID string `bson:"agent_id"`
PreviousResponseID string `bson:"previous_response_id"`
}
func (ConversationRespID) TableName() string {
return "conversation_resp_id"
}
type ConversationRespIDInterface interface {
Create(ctx context.Context, elems ...*ConversationRespID) error
Take(ctx context.Context, convID, agentID string) (*ConversationRespID, error)
Update(ctx context.Context, convID, agentID string, data map[string]any) error
Delete(ctx context.Context, convID, agentID string) error
}

View File

@@ -0,0 +1,40 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
)
type Account struct {
UserID string `bson:"user_id"`
Password string `bson:"password"`
CreateTime time.Time `bson:"create_time"`
ChangeTime time.Time `bson:"change_time"`
OperatorUserID string `bson:"operator_user_id"`
}
func (Account) TableName() string {
return "accounts"
}
type AccountInterface interface {
Create(ctx context.Context, accounts ...*Account) error
Take(ctx context.Context, userId string) (*Account, error)
Update(ctx context.Context, userID string, data map[string]any) error
UpdatePassword(ctx context.Context, userId string, password string) error
Delete(ctx context.Context, userIDs []string) error
}

View File

@@ -0,0 +1,51 @@
package chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
type Attribute struct {
UserID string `bson:"user_id"`
Account string `bson:"account"`
PhoneNumber string `bson:"phone_number"`
AreaCode string `bson:"area_code"`
Email string `bson:"email"`
Nickname string `bson:"nickname"`
FaceURL string `bson:"face_url"`
Gender int32 `bson:"gender"`
CreateTime time.Time `bson:"create_time"`
ChangeTime time.Time `bson:"change_time"`
BirthTime time.Time `bson:"birth_time"`
Level int32 `bson:"level"`
UserType int32 `bson:"user_type"` // 用户类型: 0=普通用户, 1=企业用户, 2=机器人, 3=管理员
UserFlag string `bson:"user_flag"` // 用户标签/标识类似UserType的字符串版本
AllowVibration int32 `bson:"allow_vibration"`
AllowBeep int32 `bson:"allow_beep"`
AllowAddFriend int32 `bson:"allow_add_friend"`
GlobalRecvMsgOpt int32 `bson:"global_recv_msg_opt"`
RegisterType int32 `bson:"register_type"`
}
func (Attribute) TableName() string {
return "attributes"
}
type AttributeInterface interface {
// NewTx(tx any) AttributeInterface
Create(ctx context.Context, attribute ...*Attribute) error
Update(ctx context.Context, userID string, data map[string]any) error
Find(ctx context.Context, userIds []string) ([]*Attribute, error)
FindAccount(ctx context.Context, accounts []string) ([]*Attribute, error)
Search(ctx context.Context, keyword string, genders []int32, pagination pagination.Pagination) (int64, []*Attribute, error)
TakePhone(ctx context.Context, areaCode string, phoneNumber string) (*Attribute, error)
TakeEmail(ctx context.Context, email string) (*Attribute, error)
TakeAccount(ctx context.Context, account string) (*Attribute, error)
Take(ctx context.Context, userID string) (*Attribute, error)
SearchNormalUser(ctx context.Context, keyword string, forbiddenID []string, gender int32, startTime, endTime *time.Time, pagination pagination.Pagination) (int64, []*Attribute, error)
SearchNormalUserWithUserIDs(ctx context.Context, keyword string, forbiddenID []string, gender int32, startTime, endTime *time.Time, userIDs []string, pagination pagination.Pagination) (int64, []*Attribute, error) // 按条件搜索用户支持额外的userIDs过滤
SearchUser(ctx context.Context, keyword string, userIDs []string, genders []int32, pagination pagination.Pagination) (int64, []*Attribute, error)
Delete(ctx context.Context, userIDs []string) error
}

View File

@@ -0,0 +1,32 @@
package chat
import (
"context"
"github.com/openimsdk/tools/db/pagination"
)
type Credential struct {
UserID string `bson:"user_id"`
Account string `bson:"account"`
Type int `bson:"type"` // 1:phone;2:email
AllowChange bool `bson:"allow_change"`
}
func (Credential) TableName() string {
return "credentials"
}
type CredentialInterface interface {
Create(ctx context.Context, credential ...*Credential) error
CreateOrUpdateAccount(ctx context.Context, credential *Credential) error
Update(ctx context.Context, userID string, data map[string]any) error
Find(ctx context.Context, userID string) ([]*Credential, error)
FindAccount(ctx context.Context, accounts []string) ([]*Credential, error)
Search(ctx context.Context, keyword string, pagination pagination.Pagination) (int64, []*Credential, error)
TakeAccount(ctx context.Context, account string) (*Credential, error)
Take(ctx context.Context, userID string) (*Credential, error)
SearchNormalUser(ctx context.Context, keyword string, forbiddenID []string, pagination pagination.Pagination) (int64, []*Credential, error)
SearchUser(ctx context.Context, keyword string, userIDs []string, pagination pagination.Pagination) (int64, []*Credential, error)
Delete(ctx context.Context, userIDs []string) error
DeleteByUserIDType(ctx context.Context, credentials ...*Credential) error
}

View File

@@ -0,0 +1,69 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// FavoriteType 收藏类型
const (
FavoriteTypeText = 1 // 文本
FavoriteTypeImage = 2 // 图片
FavoriteTypeLink = 3 // 链接
FavoriteTypeFile = 4 // 文件
FavoriteTypeVoice = 5 // 语音
FavoriteTypeVideo = 6 // 视频
FavoriteTypeLocation = 7 // 位置
)
type Favorite struct {
ID string `bson:"_id"` // 收藏IDMongoDB ObjectID
UserID string `bson:"user_id"` // 用户ID收藏者
Type int32 `bson:"type"` // 收藏类型1-文本2-图片3-链接4-文件5-语音6-视频7-位置
Title string `bson:"title"` // 标题(可选)
Content string `bson:"content"` // 内容根据类型不同可能是文本、图片URL、链接URL、文件路径等
Description string `bson:"description"` // 摘要/描述(可选)
Thumbnail string `bson:"thumbnail"` // 缩略图URL用于图片、视频、链接等
LinkURL string `bson:"link_url"` // 链接URL用于链接类型
FileSize int64 `bson:"file_size"` // 文件大小(字节,用于文件、语音、视频等)
Duration int32 `bson:"duration"` // 时长(秒,用于语音、视频等)
Location string `bson:"location"` // 位置信息JSON格式用于位置类型
Tags []string `bson:"tags"` // 标签列表
Remark string `bson:"remark"` // 备注(可选)
Status int32 `bson:"status"` // 状态0-已删除1-正常
CreateTime time.Time `bson:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time"` // 更新时间
}
func (Favorite) TableName() string {
return "favorites"
}
type FavoriteInterface interface {
Create(ctx context.Context, favorites ...*Favorite) error
Take(ctx context.Context, favoriteID string) (*Favorite, error)
FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*Favorite, error)
FindByUserIDAndType(ctx context.Context, userID string, favoriteType int32, pagination pagination.Pagination) (int64, []*Favorite, error)
SearchByKeyword(ctx context.Context, userID string, keyword string, pagination pagination.Pagination) (int64, []*Favorite, error)
Update(ctx context.Context, favoriteID string, data map[string]any) error
Delete(ctx context.Context, favoriteIDs []string) error
DeleteByUserID(ctx context.Context, userID string) error
CountByUserID(ctx context.Context, userID string) (int64, error)
FindByTags(ctx context.Context, userID string, tags []string, pagination pagination.Pagination) (int64, []*Favorite, error)
}

View File

@@ -0,0 +1,67 @@
package chat
import (
"context"
"time"
)
// LiveKit 表示一台LiveKit服务器配置
type LiveKit struct {
ID string `bson:"_id" json:"id"` // 服务器唯一标识
Name string `bson:"name" json:"name"` // 服务器名称
URL string `bson:"url" json:"url"` // LiveKit服务器地址
Key string `bson:"key" json:"key"` // API Key
Secret string `bson:"secret" json:"secret"` // API Secret
Region string `bson:"region" json:"region"` // 服务器区域
Status int `bson:"status" json:"status"` // 状态0-禁用1-启用
Priority int `bson:"priority" json:"priority"` // 优先级,数字越小优先级越高
MaxRooms int `bson:"max_rooms" json:"max_rooms"` // 最大房间数
MaxUsers int `bson:"max_users" json:"max_users"` // 最大用户数
Description string `bson:"description" json:"description"` // 描述信息
CreateTime time.Time `bson:"create_time" json:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time" json:"update_time"` // 更新时间
}
// TableName 返回表名
func (LiveKit) TableName() string {
return "livekits"
}
// LiveKitInterface 定义LiveKit数据库操作接口
type LiveKitInterface interface {
// Create 创建LiveKit服务器配置
Create(ctx context.Context, livekits ...*LiveKit) error
// Delete 删除LiveKit服务器配置
Delete(ctx context.Context, ids []string) error
// Update 更新LiveKit服务器配置
Update(ctx context.Context, livekit *LiveKit) error
// FindByID 根据ID查找LiveKit配置
FindByID(ctx context.Context, id string) (*LiveKit, error)
// FindByStatus 根据状态查找LiveKit配置列表
FindByStatus(ctx context.Context, status int) ([]*LiveKit, error)
// FindAll 查找所有LiveKit配置
FindAll(ctx context.Context) ([]*LiveKit, error)
// FindAvailable 查找可用的LiveKit服务器按优先级排序
FindAvailable(ctx context.Context) ([]*LiveKit, error)
// FindByRegion 根据区域查找LiveKit配置
FindByRegion(ctx context.Context, region string) ([]*LiveKit, error)
// UpdateStatus 更新服务器状态
UpdateStatus(ctx context.Context, id string, status int) error
// UpdatePriority 更新服务器优先级
UpdatePriority(ctx context.Context, id string, priority int) error
// GetNextAvailable 获取下一个可用的LiveKit服务器负载均衡
GetNextAvailable(ctx context.Context) (*LiveKit, error)
}
// 状态常量
const (
LiveKitStatusDisabled = 0 // 禁用
LiveKitStatusEnabled = 1 // 启用
)
// 默认值
const (
DefaultMaxRooms = 1000 // 默认最大房间数
DefaultMaxUsers = 100 // 默认最大用户数
DefaultPriority = 100 // 默认优先级
)

View File

@@ -0,0 +1,42 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
)
type Register struct {
UserID string `bson:"user_id"`
DeviceID string `bson:"device_id"`
IP string `bson:"ip"`
Platform string `bson:"platform"`
AccountType string `bson:"account_type"`
Mode string `bson:"mode"`
CreateTime time.Time `bson:"create_time"`
}
func (Register) TableName() string {
return "registers"
}
type RegisterInterface interface {
// NewTx(tx any) RegisterInterface
Create(ctx context.Context, registers ...*Register) error
CountTotal(ctx context.Context, before *time.Time) (int64, error)
CountToday(ctx context.Context) (int64, error) // 统计今天注册的用户数
Delete(ctx context.Context, userIDs []string) error
}

View File

@@ -0,0 +1,78 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// ScheduledTaskStatus 定时任务状态
const (
ScheduledTaskStatusDisabled = 0 // 已禁用
ScheduledTaskStatusEnabled = 1 // 已启用
)
// MessageType 消息类型
const (
MessageTypeText = 1 // 文本消息
MessageTypeImage = 2 // 图片消息
MessageTypeVideo = 3 // 视频消息
)
// ScheduledTask 定时任务配置
// CronExpression格式分 时 日 月 周
// 例如:"0 9 * * *" 表示每天9点执行
//
// "*/5 * * * *" 表示每5分钟执行
// "0 0 * * 1" 表示每周一0点执行
type ScheduledTask struct {
ID string `bson:"_id"` // 任务ID
UserID string `bson:"user_id"` // 用户ID
Name string `bson:"name"` // 任务名称
CronExpression string `bson:"cron_expression"` // Crontab表达式分 时 日 月 周(例如:"0 9 * * *"
Messages []Message `bson:"messages"` // 消息列表(支持多条消息一起发送)
RecvIDs []string `bson:"recv_ids"` // 接收者ID列表单聊可以多个
GroupIDs []string `bson:"group_ids"` // 群组ID列表群聊可以多个
Status int32 `bson:"status"` // 状态0-已禁用1-已启用
CreateTime time.Time `bson:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time"` // 更新时间
}
// Message 消息内容
type Message struct {
Type int32 `bson:"type"` // 消息类型1-文本2-图片3-视频
Content string `bson:"content"` // 消息内容文本内容、图片URL、视频URL等
Thumbnail string `bson:"thumbnail"` // 缩略图URL用于图片和视频
Duration int32 `bson:"duration"` // 时长(秒,用于视频)
FileSize int64 `bson:"file_size"` // 文件大小(字节,用于图片和视频)
Width int32 `bson:"width"` // 宽度(像素,用于图片和视频)
Height int32 `bson:"height"` // 高度(像素,用于图片和视频)
}
func (ScheduledTask) TableName() string {
return "scheduled_tasks"
}
type ScheduledTaskInterface interface {
Create(ctx context.Context, tasks ...*ScheduledTask) error
Take(ctx context.Context, taskID string) (*ScheduledTask, error)
FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*ScheduledTask, error)
FindAll(ctx context.Context, pagination pagination.Pagination) (int64, []*ScheduledTask, error)
Update(ctx context.Context, taskID string, data map[string]any) error
Delete(ctx context.Context, taskIDs []string) error
}

View File

@@ -0,0 +1,144 @@
package chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// 敏感词相关结构体定义
type SensitiveWord struct {
ID string `bson:"_id" json:"id"` // 主键ID
Word string `bson:"word" json:"word"` // 敏感词内容
Level int32 `bson:"level" json:"level"` // 敏感词级别 1:低 2:中 3:高
Type int32 `bson:"type" json:"type"` // 敏感词类型 1:政治 2:色情 3:暴力 4:广告 5:其他
Action int32 `bson:"action" json:"action"` // 处理动作 1:替换为*** 2:拦截不发
Status int32 `bson:"status" json:"status"` // 状态 1:启用 0:禁用
Creator string `bson:"creator" json:"creator"` // 创建者
Updater string `bson:"updater" json:"updater"` // 更新者
CreateTime time.Time `bson:"create_time" json:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time" json:"update_time"` // 更新时间
Remark string `bson:"remark" json:"remark"` // 备注
}
func (SensitiveWord) TableName() string {
return "sensitive_words"
}
type SensitiveWordLog struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
UserID string `bson:"user_id"` // 触发用户ID
GroupID string `bson:"group_id"` // 触发群组ID (如果是在群聊中)
Content string `bson:"content"` // 原始消息内容
MatchedWords []string `bson:"matched_words"` // 匹配到的敏感词
Action int32 `bson:"action"` // 采取的动作
ProcessedText string `bson:"processed_text"` // 处理后的文本 (如果被替换)
CreateTime time.Time `bson:"create_time"`
}
func (SensitiveWordLog) TableName() string {
return "sensitive_word_logs"
}
type SensitiveWordGroup struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"` // 分组名称
Remark string `bson:"remark"` // 备注
CreateTime time.Time `bson:"create_time"`
UpdateTime time.Time `bson:"update_time"`
}
func (SensitiveWordGroup) TableName() string {
return "sensitive_word_groups"
}
type SensitiveWordConfig struct {
ID string `bson:"_id" json:"id"` // 主键ID
EnableFilter bool `bson:"enable_filter" json:"enable_filter"` // 是否启用过滤
FilterMode int32 `bson:"filter_mode" json:"filter_mode"` // 过滤模式
ReplaceChar string `bson:"replace_char" json:"replace_char"` // 替换字符,默认***
WhitelistUsers []string `bson:"whitelist_users" json:"whitelist_users"` // 白名单用户
WhitelistGroups []string `bson:"whitelist_groups" json:"whitelist_groups"` // 白名单群组
LogEnabled bool `bson:"log_enabled" json:"log_enabled"` // 是否记录日志
AutoApprove bool `bson:"auto_approve" json:"auto_approve"` // 是否自动审核
UpdateTime time.Time `bson:"update_time" json:"update_time"` // 更新时间
}
func (SensitiveWordConfig) TableName() string {
return "sensitive_word_configs"
}
// 敏感词级别常量
const (
SensitiveLevelLow = 1 // 低级别
SensitiveLevelMedium = 2 // 中级别
SensitiveLevelHigh = 3 // 高级别
)
// 敏感词类型常量
const (
SensitiveTypePolitical = 1 // 政治
SensitiveTypePorn = 2 // 色情
SensitiveTypeViolence = 3 // 暴力
SensitiveTypeAd = 4 // 广告
SensitiveTypeOther = 5 // 其他
)
// 处理动作常量
const (
SensitiveActionReplace = 1 // 替换为***
SensitiveActionBlock = 2 // 拦截不发
)
// 状态常量
const (
SensitiveStatusDisabled = 0 // 禁用
SensitiveStatusEnabled = 1 // 启用
)
type SensitiveWordInterface interface {
// 敏感词管理
CreateSensitiveWord(ctx context.Context, word *SensitiveWord) error
UpdateSensitiveWord(ctx context.Context, id string, data map[string]any) error
DeleteSensitiveWord(ctx context.Context, ids []string) error
GetSensitiveWord(ctx context.Context, id string) (*SensitiveWord, error)
SearchSensitiveWords(ctx context.Context, keyword string, action int32, status int32, pagination pagination.Pagination) (int64, []*SensitiveWord, error)
GetAllSensitiveWords(ctx context.Context) ([]*SensitiveWord, error)
GetEnabledSensitiveWords(ctx context.Context) ([]*SensitiveWord, error)
// 敏感词检测
CheckSensitiveWords(ctx context.Context, content string) ([]*SensitiveWord, error)
FilterContent(ctx context.Context, content string) (string, []*SensitiveWord, error)
// 敏感词日志
CreateSensitiveWordLog(ctx context.Context, log *SensitiveWordLog) error
GetSensitiveWordLogs(ctx context.Context, userID string, groupID string, pagination pagination.Pagination) (int64, []*SensitiveWordLog, error)
DeleteSensitiveWordLogs(ctx context.Context, ids []string) error
// 敏感词分组管理
CreateSensitiveWordGroup(ctx context.Context, group *SensitiveWordGroup) error
UpdateSensitiveWordGroup(ctx context.Context, id string, data map[string]any) error
DeleteSensitiveWordGroup(ctx context.Context, ids []string) error
GetSensitiveWordGroup(ctx context.Context, id string) (*SensitiveWordGroup, error)
GetAllSensitiveWordGroups(ctx context.Context) ([]*SensitiveWordGroup, error)
// 敏感词配置管理
GetSensitiveWordConfig(ctx context.Context) (*SensitiveWordConfig, error)
UpdateSensitiveWordConfig(ctx context.Context, config *SensitiveWordConfig) error
IsFilterEnabled(ctx context.Context) (bool, error)
GetFilterMode(ctx context.Context) (int32, error)
GetReplaceChar(ctx context.Context) (string, error)
IsUserInWhitelist(ctx context.Context, userID string) (bool, error)
IsGroupInWhitelist(ctx context.Context, groupID string) (bool, error)
// 批量操作
BatchCreateSensitiveWords(ctx context.Context, words []*SensitiveWord) error
BatchUpdateSensitiveWords(ctx context.Context, updates map[string]map[string]any) error
BatchDeleteSensitiveWords(ctx context.Context, ids []string) error
// 统计信息
GetSensitiveWordStats(ctx context.Context) (map[string]int64, error)
GetSensitiveWordLogStats(ctx context.Context, startTime, endTime time.Time) (map[string]int64, error)
}

View File

@@ -0,0 +1,69 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// ConfigValueType 配置值类型
const (
ConfigValueTypeString = 1 // 字符串类型
ConfigValueTypeNumber = 2 // 数字类型
ConfigValueTypeBool = 3 // 布尔类型
ConfigValueTypeJSON = 4 // JSON类型
)
// ConfigKey 常用配置键
const (
// 钱包相关配置
ConfigKeyWalletEnabled = "wallet.enabled" // 是否开启钱包功能
// 注册相关配置
ConfigKeyPhoneRegisterVerifyCodeEnabled = "register.phone.verify_code.enabled" // 手机号注册验证码功能是否开启
)
// SystemConfig 系统配置模型
type SystemConfig struct {
Key string `bson:"key"` // 配置键(唯一标识)
Title string `bson:"title"` // 配置标题
Value string `bson:"value"` // 配置值字符串形式存储根据ValueType解析
ValueType int32 `bson:"value_type"` // 配置值类型1-字符串2-数字3-布尔4-JSON
Description string `bson:"description"` // 配置描述
Enabled bool `bson:"enabled"` // 是否启用(用于开关类配置)
ShowInApp bool `bson:"show_in_app"` // 是否在APP端展示
CreateTime time.Time `bson:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time"` // 更新时间
}
func (SystemConfig) TableName() string {
return "system_configs"
}
type SystemConfigInterface interface {
Create(ctx context.Context, configs ...*SystemConfig) error
Take(ctx context.Context, key string) (*SystemConfig, error)
FindByKeys(ctx context.Context, keys []string) ([]*SystemConfig, error)
FindAll(ctx context.Context, pagination pagination.Pagination) (int64, []*SystemConfig, error)
Update(ctx context.Context, key string, data map[string]any) error
UpdateValue(ctx context.Context, key string, value string) error
UpdateEnabled(ctx context.Context, key string, enabled bool) error
Delete(ctx context.Context, keys []string) error
GetEnabledConfigs(ctx context.Context) ([]*SystemConfig, error)
GetAppConfigs(ctx context.Context) ([]*SystemConfig, error) // 获取所有 show_in_app=true 的配置
}

View File

@@ -0,0 +1,43 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
type UserLoginRecord struct {
UserID string `bson:"user_id"`
LoginTime time.Time `bson:"login_time"`
IP string `bson:"ip"`
DeviceID string `bson:"device_id"`
Platform string `bson:"platform"`
}
func (UserLoginRecord) TableName() string {
return "user_login_records"
}
type UserLoginRecordInterface interface {
Create(ctx context.Context, records ...*UserLoginRecord) error
CountTotal(ctx context.Context, before *time.Time) (int64, error)
CountRangeEverydayTotal(ctx context.Context, start *time.Time, end *time.Time) (map[string]int64, int64, error)
CountTodayActiveUsers(ctx context.Context) (int64, error) // 统计今天活跃用户数(今天登录的不同用户数)
GetLatestLoginIP(ctx context.Context, userID string) (string, error) // 获取用户最新登录IP
Search(ctx context.Context, userID, ip string, pagination pagination.Pagination) (int64, []*UserLoginRecord, error) // 查询登录记录支持按用户ID或IP查询
}

View File

@@ -0,0 +1,43 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
)
type VerifyCode struct {
ID string `bson:"_id"`
Account string `bson:"account"`
Platform string `bson:"platform"`
Code string `bson:"code"`
Duration uint `bson:"duration"`
Count int `bson:"count"`
Used bool `bson:"used"`
CreateTime time.Time `bson:"create_time"`
}
func (VerifyCode) TableName() string {
return "verify_codes"
}
type VerifyCodeInterface interface {
Add(ctx context.Context, ms []*VerifyCode) error
RangeNum(ctx context.Context, account string, start time.Time, end time.Time) (int64, error)
TakeLast(ctx context.Context, account string) (*VerifyCode, error)
Incr(ctx context.Context, id string) error
Delete(ctx context.Context, id string) error
}

View File

@@ -0,0 +1,116 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// BalanceRecordType 余额变动类型
const (
BalanceRecordTypeRecharge = 1 // 充值
BalanceRecordTypeWithdraw = 2 // 提现/提款
BalanceRecordTypeConsume = 3 // 消费
BalanceRecordTypeRefund = 4 // 退款
BalanceRecordTypeReward = 5 // 奖励
BalanceRecordTypeAdminRecharge = 6 // 后台充值
BalanceRecordTypeSendRedPacket = 7 // 发红包(减少余额)
BalanceRecordTypeGrabRedPacket = 8 // 抢红包(增加余额)
BalanceRecordTypeOther = 99 // 其他
)
// WithdrawAccountType 提现账号类型
const (
WithdrawAccountTypeAlipay = 1 // 支付宝
WithdrawAccountTypeWeChat = 2 // 微信
WithdrawAccountTypeBankCard = 3 // 银行卡
)
// RealNameAuth 实名认证信息
type RealNameAuth struct {
IDCard string `bson:"id_card"` // 身份证号
IDCardPhotoFront string `bson:"id_card_photo_front"` // 身份证正面照片URL
IDCardPhotoBack string `bson:"id_card_photo_back"` // 身份证反面照片URL
Name string `bson:"name"` // 真实姓名
AuditStatus int32 `bson:"audit_status"` // 审核状态0-未审核1-审核通过2-审核拒绝
}
// Wallet 钱包模型
type Wallet struct {
UserID string `bson:"user_id"` // 用户ID
Balance int64 `bson:"balance"` // 用户余额(单位:分)
PaymentPassword string `bson:"payment_password"` // 支付密码(加密存储)
WithdrawAccount string `bson:"withdraw_account"` // 提现账号
WithdrawAccountType int32 `bson:"withdraw_account_type"` // 提现账号类型1-支付宝2-微信3-银行卡
RealNameAuth RealNameAuth `bson:"real_name_auth"` // 实名认证信息
WithdrawReceiveAccount string `bson:"withdraw_receive_account"` // 提现收款账号
CreateTime time.Time `bson:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time"` // 更新时间
}
func (Wallet) TableName() string {
return "wallets"
}
type WalletInterface interface {
Create(ctx context.Context, wallets ...*Wallet) error
Take(ctx context.Context, userID string) (*Wallet, error)
Find(ctx context.Context, userIDs []string) ([]*Wallet, error)
Update(ctx context.Context, userID string, data map[string]any) error
UpdateBalance(ctx context.Context, userID string, balance int64) error
IncrementBalance(ctx context.Context, userID string, amount int64) (beforeBalance int64, afterBalance int64, err error) // 原子更新余额,返回更新前后的余额
UpdatePaymentPassword(ctx context.Context, userID string, paymentPassword string) error
UpdateWithdrawAccount(ctx context.Context, userID string, withdrawAccount string) error // 更新提款账号(兼容旧接口)
UpdateWithdrawAccountWithType(ctx context.Context, userID string, withdrawAccount string, accountType int32) error // 更新提款账号(带类型)
UpdateRealNameAuth(ctx context.Context, userID string, realNameAuth RealNameAuth) error // 更新实名认证信息
Delete(ctx context.Context, userIDs []string) error
Page(ctx context.Context, pagination pagination.Pagination) (int64, []*Wallet, error)
PageByRealNameAuthAuditStatus(ctx context.Context, auditStatus int32, userID string, pagination pagination.Pagination) (int64, []*Wallet, error) // 按实名认证审核状态分页查询支持用户ID搜索
SearchByRealNameAuth(ctx context.Context, realNameKeyword string, idCardKeyword string) ([]string, error) // 按实名认证信息搜索钱包返回userIDs
}
// WalletBalanceRecord 钱包余额记录
type WalletBalanceRecord struct {
ID string `bson:"_id"` // 记录ID
UserID string `bson:"user_id"` // 用户ID
Amount int64 `bson:"amount"` // 变动金额(单位:分,正数表示增加,负数表示减少)
Type int32 `bson:"type"` // 变动类型1-充值2-提现/提款3-消费4-退款5-奖励6-后台充值7-发红包8-抢红包99-其他
BeforeBalance int64 `bson:"before_balance"` // 变动前余额(单位:分)
AfterBalance int64 `bson:"after_balance"` // 变动后余额(单位:分)
OrderID string `bson:"order_id"` // 关联订单ID可选
TransactionID string `bson:"transaction_id"` // 交易ID可选
RedPacketID string `bson:"red_packet_id"` // 红包ID用于发红包和抢红包记录关联可选
Remark string `bson:"remark"` // 备注
CreateTime time.Time `bson:"create_time"` // 创建时间
}
func (WalletBalanceRecord) TableName() string {
return "wallet_balance_records"
}
type WalletBalanceRecordInterface interface {
Create(ctx context.Context, records ...*WalletBalanceRecord) error
Take(ctx context.Context, recordID string) (*WalletBalanceRecord, error)
FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*WalletBalanceRecord, error)
FindByUserIDAndType(ctx context.Context, userID string, recordType int32, pagination pagination.Pagination) (int64, []*WalletBalanceRecord, error)
FindByOrderID(ctx context.Context, orderID string) (*WalletBalanceRecord, error)
FindByTransactionID(ctx context.Context, transactionID string) (*WalletBalanceRecord, error)
FindByRedPacketID(ctx context.Context, redPacketID string) ([]*WalletBalanceRecord, error)
GetUserBalanceHistory(ctx context.Context, userID string, startTime, endTime *time.Time, pagination pagination.Pagination) (int64, []*WalletBalanceRecord, error)
CountByUserID(ctx context.Context, userID string) (int64, error)
}

View File

@@ -0,0 +1,63 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// WithdrawStatus 提现状态
const (
WithdrawStatusPending = 1 // 待审核
WithdrawStatusApproved = 2 // 已通过
WithdrawStatusRejected = 3 // 已拒绝
)
// Withdraw 提现记录
type Withdraw struct {
ID string `bson:"_id"` // 提现ID
UserID string `bson:"user_id"` // 用户ID
Amount int64 `bson:"amount"` // 提现金额(单位:分)
WithdrawAccount string `bson:"withdraw_account"` // 提现账号
Status int32 `bson:"status"` // 审核状态1-待审核2-已通过3-已拒绝
AuditorID string `bson:"auditor_id"` // 审核人ID管理员ID
AuditTime time.Time `bson:"audit_time"` // 审核时间
AuditRemark string `bson:"audit_remark"` // 审核备注
IP string `bson:"ip"` // 提现IP
DeviceID string `bson:"device_id"` // 设备ID
Platform string `bson:"platform"` // 平台iOS、Android、Web等
DeviceModel string `bson:"device_model"` // 设备型号iPhone 14 Pro、Samsung Galaxy S23等
DeviceBrand string `bson:"device_brand"` // 设备品牌Apple、Samsung、Huawei等
OSVersion string `bson:"os_version"` // 操作系统版本iOS 17.0、Android 13等
AppVersion string `bson:"app_version"` // 应用版本
CreateTime time.Time `bson:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time"` // 更新时间
}
func (Withdraw) TableName() string {
return "withdraws"
}
type WithdrawInterface interface {
Create(ctx context.Context, withdraws ...*Withdraw) error
Take(ctx context.Context, withdrawID string) (*Withdraw, error)
FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*Withdraw, error)
FindByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*Withdraw, error)
UpdateStatus(ctx context.Context, withdrawID string, status int32, auditorID string, auditRemark string) error
Page(ctx context.Context, pagination pagination.Pagination) (int64, []*Withdraw, error)
}

View File

@@ -0,0 +1,68 @@
// Copyright © 2023 OpenIM open source community. 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 chat
import (
"context"
"time"
"github.com/openimsdk/tools/db/pagination"
)
// WithdrawApplicationStatus 提现申请状态
const (
WithdrawApplicationStatusPending = 1 // 待审核
WithdrawApplicationStatusApproved = 2 // 已通过
WithdrawApplicationStatusRejected = 3 // 已拒绝
WithdrawApplicationStatusProcessing = 4 // 处理中
WithdrawApplicationStatusCompleted = 5 // 已完成
)
// WithdrawApplication 提现申请
type WithdrawApplication struct {
ID string `bson:"_id"` // 申请ID
UserID string `bson:"user_id"` // 用户ID
Amount int64 `bson:"amount"` // 提现金额(单位:分)
WithdrawAccount string `bson:"withdraw_account"` // 提现账号
WithdrawAccountType int32 `bson:"withdraw_account_type"` // 提现账号类型1-支付宝2-微信3-银行卡
Status int32 `bson:"status"` // 申请状态1-待审核2-已通过3-已拒绝4-处理中5-已完成
AuditorID string `bson:"auditor_id"` // 审核人ID管理员ID
AuditTime time.Time `bson:"audit_time"` // 审核时间
AuditRemark string `bson:"audit_remark"` // 审核备注
IP string `bson:"ip"` // 申请IP
DeviceID string `bson:"device_id"` // 设备ID
Platform string `bson:"platform"` // 平台iOS、Android、Web等
DeviceModel string `bson:"device_model"` // 设备型号iPhone 14 Pro、Samsung Galaxy S23等
DeviceBrand string `bson:"device_brand"` // 设备品牌Apple、Samsung、Huawei等
OSVersion string `bson:"os_version"` // 操作系统版本iOS 17.0、Android 13等
AppVersion string `bson:"app_version"` // 应用版本
Remark string `bson:"remark"` // 申请备注
CreateTime time.Time `bson:"create_time"` // 创建时间
UpdateTime time.Time `bson:"update_time"` // 更新时间
}
func (WithdrawApplication) TableName() string {
return "withdraw_applications"
}
type WithdrawApplicationInterface interface {
Create(ctx context.Context, applications ...*WithdrawApplication) error
Take(ctx context.Context, applicationID string) (*WithdrawApplication, error)
FindByUserID(ctx context.Context, userID string, pagination pagination.Pagination) (int64, []*WithdrawApplication, error)
FindByStatus(ctx context.Context, status int32, pagination pagination.Pagination) (int64, []*WithdrawApplication, error)
UpdateStatus(ctx context.Context, applicationID string, status int32, auditorID string, auditRemark string) error
Page(ctx context.Context, pagination pagination.Pagination) (int64, []*WithdrawApplication, error)
Update(ctx context.Context, applicationID string, data map[string]any) error
}

53
pkg/common/imapi/api.go Normal file
View File

@@ -0,0 +1,53 @@
// Copyright © 2023 OpenIM open source community. 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 imapi
import (
"git.imall.cloud/openim/protocol/auth"
"git.imall.cloud/openim/protocol/group"
"git.imall.cloud/openim/protocol/relation"
"git.imall.cloud/openim/protocol/user"
)
// im caller.
var (
getAdminToken = NewApiCaller[auth.GetAdminTokenReq, auth.GetAdminTokenResp]("/auth/get_admin_token")
getuserToken = NewApiCaller[auth.GetUserTokenReq, auth.GetUserTokenResp]("/auth/get_user_token")
forceOffLine = NewApiCaller[auth.ForceLogoutReq, auth.ForceLogoutResp]("/auth/force_logout")
updateUserInfo = NewApiCaller[user.UpdateUserInfoReq, user.UpdateUserInfoResp]("/user/update_user_info")
updateUserInfoEx = NewApiCaller[user.UpdateUserInfoExReq, user.UpdateUserInfoExResp]("/user/update_user_info_ex")
registerUser = NewApiCaller[user.UserRegisterReq, user.UserRegisterResp]("/user/user_register")
getUserInfo = NewApiCaller[user.GetDesignateUsersReq, user.GetDesignateUsersResp]("/user/get_users_info")
accountCheck = NewApiCaller[user.AccountCheckReq, user.AccountCheckResp]("/user/account_check")
addNotificationAccount = NewApiCaller[user.AddNotificationAccountReq, user.AddNotificationAccountResp]("/user/add_notification_account")
updateNotificationAccount = NewApiCaller[user.UpdateNotificationAccountInfoReq, user.UpdateNotificationAccountInfoResp]("/user/update_notification_account")
getGroupsInfo = NewApiCaller[group.GetGroupsInfoReq, group.GetGroupsInfoResp]("/group/get_groups_info")
inviteToGroup = NewApiCaller[group.InviteUserToGroupReq, group.InviteUserToGroupResp]("/group/invite_user_to_group")
registerUserCount = NewApiCaller[user.UserRegisterCountReq, user.UserRegisterCountResp]("/statistics/user/register")
onlineUserCount = NewApiCaller[OnlineUserCountReq, OnlineUserCountResp]("/statistics/online_user_count")
onlineUserCountTrend = NewApiCaller[OnlineUserCountTrendReq, OnlineUserCountTrendResp]("/statistics/online_user_count_trend")
userSendMsgCount = NewApiCaller[UserSendMsgCountReq, UserSendMsgCountResp]("/statistics/user_send_msg_count")
userSendMsgCountTrend = NewApiCaller[UserSendMsgCountTrendReq, UserSendMsgCountTrendResp]("/statistics/user_send_msg_count_trend")
userSendMsgQuery = NewApiCaller[UserSendMsgQueryReq, UserSendMsgQueryResp]("/statistics/user_send_msg_query")
friendUserIDs = NewApiCaller[relation.GetFriendIDsReq, relation.GetFriendIDsResp]("/friend/get_friend_id")
importFriend = NewApiCaller[relation.ImportFriendReq, relation.ImportFriendResp]("/friend/import_friend")
sendSimpleMsg = NewApiCaller[SendSingleMsgReq, SendSingleMsgResp]("/msg/send_simple_msg")
)

160
pkg/common/imapi/call.go Normal file
View File

@@ -0,0 +1,160 @@
// Copyright © 2023 OpenIM open source community. 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 imapi
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"time"
"git.imall.cloud/openim/chat/pkg/common/constant"
constantpb "git.imall.cloud/openim/protocol/constant"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"gorm.io/gorm/utils"
)
type baseApiResponse[T any] struct {
ErrCode int `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
Data *T `json:"data"`
}
var client = &http.Client{
Timeout: time.Second * 10,
}
type ApiCaller[Req, Resp any] interface {
Call(ctx context.Context, apiPrefix string, req *Req) (*Resp, error)
CallWithQuery(ctx context.Context, apiPrefix string, req *Req, queryParams map[string]string) (*Resp, error)
}
func NewApiCaller[Req, Resp any](api string) ApiCaller[Req, Resp] {
return &caller[Req, Resp]{
api: api,
}
}
type caller[Req, Resp any] struct {
api string
}
func (a caller[Req, Resp]) Call(ctx context.Context, apiPrefix string, req *Req) (*Resp, error) {
start := time.Now()
resp, err := a.call(ctx, apiPrefix, req)
if err != nil {
log.ZError(ctx, "api caller failed", err, "api", a.api, "duration", time.Since(start), "req", req, "resp", resp)
return nil, err
}
log.ZInfo(ctx, "api caller success resp", "api", a.api, "duration", time.Since(start), "req", req, "resp", resp)
return resp, nil
}
func (a caller[Req, Resp]) call(ctx context.Context, apiPrefix string, req *Req) (*Resp, error) {
url := apiPrefix + a.api
reqBody, err := json.Marshal(req)
if err != nil {
return nil, err
}
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
operationID := utils.ToString(ctx.Value(constantpb.OperationID))
request.Header.Set(constantpb.OperationID, operationID)
if token, _ := ctx.Value(constant.CtxApiToken).(string); token != "" {
request.Header.Set(constantpb.Token, token)
}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
data, err := io.ReadAll(response.Body)
if err != nil {
return nil, errs.WrapMsg(err, "read http response body", "url", url, "code", response.StatusCode)
}
var resp baseApiResponse[Resp]
if err := json.Unmarshal(data, &resp); err != nil {
return nil, errs.WrapMsg(err, string(data))
}
if resp.ErrCode != 0 {
return nil, errs.NewCodeError(resp.ErrCode, resp.ErrMsg).WithDetail(resp.ErrDlt).Wrap()
}
return resp.Data, nil
}
func (a caller[Req, Resp]) CallWithQuery(ctx context.Context, apiPrefix string, req *Req, queryParams map[string]string) (*Resp, error) {
start := time.Now()
resp, err := a.callWithQuery(ctx, apiPrefix, req, queryParams)
if err != nil {
log.ZError(ctx, "api caller failed", err, "api", a.api, "duration", time.Since(start), "req", req, "resp", resp)
return nil, err
}
log.ZInfo(ctx, "api caller success resp", "api", a.api, "duration", time.Since(start), "req", req, "resp", resp)
return resp, nil
}
func (a caller[Req, Resp]) callWithQuery(ctx context.Context, apiPrefix string, req *Req, queryParams map[string]string) (*Resp, error) {
fullURL := apiPrefix + a.api
parsedURL, err := url.Parse(fullURL)
if err != nil {
return nil, errs.WrapMsg(err, "failed to parse URL", fullURL)
}
query := parsedURL.Query()
for key, value := range queryParams {
query.Set(key, value)
}
parsedURL.RawQuery = query.Encode()
fullURL = parsedURL.String()
reqBody, err := json.Marshal(req)
if err != nil {
return nil, err
}
request, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURL, bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
operationID := utils.ToString(ctx.Value(constantpb.OperationID))
request.Header.Set(constantpb.OperationID, operationID)
if token, _ := ctx.Value(constant.CtxApiToken).(string); token != "" {
request.Header.Set(constantpb.Token, token)
}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
data, err := io.ReadAll(response.Body)
if err != nil {
return nil, errs.WrapMsg(err, "read http response body", "fullUrl", fullURL, "code", response.StatusCode)
}
var resp baseApiResponse[Resp]
if err := json.Unmarshal(data, &resp); err != nil {
return nil, errs.WrapMsg(err, string(data))
}
if resp.ErrCode != 0 {
return nil, errs.NewCodeError(resp.ErrCode, resp.ErrMsg).WithDetail(resp.ErrDlt).Wrap()
}
return resp.Data, nil
}

281
pkg/common/imapi/caller.go Normal file
View File

@@ -0,0 +1,281 @@
package imapi
import (
"context"
"sync"
"time"
"git.imall.cloud/openim/chat/pkg/botstruct"
"git.imall.cloud/openim/chat/pkg/eerrs"
"git.imall.cloud/openim/protocol/auth"
"git.imall.cloud/openim/protocol/constant"
"git.imall.cloud/openim/protocol/group"
"git.imall.cloud/openim/protocol/relation"
"git.imall.cloud/openim/protocol/sdkws"
"git.imall.cloud/openim/protocol/user"
wrapperspb "git.imall.cloud/openim/protocol/wrapperspb"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
)
type CallerInterface interface {
ImAdminTokenWithDefaultAdmin(ctx context.Context) (string, error)
ImportFriend(ctx context.Context, ownerUserID string, friendUserID []string) error
GetUserToken(ctx context.Context, userID string, platform int32) (string, error)
GetAdminTokenCache(ctx context.Context, userID string) (string, error)
GetAdminTokenServer(ctx context.Context, userID string) (string, error)
InviteToGroup(ctx context.Context, userID string, groupIDs []string) error
UpdateUserInfo(ctx context.Context, userID string, nickName string, faceURL string, userType int32, userFlag string) error
UpdateUserInfoEx(ctx context.Context, userID string, ex string) error
GetUserInfo(ctx context.Context, userID string) (*sdkws.UserInfo, error)
GetUsersInfo(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error)
AddNotificationAccount(ctx context.Context, req *user.AddNotificationAccountReq) error
UpdateNotificationAccount(ctx context.Context, req *user.UpdateNotificationAccountInfoReq) error
ForceOffLine(ctx context.Context, userID string) error
RegisterUser(ctx context.Context, users []*sdkws.UserInfo) error
FindGroupInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error)
UserRegisterCount(ctx context.Context, start int64, end int64) (map[string]int64, int64, error)
OnlineUserCount(ctx context.Context) (*OnlineUserCountResp, error)
OnlineUserCountTrend(ctx context.Context, req *OnlineUserCountTrendReq) (*OnlineUserCountTrendResp, error)
UserSendMsgCount(ctx context.Context, req *UserSendMsgCountReq) (*UserSendMsgCountResp, error)
UserSendMsgCountTrend(ctx context.Context, req *UserSendMsgCountTrendReq) (*UserSendMsgCountTrendResp, error)
UserSendMsgQuery(ctx context.Context, req *UserSendMsgQueryReq) (*UserSendMsgQueryResp, error)
FriendUserIDs(ctx context.Context, userID string) ([]string, error)
AccountCheckSingle(ctx context.Context, userID string) (bool, error)
SendSimpleMsg(ctx context.Context, req *SendSingleMsgReq, key string) error
}
type authToken struct {
token string
expired time.Time
}
type Caller struct {
imApi string
imSecret string
defaultIMUserID string
tokenCache map[string]*authToken
lock sync.RWMutex
}
func New(imApi string, imSecret string, defaultIMUserID string) CallerInterface {
return &Caller{
imApi: imApi,
imSecret: imSecret,
defaultIMUserID: defaultIMUserID,
tokenCache: make(map[string]*authToken),
lock: sync.RWMutex{},
}
}
func (c *Caller) ImportFriend(ctx context.Context, ownerUserID string, friendUserIDs []string) error {
if len(friendUserIDs) == 0 {
return nil
}
_, err := importFriend.Call(ctx, c.imApi, &relation.ImportFriendReq{
OwnerUserID: ownerUserID,
FriendUserIDs: friendUserIDs,
})
return err
}
func (c *Caller) ImAdminTokenWithDefaultAdmin(ctx context.Context) (string, error) {
return c.GetAdminTokenCache(ctx, c.defaultIMUserID)
}
func (c *Caller) GetAdminTokenCache(ctx context.Context, userID string) (string, error) {
c.lock.RLock()
t, ok := c.tokenCache[userID]
c.lock.RUnlock()
if ok && t.expired.After(time.Now()) {
return t.token, nil
}
c.lock.Lock()
defer c.lock.Unlock()
t, ok = c.tokenCache[userID]
if ok && t.expired.After(time.Now()) {
return t.token, nil
}
token, err := c.GetAdminTokenServer(ctx, userID)
if err != nil {
return "", err
}
c.tokenCache[userID] = &authToken{token: token, expired: time.Now().Add(time.Minute * 4)}
return token, nil
}
func (c *Caller) GetAdminTokenServer(ctx context.Context, userID string) (string, error) {
resp, err := getAdminToken.Call(ctx, c.imApi, &auth.GetAdminTokenReq{
Secret: c.imSecret,
UserID: userID,
})
if err != nil {
return "", err
}
log.ZDebug(ctx, "get im admin token from server", "userID", userID, "token", resp.Token)
return resp.Token, nil
}
func (c *Caller) GetUserToken(ctx context.Context, userID string, platformID int32) (string, error) {
resp, err := getuserToken.Call(ctx, c.imApi, &auth.GetUserTokenReq{
PlatformID: platformID,
UserID: userID,
})
if err != nil {
return "", err
}
return resp.Token, nil
}
func (c *Caller) InviteToGroup(ctx context.Context, userID string, groupIDs []string) error {
for _, groupID := range groupIDs {
_, _ = inviteToGroup.Call(ctx, c.imApi, &group.InviteUserToGroupReq{
GroupID: groupID,
Reason: "",
InvitedUserIDs: []string{userID},
})
}
return nil
}
func (c *Caller) UpdateUserInfo(ctx context.Context, userID string, nickName string, faceURL string, userType int32, userFlag string) error {
_, err := updateUserInfo.Call(ctx, c.imApi, &user.UpdateUserInfoReq{UserInfo: &sdkws.UserInfo{
UserID: userID,
Nickname: nickName,
FaceURL: faceURL,
UserType: userType,
UserFlag: userFlag,
}})
return err
}
func (c *Caller) UpdateUserInfoEx(ctx context.Context, userID string, ex string) error {
_, err := updateUserInfoEx.Call(ctx, c.imApi, &user.UpdateUserInfoExReq{
UserInfo: &sdkws.UserInfoWithEx{
UserID: userID,
Ex: &wrapperspb.StringValue{Value: ex},
},
})
return err
}
func (c *Caller) GetUserInfo(ctx context.Context, userID string) (*sdkws.UserInfo, error) {
resp, err := c.GetUsersInfo(ctx, []string{userID})
if err != nil {
return nil, err
}
if len(resp) == 0 {
return nil, errs.ErrRecordNotFound.WrapMsg("record not found")
}
return resp[0], nil
}
func (c *Caller) GetUsersInfo(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error) {
resp, err := getUserInfo.Call(ctx, c.imApi, &user.GetDesignateUsersReq{
UserIDs: userIDs,
})
if err != nil {
return nil, err
}
return resp.UsersInfo, nil
}
func (c *Caller) RegisterUser(ctx context.Context, users []*sdkws.UserInfo) error {
_, err := registerUser.Call(ctx, c.imApi, &user.UserRegisterReq{
Users: users,
})
return err
}
func (c *Caller) ForceOffLine(ctx context.Context, userID string) error {
for id := range constant.PlatformID2Name {
_, _ = forceOffLine.Call(ctx, c.imApi, &auth.ForceLogoutReq{
PlatformID: int32(id),
UserID: userID,
})
}
return nil
}
func (c *Caller) FindGroupInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) {
resp, err := getGroupsInfo.Call(ctx, c.imApi, &group.GetGroupsInfoReq{
GroupIDs: groupIDs,
})
if err != nil {
return nil, err
}
return resp.GroupInfos, nil
}
func (c *Caller) UserRegisterCount(ctx context.Context, start int64, end int64) (map[string]int64, int64, error) {
resp, err := registerUserCount.Call(ctx, c.imApi, &user.UserRegisterCountReq{
Start: start,
End: end,
})
if err != nil {
return nil, 0, err
}
return resp.Count, resp.Total, nil
}
// OnlineUserCount 获取在线人数统计
func (c *Caller) OnlineUserCount(ctx context.Context) (*OnlineUserCountResp, error) {
return onlineUserCount.Call(ctx, c.imApi, &OnlineUserCountReq{})
}
// OnlineUserCountTrend 获取在线人数走势统计
func (c *Caller) OnlineUserCountTrend(ctx context.Context, req *OnlineUserCountTrendReq) (*OnlineUserCountTrendResp, error) {
return onlineUserCountTrend.Call(ctx, c.imApi, req)
}
// UserSendMsgCount 获取用户发送消息统计
func (c *Caller) UserSendMsgCount(ctx context.Context, req *UserSendMsgCountReq) (*UserSendMsgCountResp, error) {
return userSendMsgCount.Call(ctx, c.imApi, req)
}
// UserSendMsgCountTrend 获取用户发送消息走势统计
func (c *Caller) UserSendMsgCountTrend(ctx context.Context, req *UserSendMsgCountTrendReq) (*UserSendMsgCountTrendResp, error) {
return userSendMsgCountTrend.Call(ctx, c.imApi, req)
}
// UserSendMsgQuery 获取用户发送消息查询列表
func (c *Caller) UserSendMsgQuery(ctx context.Context, req *UserSendMsgQueryReq) (*UserSendMsgQueryResp, error) {
return userSendMsgQuery.Call(ctx, c.imApi, req)
}
func (c *Caller) FriendUserIDs(ctx context.Context, userID string) ([]string, error) {
resp, err := friendUserIDs.Call(ctx, c.imApi, &relation.GetFriendIDsReq{UserID: userID})
if err != nil {
return nil, err
}
return resp.FriendIDs, nil
}
// return true when isUserNotExist.
func (c *Caller) AccountCheckSingle(ctx context.Context, userID string) (bool, error) {
resp, err := accountCheck.Call(ctx, c.imApi, &user.AccountCheckReq{CheckUserIDs: []string{userID}})
if err != nil {
return false, err
}
if resp.Results[0].AccountStatus == constant.Registered {
return false, eerrs.ErrAccountAlreadyRegister.Wrap()
}
return true, nil
}
func (c *Caller) SendSimpleMsg(ctx context.Context, req *SendSingleMsgReq, key string) error {
_, err := sendSimpleMsg.CallWithQuery(ctx, c.imApi, req, map[string]string{botstruct.Key: key})
return err
}
func (c *Caller) AddNotificationAccount(ctx context.Context, req *user.AddNotificationAccountReq) error {
_, err := addNotificationAccount.Call(ctx, c.imApi, req)
return err
}
func (c *Caller) UpdateNotificationAccount(ctx context.Context, req *user.UpdateNotificationAccountInfoReq) error {
_, err := updateNotificationAccount.Call(ctx, c.imApi, req)
return err
}

139
pkg/common/imapi/model.go Normal file
View File

@@ -0,0 +1,139 @@
package imapi
import "git.imall.cloud/openim/protocol/sdkws"
// 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 SendSingleMsgResp struct{}
// OnlineUserCountReq 在线人数统计请求
type OnlineUserCountReq struct{}
// OnlineUserCountResp 在线人数统计返回
type OnlineUserCountResp struct {
OnlineCount int64 `json:"onlineCount"`
}
// OnlineUserCountTrendReq 在线人数走势统计请求
type OnlineUserCountTrendReq struct {
// StartTime 统计开始时间毫秒时间戳为空时默认最近24小时
StartTime int64 `json:"startTime"`
// EndTime 统计结束时间(毫秒时间戳),为空时默认当前时间
EndTime int64 `json:"endTime"`
// IntervalMinutes 统计间隔分钟仅支持15/30/60
IntervalMinutes int32 `json:"intervalMinutes"`
}
// 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"`
}
// 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"`
}
// UserSendMsgCountTrendReq 用户发送消息走势统计请求
type UserSendMsgCountTrendReq struct {
// UserID 发送者用户ID
UserID string `json:"userID"`
// ChatType 聊天类型1-单聊2-群聊
ChatType int32 `json:"chatType"`
// StartTime 统计开始时间毫秒时间戳为空时默认最近24小时
StartTime int64 `json:"startTime"`
// EndTime 统计结束时间(毫秒时间戳),为空时默认当前时间
EndTime int64 `json:"endTime"`
// IntervalMinutes 统计间隔分钟仅支持15/30/60
IntervalMinutes int32 `json:"intervalMinutes"`
}
// 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"`
}
// 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"`
}

View File

@@ -0,0 +1,48 @@
package imwebhook
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"`
}
type CommonCallbackResp struct {
ActionCode int32 `json:"actionCode"`
ErrCode int32 `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
NextCode int32 `json:"nextCode"`
}
type CallbackAfterSendSingleMsgReq struct {
CommonCallbackReq
RecvID string `json:"recvID"`
}
type CallbackAfterSendSingleMsgResp struct {
CommonCallbackResp
}
type CallbackAfterSendGroupMsgReq struct {
CommonCallbackReq
GroupID string `json:"groupID"`
}
type CallbackAfterSendGroupMsgResp struct {
CommonCallbackResp
}

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 "github.com/openimsdk/chat-deploy/v3/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 "github.com/openimsdk/chat-deploy/v3/pkg/common/discoveryregister/direct"

View File

@@ -0,0 +1,51 @@
// 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 kdisc
import (
"time"
"git.imall.cloud/openim/chat/pkg/common/config"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/discovery/kubernetes"
"github.com/openimsdk/tools/errs"
)
const (
ETCDCONST = "etcd"
KUBERNETESCONST = "kubernetes"
DIRECTCONST = "direct"
)
// NewDiscoveryRegister creates a new service discovery and registry client based on the provided environment type.
func NewDiscoveryRegister(discovery *config.Discovery, runtimeEnv string, watchNames []string) (discovery.SvcDiscoveryRegistry, error) {
if runtimeEnv == KUBERNETESCONST {
return kubernetes.NewKubernetesConnManager(discovery.Kubernetes.Namespace)
}
switch discovery.Enable {
case ETCDCONST:
return etcd.NewSvcDiscoveryRegistry(
discovery.Etcd.RootDirectory,
discovery.Etcd.Address,
watchNames,
etcd.WithDialTimeout(10*time.Second),
etcd.WithMaxCallSendMsgSize(20*1024*1024),
etcd.WithUsernameAndPassword(discovery.Etcd.Username, discovery.Etcd.Password))
default:
return nil, errs.New("unsupported discovery type", "type", discovery.Enable).Wrap()
}
}

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 = "/chat/config/"
RestartKey = "restart"
EnableConfigCenterKey = "enable-config-center"
Enable = "enable"
Disable = "disable"
)

136
pkg/common/mctx/get.go Normal file
View File

@@ -0,0 +1,136 @@
// Copyright © 2023 OpenIM open source community. 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 mctx
import (
"context"
"strconv"
"github.com/openimsdk/tools/utils/datautil"
constantpb "git.imall.cloud/openim/protocol/constant"
"github.com/openimsdk/tools/errs"
"git.imall.cloud/openim/chat/pkg/common/constant"
"git.imall.cloud/openim/chat/pkg/common/tokenverify"
)
func HaveOpUser(ctx context.Context) bool {
return ctx.Value(constant.RpcOpUserID) != nil
}
func Check(ctx context.Context) (string, int32, error) {
opUserIDVal := ctx.Value(constant.RpcOpUserID)
opUserID, ok := opUserIDVal.(string)
if !ok {
return "", 0, errs.ErrNoPermission.WrapMsg("no opUserID")
}
if opUserID == "" {
return "", 0, errs.ErrNoPermission.WrapMsg("opUserID empty")
}
opUserTypeArr, ok := ctx.Value(constant.RpcOpUserType).([]string)
if !ok {
return "", 0, errs.ErrNoPermission.WrapMsg("missing user type")
}
if len(opUserTypeArr) == 0 {
return "", 0, errs.ErrNoPermission.WrapMsg("user type empty")
}
userType, err := strconv.Atoi(opUserTypeArr[0])
if err != nil {
return "", 0, errs.ErrNoPermission.WrapMsg("user type invalid " + err.Error())
}
if !(userType == constant.AdminUser || userType == constant.NormalUser) {
return "", 0, errs.ErrNoPermission.WrapMsg("user type invalid")
}
return opUserID, int32(userType), nil
}
func CheckAdmin(ctx context.Context) (string, error) {
userID, userType, err := Check(ctx)
if err != nil {
return "", err
}
if userType != constant.AdminUser {
return "", errs.ErrNoPermission.WrapMsg("not admin")
}
return userID, nil
}
func CheckUser(ctx context.Context) (string, error) {
userID, userType, err := Check(ctx)
if err != nil {
return "", err
}
if userType != constant.NormalUser {
return "", errs.ErrNoPermission.WrapMsg("not user")
}
return userID, nil
}
func CheckAdminOrUser(ctx context.Context) (string, int32, error) {
userID, userType, err := Check(ctx)
if err != nil {
return "", 0, err
}
return userID, userType, nil
}
func CheckAdminOr(ctx context.Context, userIDs ...string) error {
userID, userType, err := Check(ctx)
if err != nil {
return err
}
if userType == tokenverify.TokenAdmin {
return nil
}
for _, id := range userIDs {
if userID == id {
return nil
}
}
return errs.ErrNoPermission.WrapMsg("not admin or not in userIDs")
}
func GetOpUserID(ctx context.Context) string {
userID, _ := ctx.Value(constantpb.OpUserID).(string)
return userID
}
func GetUserType(ctx context.Context) (int, error) {
userTypeArr, _ := ctx.Value(constant.RpcOpUserType).([]string)
userType, err := strconv.Atoi(userTypeArr[0])
if err != nil {
return 0, errs.ErrNoPermission.WrapMsg("user type invalid " + err.Error())
}
return userType, nil
}
func WithOpUserID(ctx context.Context, opUserID string, userType int) context.Context {
headers, _ := ctx.Value(constant.RpcCustomHeader).([]string)
ctx = context.WithValue(ctx, constant.RpcOpUserID, opUserID)
ctx = context.WithValue(ctx, constant.RpcOpUserType, []string{strconv.Itoa(userType)})
if datautil.IndexOf(constant.RpcOpUserType, headers...) < 0 {
ctx = context.WithValue(ctx, constant.RpcCustomHeader, append(headers, constant.RpcOpUserType))
}
return ctx
}
func WithAdminUser(ctx context.Context, userID string) context.Context {
return WithOpUserID(ctx, userID, constant.AdminUser)
}
func WithApiToken(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, constant.CtxApiToken, token)
}

57
pkg/common/mw/gin_log.go Normal file
View File

@@ -0,0 +1,57 @@
// Copyright © 2023 OpenIM open source community. 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 mw
import (
"bytes"
"io"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/openimsdk/tools/log"
)
type responseWriter struct {
gin.ResponseWriter
buf *bytes.Buffer
}
func (w *responseWriter) Write(b []byte) (int, error) {
w.buf.Write(b)
return w.ResponseWriter.Write(b)
}
func GinLog() gin.HandlerFunc {
return func(c *gin.Context) {
req, err := io.ReadAll(c.Request.Body)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
c.Abort()
return
}
start := time.Now()
log.ZDebug(c, "gin request", "method", c.Request.Method, "uri", c.Request.RequestURI, "req", string(req))
c.Request.Body = io.NopCloser(bytes.NewReader(req))
writer := &responseWriter{
ResponseWriter: c.Writer,
buf: bytes.NewBuffer(nil),
}
c.Writer = writer
c.Next()
resp := writer.buf.Bytes()
log.ZDebug(c, "gin response", "time", time.Since(start), "status", c.Writer.Status(), "resp", string(resp))
}
}

36
pkg/common/mw/user.go Normal file
View File

@@ -0,0 +1,36 @@
// Copyright © 2023 OpenIM open source community. 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 mw
import (
"context"
"git.imall.cloud/openim/chat/pkg/common/constant"
"github.com/openimsdk/tools/log"
"google.golang.org/grpc"
)
func AddUserType() grpc.DialOption {
return grpc.WithChainUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
log.ZInfo(ctx, "add user type", "method", method)
if arr, _ := ctx.Value(constant.RpcOpUserType).([]string); len(arr) > 0 {
log.ZInfo(ctx, "add user type", "method", method, "userType", arr)
headers, _ := ctx.Value(constant.RpcCustomHeader).([]string)
ctx = context.WithValue(ctx, constant.RpcCustomHeader, append(headers, constant.RpcOpUserType))
ctx = context.WithValue(ctx, constant.RpcOpUserType, arr)
}
return invoker(ctx, method, req, reply, cc, opts...)
})
}

30
pkg/common/rtc/rtc.go Normal file
View File

@@ -0,0 +1,30 @@
package rtc
import (
"github.com/livekit/protocol/auth"
"time"
)
func NewLiveKit(key, secret, url string) *LiveKit {
return &LiveKit{
token: auth.NewAccessToken(key, secret),
url: url,
}
}
type LiveKit struct {
token *auth.AccessToken
url string
}
func (l *LiveKit) GetLiveKitURL() string {
return l.url
}
func (l *LiveKit) GetLiveKitToken(room string, identity string) (string, error) {
grant := &auth.VideoGrant{
RoomJoin: true,
Room: room,
}
return l.token.AddGrant(grant).SetIdentity(identity).SetValidFor(time.Hour).ToJWT()
}

View File

@@ -0,0 +1,129 @@
package startrpc
import (
"context"
"fmt"
"net"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
"git.imall.cloud/openim/chat/pkg/common/config"
"git.imall.cloud/openim/chat/pkg/common/kdisc"
disetcd "git.imall.cloud/openim/chat/pkg/common/kdisc/etcd"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/runtimeenv"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/system/program"
"github.com/openimsdk/tools/utils/network"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// Start rpc server.
func Start[T any](ctx context.Context, discovery *config.Discovery, listenIP,
registerIP string, rpcPorts []int, index int, rpcRegisterName string, share *config.Share, config T,
watchConfigNames []string, watchServiceNames []string,
rpcFn func(ctx context.Context, config T, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error, options ...grpc.ServerOption) error {
runtimeEnv := runtimeenv.PrintRuntimeEnvironment()
rpcPort, err := datautil.GetElemByIndex(rpcPorts, index)
if err != nil {
return err
}
log.CInfo(ctx, "RPC server is initializing", " runtimeEnv ", runtimeEnv, "rpcRegisterName", rpcRegisterName, "rpcPort", rpcPort)
rpcTcpAddr := net.JoinHostPort(network.GetListenIP(listenIP), strconv.Itoa(rpcPort))
listener, err := net.Listen(
"tcp",
rpcTcpAddr,
)
if err != nil {
return errs.WrapMsg(err, "listen err", "rpcTcpAddr", rpcTcpAddr)
}
defer listener.Close()
client, err := kdisc.NewDiscoveryRegister(discovery, runtimeEnv, watchServiceNames)
if err != nil {
return err
}
defer client.Close()
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
registerIP, err = network.GetRpcRegisterIP(registerIP)
if err != nil {
return err
}
options = append(options, mw.GrpcServer())
srv := grpc.NewServer(options...)
once := sync.Once{}
defer func() {
once.Do(srv.GracefulStop)
}()
err = rpcFn(ctx, config, client, srv)
if err != nil {
return err
}
if err := client.Register(rpcRegisterName, registerIP, rpcPort, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
return err
}
var (
netDone = make(chan struct{}, 2)
netErr error
)
go func() {
err := srv.Serve(listener)
if err != nil {
netErr = errs.WrapMsg(err, "rpc start err: ", rpcTcpAddr)
netDone <- struct{}{}
}
}()
if discovery.Enable == kdisc.ETCDCONST {
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), watchConfigNames)
cm.Watch(ctx)
}
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM)
select {
case <-sigs:
program.SIGTERMExit()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := gracefulStopWithCtx(ctx, srv.GracefulStop); err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
return nil
case <-netDone:
close(netDone)
return netErr
}
}
func gracefulStopWithCtx(ctx context.Context, f func()) error {
done := make(chan struct{}, 1)
go func() {
f()
close(done)
}()
select {
case <-ctx.Done():
return errs.New("timeout, ctx graceful stop")
case <-done:
return nil
}
}

View File

@@ -0,0 +1,132 @@
// Copyright © 2023 OpenIM open source community. 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 tokenverify
import (
"time"
"git.imall.cloud/openim/chat/pkg/common/constant"
"github.com/golang-jwt/jwt/v4"
"github.com/openimsdk/tools/errs"
)
const (
TokenUser = constant.NormalUser
TokenAdmin = constant.AdminUser
)
type claims struct {
UserID string
UserType int32
PlatformID int32
jwt.RegisteredClaims
}
type Token struct {
Expires time.Duration
Secret string
}
func (t *Token) secret() jwt.Keyfunc {
return func(token *jwt.Token) (any, error) {
return []byte(t.Secret), nil
}
}
func (t *Token) buildClaims(userID string, userType int32) claims {
now := time.Now()
return claims{
UserID: userID,
UserType: userType,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(t.Expires)), // Expiration time
IssuedAt: jwt.NewNumericDate(now), // Issuing time
NotBefore: jwt.NewNumericDate(now.Add(-time.Minute)), // Begin Effective time
},
}
}
func (t *Token) getToken(str string) (string, int32, error) {
token, err := jwt.ParseWithClaims(str, &claims{}, t.secret())
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return "", 0, errs.ErrTokenMalformed.Wrap()
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return "", 0, errs.ErrTokenExpired.Wrap()
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return "", 0, errs.ErrTokenNotValidYet.Wrap()
} else {
return "", 0, errs.ErrTokenUnknown.Wrap()
}
} else {
return "", 0, errs.ErrTokenNotValidYet.Wrap()
}
} else {
claims, ok := token.Claims.(*claims)
if claims.PlatformID != 0 {
return "", 0, errs.ErrTokenExpired.Wrap()
}
if ok && token.Valid {
return claims.UserID, claims.UserType, nil
}
return "", 0, errs.ErrTokenNotValidYet.Wrap()
}
}
func (t *Token) CreateToken(UserID string, userType int32) (string, time.Duration, error) {
if !(userType == TokenUser || userType == TokenAdmin) {
return "", 0, errs.ErrTokenUnknown.WrapMsg("token type unknown")
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, t.buildClaims(UserID, userType))
str, err := token.SignedString([]byte(t.Secret))
if err != nil {
return "", 0, errs.Wrap(err)
}
return str, t.Expires, nil
}
func (t *Token) GetToken(token string) (string, int32, error) {
userID, userType, err := t.getToken(token)
if err != nil {
return "", 0, err
}
if !(userType == TokenUser || userType == TokenAdmin) {
return "", 0, errs.ErrTokenUnknown.WrapMsg("token type unknown")
}
return userID, userType, nil
}
//func (t *Token) GetAdminTokenCache(token string) (string, error) {
// userID, userType, err := getToken(token)
// if err != nil {
// return "", err
// }
// if userType != TokenAdmin {
// return "", errs.ErrTokenUnknown.WrapMsg("token type error")
// }
// return userID, nil
//}
//
//func (t *Token) GetUserToken(token string) (string, error) {
// userID, userType, err := getToken(token)
// if err != nil {
// return "", err
// }
// if userType != TokenUser {
// return "", errs.ErrTokenUnknown.WrapMsg("token type error")
// }
// return userID, nil
//}

248
pkg/common/util/aes.go Normal file
View File

@@ -0,0 +1,248 @@
// Copyright © 2023 OpenIM open source community. 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 util
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"time"
"github.com/openimsdk/tools/errs"
)
const (
// AES密钥字符串
aesKeyString = "F9cLjK3FgkvcR6vaWZKEj3DYJfwrIHMULLwpmDAj553mpmlvMYoLNIqajLnhIsWq"
)
// DecryptVerifyCode 解密验证码
// encryptedText: Base64编码的加密字符串
// 返回: 解密后的明文(格式:验证码-时间戳)
func DecryptVerifyCode(encryptedText string) (string, error) {
// 解码Base64
encrypted, err := base64.StdEncoding.DecodeString(encryptedText)
if err != nil {
return "", errs.WrapMsg(err, "base64解码失败")
}
// 使用SHA-256哈希密钥字符串
keyBytes := sha256.Sum256([]byte(aesKeyString))
key := keyBytes[:]
// 创建AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", errs.WrapMsg(err, "创建AES cipher失败")
}
// 检查密文长度
if len(encrypted) < aes.BlockSize {
return "", errs.New("密文长度不足")
}
// 使用CBC模式固定IV全零
iv := make([]byte, aes.BlockSize) // 全零IV
mode := cipher.NewCBCDecrypter(block, iv)
// 解密
decrypted := make([]byte, len(encrypted))
mode.CryptBlocks(decrypted, encrypted)
// 去除PKCS7填充
decrypted = unpadPKCS7(decrypted)
return string(decrypted), nil
}
// unpadPKCS7 去除PKCS7填充
func unpadPKCS7(data []byte) []byte {
if len(data) == 0 {
return data
}
padLen := int(data[len(data)-1])
if padLen > len(data) || padLen == 0 {
return data
}
// 验证填充
for i := len(data) - padLen; i < len(data); i++ {
if data[i] != byte(padLen) {
return data
}
}
return data[:len(data)-padLen]
}
// ParseVerifyCodeWithTimestamp 解析验证码和时间戳
// format: "验证码-时间戳"
// 返回: 验证码字符串和时间戳(毫秒)
func ParseVerifyCodeWithTimestamp(decryptedText string) (string, int64, error) {
var code string
var timestamp int64
// 查找最后一个 "-" 分隔符
lastDashIndex := -1
for i := len(decryptedText) - 1; i >= 0; i-- {
if decryptedText[i] == '-' {
lastDashIndex = i
break
}
}
if lastDashIndex == -1 {
return "", 0, errs.New("格式错误:未找到时间戳分隔符")
}
code = decryptedText[:lastDashIndex]
timestampStr := decryptedText[lastDashIndex+1:]
// 解析时间戳
_, err := fmt.Sscanf(timestampStr, "%d", &timestamp)
if err != nil {
return "", 0, errs.WrapMsg(err, "解析时间戳失败")
}
return code, timestamp, nil
}
// ValidateTimestamp 验证时间戳是否在有效期内
// timestamp: 毫秒级时间戳
// maxAgeSeconds: 最大允许的秒数允许的时间差默认30秒
// 返回: 是否有效
func ValidateTimestamp(timestamp int64, maxAgeSeconds int64) bool {
if timestamp <= 0 {
return false
}
currentTime := getCurrentTimestamp()
diff := currentTime - timestamp
// 转换为毫秒
maxAgeMillis := maxAgeSeconds * 1000
// 时间戳不能是未来的允许5秒的时钟偏差
if diff < -5000 {
return false
}
// 时间差不能超过maxAgeSeconds秒
if diff > maxAgeMillis {
return false
}
return true
}
// getCurrentTimestamp 获取当前时间戳(毫秒)
func getCurrentTimestamp() int64 {
return time.Now().UnixMilli()
}
// padPKCS7 添加PKCS7填充
func padPKCS7(data []byte, blockSize int) []byte {
padLen := blockSize - len(data)%blockSize
pad := make([]byte, padLen)
for i := range pad {
pad[i] = byte(padLen)
}
return append(data, pad...)
}
// EncryptPhone 加密手机号
// phoneNumber: 手机号明文
// 返回: Base64编码的加密字符串
func EncryptPhone(phoneNumber string) (string, error) {
if phoneNumber == "" {
return "", errs.New("手机号不能为空")
}
// 使用SHA-256哈希密钥字符串
keyBytes := sha256.Sum256([]byte(aesKeyString))
key := keyBytes[:]
// 创建AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", errs.WrapMsg(err, "创建AES cipher失败")
}
// 使用CBC模式固定IV全零
iv := make([]byte, aes.BlockSize) // 全零IV
// 添加PKCS7填充
plaintext := padPKCS7([]byte(phoneNumber), aes.BlockSize)
// 加密
mode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(plaintext))
mode.CryptBlocks(encrypted, plaintext)
// 编码为Base64
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptPhone 解密手机号
// encryptedText: Base64编码的加密字符串
// 返回: 解密后的手机号明文
func DecryptPhone(encryptedText string) (string, error) {
// 复用DecryptVerifyCode的解密逻辑手机号没有时间戳格式直接解密
return DecryptVerifyCode(encryptedText)
}
// EncryptRealNameAuthData 加密实名认证数据(使用 AES-GCM 模式)
// data: JSON字符串例如: {"cardNo":"330329199001021122","realName":"李四"}
// key: AES密钥字符串与服务端相同的默认密钥
// 返回: Base64编码的加密字符串
func EncryptRealNameAuthData(data string, key string) (string, error) {
if data == "" {
return "", errs.New("数据不能为空")
}
if key == "" {
return "", errs.New("密钥不能为空")
}
// 使用 SHA256 哈希密钥字符串(与服务端保持一致)
// 注意:直接对密钥字符串进行哈希,而不是先转换为字节
hash := sha256.Sum256([]byte(key))
aesKey := hash[:]
// 创建AES cipher
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", errs.WrapMsg(err, "创建AES cipher失败")
}
// 使用 GCM 模式
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", errs.WrapMsg(err, "创建 GCM 失败")
}
// 生成随机 nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", errs.WrapMsg(err, "生成 nonce 失败")
}
// 加密
ciphertext := gcm.Seal(nonce, nonce, []byte(data), nil)
// 编码为Base64
return base64.StdEncoding.EncodeToString(ciphertext), nil
}

View File

@@ -0,0 +1,61 @@
// Copyright © 2023 OpenIM open source community. 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 version
// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
var (
// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
// instead. First step in deprecation, keep the fields but make
// them irrelevant. (Next we'll take it out, which may muck with
// scripts consuming the kubectl version output - but most of
// these should be looking at gitVersion already anyways.)
gitMajor string = "" // major version, always numeric
gitMinor string = "" // minor version, numeric possibly followed by "+"
// semantic version, derived by build scripts (see
// https://github.com/kubernetes/sig-release/blob/master/release-engineering/versioning.md#kubernetes-release-versioning
// https://kubernetes.io/releases/version-skew-policy/
// for a detailed discussion of this field)
//
// TODO: This field is still called "gitVersion" for legacy
// reasons. For prerelease versions, the build metadata on the
// semantic version is a git hash, but the version itself is no
// longer the direct output of "git describe", but a slight
// translation to be semver compliant.
// NOTE: The $Format strings are replaced during 'git archive' thanks to the
// companion .gitattributes file containing 'export-subst' in this same
// directory. See also https://git-scm.com/docs/gitattributes
gitVersion string = "latest"
gitCommit string = "" // sha1 from git, output of $(git rev-parse HEAD)
gitTreeState string = "" // state of git tree, either "clean" or "dirty"
buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)

View File

@@ -0,0 +1,34 @@
// Copyright © 2023 OpenIM open source community. 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 version
// Info contains versioning information.
// TODO: Add []string of api versions supported? It's still unclear
// how we'll want to distribute that information.
type Info struct {
Major string `json:"major,omitempty"`
Minor string `json:"minor,omitempty"`
GitVersion string `json:"gitVersion"`
GitCommit string `json:"gitCommit,omitempty"`
BuildDate string `json:"buildDate"`
GoVersion string `json:"goVersion"`
Compiler string `json:"compiler"`
Platform string `json:"platform"`
}
// String returns info as a human-friendly version string.
func (info Info) String() string {
return info.GitVersion
}

View File

@@ -0,0 +1,52 @@
// Copyright © 2023 OpenIM open source community. 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 version
import (
"fmt"
"runtime"
)
// Get returns the overall codebase version. It's for detecting
// what code a binary was built from.
func Get() Info {
// These variables typically come from -ldflags settings and in
// their absence fallback to the settings in ./base.go
return Info{
Major: gitMajor,
Minor: gitMinor,
GitVersion: gitVersion,
GitCommit: gitCommit,
BuildDate: buildDate,
GoVersion: runtime.Version(),
Compiler: runtime.Compiler,
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}
}
// GetSingleVersion returns single version of sealer
func GetSingleVersion() string {
return gitVersion
}
type Output struct {
OpenIMChatVersion Info `json:"OpenIMChatVersion,omitempty" yaml:"OpenIMChatVersion,omitempty"`
OpenIMServerVersion *OpenIMServerVersion `json:"OpenIMServerVersion,omitempty" yaml:"OpenIMServerVersion,omitempty"`
}
type OpenIMServerVersion struct {
ServerVersion string `json:"serverVersion,omitempty" yaml:"serverVersion,omitempty"`
ClientVersion string `json:"clientVersion,omitempty" yaml:"clientVersion,omitempty"` //sdk core version
}

115
pkg/common/xlsx/main.go Normal file
View File

@@ -0,0 +1,115 @@
package xlsx
import (
"errors"
"github.com/xuri/excelize/v2"
"io"
"reflect"
)
func ParseSheet(file *excelize.File, v interface{}) error {
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("not ptr")
}
val = val.Elem()
if val.Kind() != reflect.Slice {
return errors.New("not slice")
}
itemType := val.Type().Elem()
if itemType.Kind() != reflect.Struct {
return errors.New("not struct")
}
newItemValue := func() reflect.Value {
return reflect.New(itemType).Elem()
}
putItem := func(v reflect.Value) {
val.Set(reflect.Append(val, v))
}
var sheetName string
if s, ok := newItemValue().Interface().(SheetName); ok {
sheetName = s.SheetName()
} else {
sheetName = itemType.Name()
}
if sheetIndex, err := file.GetSheetIndex(sheetName); err != nil {
return err
} else if sheetIndex < 0 {
return nil
}
fieldIndex := make(map[string]int)
for i := 0; i < itemType.NumField(); i++ {
field := itemType.Field(i)
alias := field.Tag.Get("column")
switch alias {
case "":
fieldIndex[field.Name] = i
case "-":
continue
default:
fieldIndex[alias] = i
}
}
if len(fieldIndex) == 0 {
return errors.New("empty column struct")
}
sheetIndex := make(map[string]int)
for i := 1; ; i++ {
name, err := file.GetCellValue(sheetName, GetAxis(i, 1))
if err != nil {
return err
}
if name == "" {
break
}
if _, ok := fieldIndex[name]; ok {
sheetIndex[name] = i
}
}
if len(sheetIndex) == 0 {
return errors.New("sheet column empty")
}
for i := 2; ; i++ {
var (
notEmpty int
item = newItemValue()
)
for column, index := range sheetIndex {
s, err := file.GetCellValue(sheetName, GetAxis(index, i))
if err != nil {
return err
}
if s == "" {
continue
}
notEmpty++
if err = String2Value(s, item.Field(fieldIndex[column])); err != nil {
return err
}
}
if notEmpty > 0 {
putItem(item)
} else {
break
}
}
return nil
}
func ParseAll(r io.Reader, models ...interface{}) error {
if len(models) == 0 {
return errors.New("empty models")
}
file, err := excelize.OpenReader(r)
if err != nil {
return err
}
defer file.Close()
for i := 0; i < len(models); i++ {
if err := ParseSheet(file, models[i]); err != nil {
return err
}
}
return nil
}

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