// 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" "encoding/json" "fmt" "strconv" "time" chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat" "git.imall.cloud/openim/chat/pkg/common/mctx" adminpb "git.imall.cloud/openim/chat/pkg/protocol/admin" "github.com/openimsdk/tools/errs" ) // ==================== 系统配置管理相关 RPC ==================== // convertValueToString 将任意类型的值转换为字符串(根据 ValueType) // 支持接收:字符串、数字、布尔值、JSON对象 func convertValueToString(value interface{}, valueType int32) (string, error) { if value == nil { return "", errs.ErrArgs.WrapMsg("value cannot be nil") } switch valueType { case chatdb.ConfigValueTypeString: // 字符串类型:直接转换为字符串 switch v := value.(type) { case string: return v, nil default: // 其他类型转为字符串 return fmt.Sprintf("%v", v), nil } case chatdb.ConfigValueTypeNumber: // 数字类型:转换为数字字符串 switch v := value.(type) { case string: // 验证是否为有效数字 if _, err := strconv.ParseFloat(v, 64); err != nil { return "", errs.ErrArgs.WrapMsg("value must be a valid number") } return v, nil case float64: return strconv.FormatFloat(v, 'f', -1, 64), nil case float32: return strconv.FormatFloat(float64(v), 'f', -1, 32), nil case int: return strconv.Itoa(v), nil case int64: return strconv.FormatInt(v, 10), nil case int32: return strconv.FormatInt(int64(v), 10), nil default: // 尝试转换为数字 if num, ok := v.(float64); ok { return strconv.FormatFloat(num, 'f', -1, 64), nil } return "", errs.ErrArgs.WrapMsg("value must be a number") } case chatdb.ConfigValueTypeBool: // 布尔类型:转换为 "true" 或 "false" switch v := value.(type) { case string: // 验证是否为有效的布尔字符串 if _, err := strconv.ParseBool(v); err != nil { return "", errs.ErrArgs.WrapMsg("value must be 'true' or 'false'") } return v, nil case bool: return strconv.FormatBool(v), nil default: return "", errs.ErrArgs.WrapMsg("value must be a boolean") } case chatdb.ConfigValueTypeJSON: // JSON类型:转换为 JSON 字符串 switch v := value.(type) { case string: // 验证是否为有效的 JSON var js interface{} if err := json.Unmarshal([]byte(v), &js); err != nil { return "", errs.ErrArgs.WrapMsg("value must be a valid JSON string") } return v, nil default: // 将对象序列化为 JSON 字符串 jsonBytes, err := json.Marshal(v) if err != nil { return "", errs.ErrArgs.WrapMsg("value must be a valid JSON object") } return string(jsonBytes), nil } default: return "", errs.ErrArgs.WrapMsg("invalid value type") } } // convertValueFromString 将字符串值转换为对应类型(用于返回给前端) func convertValueFromString(value string, valueType int32) (interface{}, error) { switch valueType { case chatdb.ConfigValueTypeString: return value, nil case chatdb.ConfigValueTypeNumber: // 尝试解析为数字 if num, err := strconv.ParseFloat(value, 64); err == nil { // 如果是整数,返回整数;否则返回浮点数 if num == float64(int64(num)) { return int64(num), nil } return num, nil } return nil, errs.ErrArgs.WrapMsg("invalid number format") case chatdb.ConfigValueTypeBool: return strconv.ParseBool(value) case chatdb.ConfigValueTypeJSON: var js interface{} if err := json.Unmarshal([]byte(value), &js); err != nil { return nil, err } return js, nil default: return value, nil } } // validateValueByType 根据 ValueType 验证 Value 的格式 func validateValueByType(value string, valueType int32) error { switch valueType { case chatdb.ConfigValueTypeString: // 字符串类型:任何字符串都可以,无需验证 return nil case chatdb.ConfigValueTypeNumber: // 数字类型:必须是有效的数字字符串 if value == "" { return errs.ErrArgs.WrapMsg("value cannot be empty for number type") } _, err := strconv.ParseFloat(value, 64) if err != nil { return errs.ErrArgs.WrapMsg("value must be a valid number string") } return nil case chatdb.ConfigValueTypeBool: // 布尔类型:必须是 "true" 或 "false" if value == "" { return errs.ErrArgs.WrapMsg("value cannot be empty for bool type") } _, err := strconv.ParseBool(value) if err != nil { return errs.ErrArgs.WrapMsg("value must be 'true' or 'false' for bool type") } return nil case chatdb.ConfigValueTypeJSON: // JSON类型:必须是有效的 JSON 字符串 if value == "" { return errs.ErrArgs.WrapMsg("value cannot be empty for JSON type") } var js interface{} if err := json.Unmarshal([]byte(value), &js); err != nil { return errs.ErrArgs.WrapMsg("value must be a valid JSON string") } return nil default: return errs.ErrArgs.WrapMsg("invalid value type") } } // CreateSystemConfig 创建系统配置 func (o *adminServer) CreateSystemConfig(ctx context.Context, req *adminpb.CreateSystemConfigReq) (*adminpb.CreateSystemConfigResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 验证必填字段 if req.Key == "" { return nil, errs.ErrArgs.WrapMsg("config key is required") } // 检查配置键是否已存在 _, err := o.ChatDatabase.GetSystemConfig(ctx, req.Key) if err == nil { return nil, errs.ErrDuplicateKey.WrapMsg("config key already exists") } // 创建配置对象 config := &chatdb.SystemConfig{ Key: req.Key, Title: req.Title, Value: req.Value, ValueType: req.ValueType, Description: req.Description, Enabled: req.Enabled, ShowInApp: req.ShowInApp, CreateTime: time.Now(), UpdateTime: time.Now(), } // 如果未设置值类型,默认为字符串类型 if config.ValueType == 0 { config.ValueType = chatdb.ConfigValueTypeString } // 根据 ValueType 验证 Value 的格式 if err := validateValueByType(config.Value, config.ValueType); err != nil { return nil, err } // 保存到数据库 if err := o.ChatDatabase.CreateSystemConfig(ctx, config); err != nil { return nil, err } return &adminpb.CreateSystemConfigResp{}, nil } // GetSystemConfig 获取系统配置详情 func (o *adminServer) GetSystemConfig(ctx context.Context, req *adminpb.GetSystemConfigReq) (*adminpb.GetSystemConfigResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 获取配置 config, err := o.ChatDatabase.GetSystemConfig(ctx, req.Key) if err != nil { return nil, err } return &adminpb.GetSystemConfigResp{ Config: convertSystemConfigToProto(config), }, nil } // GetAllSystemConfigs 获取所有系统配置(分页) func (o *adminServer) GetAllSystemConfigs(ctx context.Context, req *adminpb.GetAllSystemConfigsReq) (*adminpb.GetAllSystemConfigsResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 获取配置列表 total, configs, err := o.ChatDatabase.GetAllSystemConfigs(ctx, req.Pagination) if err != nil { return nil, err } // 转换为响应格式 configInfos := make([]*adminpb.SystemConfigInfo, 0, len(configs)) for _, config := range configs { configInfos = append(configInfos, convertSystemConfigToProto(config)) } return &adminpb.GetAllSystemConfigsResp{ Total: uint32(total), List: configInfos, }, nil } // UpdateSystemConfig 更新系统配置 func (o *adminServer) UpdateSystemConfig(ctx context.Context, req *adminpb.UpdateSystemConfigReq) (*adminpb.UpdateSystemConfigResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 验证配置是否存在 _, err := o.ChatDatabase.GetSystemConfig(ctx, req.Key) if err != nil { return nil, err } // 获取当前配置,用于验证 currentConfig, err := o.ChatDatabase.GetSystemConfig(ctx, req.Key) if err != nil { return nil, err } // 确定要使用的 ValueType(如果更新了 ValueType,使用新的;否则使用当前的) newValueType := currentConfig.ValueType if req.ValueType != nil { newValueType = req.ValueType.Value } // 确定要使用的 Value(如果更新了 Value,使用新的;否则使用当前的) newValue := currentConfig.Value if req.Value != nil { newValue = req.Value.Value // 如果更新了 Value,需要根据 ValueType 验证 if err := validateValueByType(newValue, newValueType); err != nil { return nil, err } } else if req.ValueType != nil { // 如果只更新了 ValueType,需要验证当前 Value 是否符合新的 ValueType if err := validateValueByType(currentConfig.Value, newValueType); err != nil { return nil, err } } // 构建更新数据 updateData := make(map[string]any) if req.Title != nil { updateData["title"] = req.Title.Value } if req.Value != nil { updateData["value"] = req.Value.Value } if req.ValueType != nil { updateData["value_type"] = req.ValueType.Value } if req.Description != nil { updateData["description"] = req.Description.Value } if req.Enabled != nil { updateData["enabled"] = req.Enabled.Value } if req.ShowInApp != nil { updateData["show_in_app"] = req.ShowInApp.Value } // 更新配置 if err := o.ChatDatabase.UpdateSystemConfig(ctx, req.Key, updateData); err != nil { return nil, err } return &adminpb.UpdateSystemConfigResp{}, nil } // UpdateSystemConfigValue 更新系统配置值 func (o *adminServer) UpdateSystemConfigValue(ctx context.Context, req *adminpb.UpdateSystemConfigValueReq) (*adminpb.UpdateSystemConfigValueResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 获取当前配置,用于获取 ValueType 进行验证 config, err := o.ChatDatabase.GetSystemConfig(ctx, req.Key) if err != nil { return nil, err } // 根据当前 ValueType 验证新 Value 的格式 if err := validateValueByType(req.Value, config.ValueType); err != nil { return nil, err } // 更新配置值 if err := o.ChatDatabase.UpdateSystemConfigValue(ctx, req.Key, req.Value); err != nil { return nil, err } return &adminpb.UpdateSystemConfigValueResp{}, nil } // UpdateSystemConfigEnabled 更新系统配置启用状态 func (o *adminServer) UpdateSystemConfigEnabled(ctx context.Context, req *adminpb.UpdateSystemConfigEnabledReq) (*adminpb.UpdateSystemConfigEnabledResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 更新启用状态 if err := o.ChatDatabase.UpdateSystemConfigEnabled(ctx, req.Key, req.Enabled); err != nil { return nil, err } return &adminpb.UpdateSystemConfigEnabledResp{}, nil } // DeleteSystemConfig 删除系统配置 func (o *adminServer) DeleteSystemConfig(ctx context.Context, req *adminpb.DeleteSystemConfigReq) (*adminpb.DeleteSystemConfigResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 删除配置 if err := o.ChatDatabase.DeleteSystemConfig(ctx, req.Keys); err != nil { return nil, err } return &adminpb.DeleteSystemConfigResp{}, nil } // GetEnabledSystemConfigs 获取所有已启用的配置 func (o *adminServer) GetEnabledSystemConfigs(ctx context.Context, req *adminpb.GetEnabledSystemConfigsReq) (*adminpb.GetEnabledSystemConfigsResp, error) { // 检查管理员权限 if _, err := mctx.CheckAdmin(ctx); err != nil { return nil, err } // 获取已启用的配置 configs, err := o.ChatDatabase.GetEnabledSystemConfigs(ctx) if err != nil { return nil, err } // 转换为响应格式 configInfos := make([]*adminpb.SystemConfigInfo, 0, len(configs)) for _, config := range configs { configInfos = append(configInfos, convertSystemConfigToProto(config)) } return &adminpb.GetEnabledSystemConfigsResp{ List: configInfos, }, nil } // convertSystemConfigToProto 将数据库模型转换为 protobuf 消息 func convertSystemConfigToProto(config *chatdb.SystemConfig) *adminpb.SystemConfigInfo { return &adminpb.SystemConfigInfo{ Key: config.Key, Title: config.Title, Value: config.Value, ValueType: config.ValueType, Description: config.Description, Enabled: config.Enabled, ShowInApp: config.ShowInApp, CreateTime: config.CreateTime.UnixMilli(), UpdateTime: config.UpdateTime.UnixMilli(), } }