复制项目
This commit is contained in:
433
pkg/common/webhook/config_manager.go
Normal file
433
pkg/common/webhook/config_manager.go
Normal file
@@ -0,0 +1,433 @@
|
||||
// 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 webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
|
||||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
|
||||
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
const (
|
||||
// WebhookConfigKey 数据库中的webhook配置键
|
||||
WebhookConfigKey = "webhook_config"
|
||||
// DefaultRefreshInterval 默认刷新间隔(30秒,方便调试)
|
||||
DefaultRefreshInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
// ConfigManager webhook配置管理器,支持从数据库读取和定时刷新
|
||||
type ConfigManager struct {
|
||||
db database.SystemConfig
|
||||
defaultConfig *config.Webhooks
|
||||
mu sync.RWMutex
|
||||
cachedConfig *config.Webhooks
|
||||
lastUpdate time.Time
|
||||
refreshInterval time.Duration
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// NewConfigManager 创建webhook配置管理器
|
||||
func NewConfigManager(db database.SystemConfig, defaultConfig *config.Webhooks) *ConfigManager {
|
||||
cm := &ConfigManager{
|
||||
db: db,
|
||||
defaultConfig: defaultConfig,
|
||||
cachedConfig: defaultConfig,
|
||||
refreshInterval: DefaultRefreshInterval,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
// Start 启动配置管理器,开始定时刷新
|
||||
func (cm *ConfigManager) Start(ctx context.Context) error {
|
||||
// 立即加载一次配置
|
||||
log.ZInfo(ctx, "webhook config manager starting, initial refresh...")
|
||||
if err := cm.Refresh(ctx); err != nil {
|
||||
log.ZWarn(ctx, "initial webhook config refresh failed, using default config", err)
|
||||
} else {
|
||||
currentConfig := cm.GetConfig()
|
||||
log.ZInfo(ctx, "webhook config manager started successfully", "url", currentConfig.URL, "refresh_interval", cm.refreshInterval)
|
||||
}
|
||||
|
||||
// 启动定时刷新goroutine
|
||||
go cm.refreshLoop(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止配置管理器
|
||||
func (cm *ConfigManager) Stop() {
|
||||
close(cm.stopCh)
|
||||
}
|
||||
|
||||
// refreshLoop 定时刷新循环
|
||||
func (cm *ConfigManager) refreshLoop(ctx context.Context) {
|
||||
ticker := time.NewTicker(cm.refreshInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.ZInfo(ctx, "webhook config refresh loop started", "interval", cm.refreshInterval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
log.ZDebug(ctx, "webhook config scheduled refresh triggered")
|
||||
if err := cm.Refresh(ctx); err != nil {
|
||||
log.ZWarn(ctx, "webhook config refresh failed", err)
|
||||
}
|
||||
case <-cm.stopCh:
|
||||
log.ZInfo(ctx, "webhook config refresh loop stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh 从数据库刷新配置
|
||||
func (cm *ConfigManager) Refresh(ctx context.Context) error {
|
||||
// 从数据库读取配置
|
||||
sysConfig, err := cm.db.FindByKey(ctx, WebhookConfigKey)
|
||||
if err != nil {
|
||||
// 如果查询出错,使用默认配置
|
||||
log.ZWarn(ctx, "failed to get webhook config from database, using default config", err)
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 如果数据库中没有配置,使用默认配置
|
||||
if sysConfig == nil {
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
log.ZInfo(ctx, "webhook config not found in database, using default config", "default_url", cm.defaultConfig.URL)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查值类型
|
||||
if sysConfig.ValueType != model.SystemConfigValueTypeJSON {
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
log.ZWarn(ctx, "webhook config valueType is not json, using default config", nil, "value_type", sysConfig.ValueType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 如果配置被禁用,使用默认配置
|
||||
if !sysConfig.Enabled {
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
log.ZInfo(ctx, "webhook config is disabled, using default config", "default_url", cm.defaultConfig.URL)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 如果配置值为空,使用默认配置
|
||||
if sysConfig.Value == "" {
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
log.ZInfo(ctx, "webhook config value is empty, using default config", "default_url", cm.defaultConfig.URL)
|
||||
return nil
|
||||
}
|
||||
|
||||
valuePreview := sysConfig.Value
|
||||
if len(valuePreview) > 100 {
|
||||
valuePreview = valuePreview[:100] + "..."
|
||||
}
|
||||
log.ZDebug(ctx, "webhook config value found", "value_length", len(sysConfig.Value), "value_preview", valuePreview)
|
||||
|
||||
// 解析配置
|
||||
var webhookConfig config.Webhooks
|
||||
if err := json.Unmarshal([]byte(sysConfig.Value), &webhookConfig); err != nil {
|
||||
// 如果解析失败,使用默认配置
|
||||
log.ZWarn(ctx, "failed to unmarshal webhook config, using default config", err)
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证解析后的配置,确保AttentionIds不为nil
|
||||
if webhookConfig.AfterSendGroupMsg.AttentionIds == nil {
|
||||
webhookConfig.AfterSendGroupMsg.AttentionIds = []string{}
|
||||
}
|
||||
|
||||
normalized, ok := normalizeWebhookConfig(cm.defaultConfig, &webhookConfig)
|
||||
if !ok {
|
||||
log.ZWarn(ctx, "webhook config URL is empty, using default config", nil)
|
||||
cm.mu.Lock()
|
||||
cm.cachedConfig = cm.defaultConfig
|
||||
cm.lastUpdate = time.Now()
|
||||
cm.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
cm.mu.Lock()
|
||||
oldURL := cm.cachedConfig.URL
|
||||
oldAttentionIdsCount := len(cm.cachedConfig.AfterSendGroupMsg.AttentionIds)
|
||||
cm.cachedConfig = normalized
|
||||
cm.lastUpdate = time.Now()
|
||||
newAttentionIdsCount := len(normalized.AfterSendGroupMsg.AttentionIds)
|
||||
cm.mu.Unlock()
|
||||
|
||||
// 如果URL或AttentionIds发生变化,记录日志
|
||||
urlChanged := oldURL != webhookConfig.URL
|
||||
attentionIdsChanged := oldAttentionIdsCount != newAttentionIdsCount
|
||||
if urlChanged || attentionIdsChanged {
|
||||
log.ZInfo(ctx, "webhook config updated from database",
|
||||
"old_url", oldURL, "new_url", webhookConfig.URL,
|
||||
"old_attention_ids_count", oldAttentionIdsCount,
|
||||
"new_attention_ids_count", newAttentionIdsCount,
|
||||
"url_changed", urlChanged,
|
||||
"attention_ids_changed", attentionIdsChanged)
|
||||
} else {
|
||||
log.ZDebug(ctx, "webhook config refreshed (no change)", "url", webhookConfig.URL, "attention_ids_count", newAttentionIdsCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig 获取当前缓存的配置
|
||||
func (cm *ConfigManager) GetConfig() *config.Webhooks {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.cachedConfig
|
||||
}
|
||||
|
||||
// normalizeWebhookConfig 校验并填充缺省值,返回是否有效
|
||||
func normalizeWebhookConfig(defaultCfg *config.Webhooks, cfg *config.Webhooks) (*config.Webhooks, bool) {
|
||||
if cfg == nil {
|
||||
return nil, false
|
||||
}
|
||||
if cfg.URL == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
normalized := *cfg // 浅拷贝
|
||||
|
||||
// 填充默认值,避免数据库配置错误导致回调不可用
|
||||
if normalized.AfterSendGroupMsg.AttentionIds == nil {
|
||||
normalized.AfterSendGroupMsg.AttentionIds = []string{}
|
||||
}
|
||||
|
||||
applyBeforeDefaults := func(dst *config.BeforeConfig, fallback config.BeforeConfig) {
|
||||
if dst.Timeout <= 0 {
|
||||
dst.Timeout = fallback.Timeout
|
||||
}
|
||||
}
|
||||
applyAfterDefaults := func(dst *config.AfterConfig, fallback config.AfterConfig) {
|
||||
if dst.Timeout <= 0 {
|
||||
dst.Timeout = fallback.Timeout
|
||||
}
|
||||
if dst.AttentionIds == nil {
|
||||
dst.AttentionIds = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if defaultCfg != nil {
|
||||
applyBeforeDefaults(&normalized.BeforeSendSingleMsg, defaultCfg.BeforeSendSingleMsg)
|
||||
applyBeforeDefaults(&normalized.BeforeUpdateUserInfoEx, defaultCfg.BeforeUpdateUserInfoEx)
|
||||
applyAfterDefaults(&normalized.AfterUpdateUserInfoEx, defaultCfg.AfterUpdateUserInfoEx)
|
||||
applyAfterDefaults(&normalized.AfterSendSingleMsg, defaultCfg.AfterSendSingleMsg)
|
||||
applyBeforeDefaults(&normalized.BeforeSendGroupMsg, defaultCfg.BeforeSendGroupMsg)
|
||||
applyBeforeDefaults(&normalized.BeforeMsgModify, defaultCfg.BeforeMsgModify)
|
||||
applyAfterDefaults(&normalized.AfterSendGroupMsg, defaultCfg.AfterSendGroupMsg)
|
||||
applyAfterDefaults(&normalized.AfterMsgSaveDB, defaultCfg.AfterMsgSaveDB)
|
||||
applyAfterDefaults(&normalized.AfterUserOnline, defaultCfg.AfterUserOnline)
|
||||
applyAfterDefaults(&normalized.AfterUserOffline, defaultCfg.AfterUserOffline)
|
||||
applyAfterDefaults(&normalized.AfterUserKickOff, defaultCfg.AfterUserKickOff)
|
||||
applyBeforeDefaults(&normalized.BeforeOfflinePush, defaultCfg.BeforeOfflinePush)
|
||||
applyBeforeDefaults(&normalized.BeforeOnlinePush, defaultCfg.BeforeOnlinePush)
|
||||
applyBeforeDefaults(&normalized.BeforeGroupOnlinePush, defaultCfg.BeforeGroupOnlinePush)
|
||||
applyBeforeDefaults(&normalized.BeforeAddFriend, defaultCfg.BeforeAddFriend)
|
||||
applyBeforeDefaults(&normalized.BeforeUpdateUserInfo, defaultCfg.BeforeUpdateUserInfo)
|
||||
applyAfterDefaults(&normalized.AfterUpdateUserInfo, defaultCfg.AfterUpdateUserInfo)
|
||||
applyBeforeDefaults(&normalized.BeforeCreateGroup, defaultCfg.BeforeCreateGroup)
|
||||
applyAfterDefaults(&normalized.AfterCreateGroup, defaultCfg.AfterCreateGroup)
|
||||
applyBeforeDefaults(&normalized.BeforeMemberJoinGroup, defaultCfg.BeforeMemberJoinGroup)
|
||||
applyBeforeDefaults(&normalized.BeforeSetGroupMemberInfo, defaultCfg.BeforeSetGroupMemberInfo)
|
||||
applyAfterDefaults(&normalized.AfterSetGroupMemberInfo, defaultCfg.AfterSetGroupMemberInfo)
|
||||
applyAfterDefaults(&normalized.AfterQuitGroup, defaultCfg.AfterQuitGroup)
|
||||
applyAfterDefaults(&normalized.AfterKickGroupMember, defaultCfg.AfterKickGroupMember)
|
||||
applyAfterDefaults(&normalized.AfterDismissGroup, defaultCfg.AfterDismissGroup)
|
||||
}
|
||||
|
||||
return &normalized, true
|
||||
}
|
||||
|
||||
// GetURL 获取当前webhook URL
|
||||
func (cm *ConfigManager) GetURL() string {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.cachedConfig.URL
|
||||
}
|
||||
|
||||
// SetRefreshInterval 设置刷新间隔
|
||||
func (cm *ConfigManager) SetRefreshInterval(interval time.Duration) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
cm.refreshInterval = interval
|
||||
}
|
||||
|
||||
// GetLastUpdate 获取最后更新时间
|
||||
func (cm *ConfigManager) GetLastUpdate() time.Time {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
return cm.lastUpdate
|
||||
}
|
||||
|
||||
// UpdateAttentionIds 更新webhook配置中的attentionIds(添加或移除群ID)
|
||||
// add: true表示添加groupID,false表示移除groupID
|
||||
func UpdateAttentionIds(ctx context.Context, db database.SystemConfig, groupID string, add bool) error {
|
||||
if groupID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取当前配置
|
||||
sysConfig, err := db.FindByKey(ctx, WebhookConfigKey)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: failed to get webhook config from database", err, "groupID", groupID, "add", add)
|
||||
return errs.WrapMsg(err, "failed to get webhook config from database")
|
||||
}
|
||||
|
||||
if sysConfig == nil {
|
||||
log.ZDebug(ctx, "UpdateAttentionIds: webhook config not found in database, skipping", "groupID", groupID, "add", add)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !sysConfig.Enabled {
|
||||
log.ZDebug(ctx, "UpdateAttentionIds: webhook config is disabled, skipping", "groupID", groupID, "add", add)
|
||||
return nil
|
||||
}
|
||||
|
||||
if sysConfig.Value == "" {
|
||||
log.ZDebug(ctx, "UpdateAttentionIds: webhook config value is empty, skipping", "groupID", groupID, "add", add)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查值类型
|
||||
if sysConfig.ValueType != model.SystemConfigValueTypeJSON {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: webhook config valueType is not json, skipping", nil, "groupID", groupID, "add", add, "value_type", sysConfig.ValueType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析当前配置
|
||||
var webhookConfig config.Webhooks
|
||||
if err := json.Unmarshal([]byte(sysConfig.Value), &webhookConfig); err != nil {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: failed to unmarshal webhook config", err, "groupID", groupID, "add", add)
|
||||
return errs.WrapMsg(err, "failed to unmarshal webhook config")
|
||||
}
|
||||
|
||||
// 记录更新前的Enable状态
|
||||
enableBefore := webhookConfig.AfterSendGroupMsg.Enable
|
||||
|
||||
// 验证解析后的配置是否有效
|
||||
if webhookConfig.AfterSendGroupMsg.AttentionIds == nil {
|
||||
webhookConfig.AfterSendGroupMsg.AttentionIds = []string{}
|
||||
}
|
||||
|
||||
// 更新afterSendGroupMsg的attentionIds
|
||||
attentionIds := webhookConfig.AfterSendGroupMsg.AttentionIds
|
||||
oldCount := len(attentionIds)
|
||||
var updated bool
|
||||
|
||||
if add {
|
||||
// 添加groupID(如果不存在)
|
||||
if !datautil.Contain(groupID, attentionIds...) {
|
||||
attentionIds = append(attentionIds, groupID)
|
||||
webhookConfig.AfterSendGroupMsg.AttentionIds = attentionIds
|
||||
updated = true
|
||||
log.ZInfo(ctx, "UpdateAttentionIds: adding groupID to attentionIds", "groupID", groupID, "old_count", oldCount, "new_count", len(attentionIds), "enable", enableBefore)
|
||||
} else {
|
||||
log.ZDebug(ctx, "UpdateAttentionIds: groupID already exists in attentionIds, skipping", "groupID", groupID, "count", oldCount)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// 移除groupID
|
||||
newAttentionIds := make([]string, 0, len(attentionIds))
|
||||
for _, id := range attentionIds {
|
||||
if id != groupID {
|
||||
newAttentionIds = append(newAttentionIds, id)
|
||||
}
|
||||
}
|
||||
if len(newAttentionIds) != len(attentionIds) {
|
||||
webhookConfig.AfterSendGroupMsg.AttentionIds = newAttentionIds
|
||||
updated = true
|
||||
log.ZInfo(ctx, "UpdateAttentionIds: removing groupID from attentionIds", "groupID", groupID, "old_count", oldCount, "new_count", len(newAttentionIds), "enable", enableBefore)
|
||||
} else {
|
||||
log.ZDebug(ctx, "UpdateAttentionIds: groupID not found in attentionIds, skipping", "groupID", groupID, "count", oldCount)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !updated {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证配置URL是否存在(必要的验证)
|
||||
if webhookConfig.URL == "" {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: webhook config URL is empty, skipping update", nil, "groupID", groupID, "add", add)
|
||||
return errs.ErrArgs.WrapMsg("webhook config URL is empty")
|
||||
}
|
||||
|
||||
// 记录更新后的Enable状态(确保没有被修改)
|
||||
enableAfter := webhookConfig.AfterSendGroupMsg.Enable
|
||||
if enableBefore != enableAfter {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: Enable field changed unexpectedly", nil,
|
||||
"groupID", groupID, "add", add,
|
||||
"enable_before", enableBefore, "enable_after", enableAfter)
|
||||
// 恢复原始值,确保不会修改Enable字段
|
||||
webhookConfig.AfterSendGroupMsg.Enable = enableBefore
|
||||
}
|
||||
|
||||
// 序列化更新后的配置(只更新attentionIds,不改变其他配置)
|
||||
updatedValue, err := json.Marshal(webhookConfig)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: failed to marshal updated webhook config", err, "groupID", groupID, "add", add)
|
||||
return errs.WrapMsg(err, "failed to marshal updated webhook config")
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
if err := db.Update(ctx, WebhookConfigKey, map[string]any{
|
||||
"value": string(updatedValue),
|
||||
}); err != nil {
|
||||
log.ZWarn(ctx, "UpdateAttentionIds: failed to update webhook config in database", err, "groupID", groupID, "add", add)
|
||||
return errs.WrapMsg(err, "failed to update webhook config in database")
|
||||
}
|
||||
|
||||
log.ZInfo(ctx, "UpdateAttentionIds: successfully updated attentionIds in database",
|
||||
"groupID", groupID, "add", add,
|
||||
"attention_ids_count", len(webhookConfig.AfterSendGroupMsg.AttentionIds),
|
||||
"enable", webhookConfig.AfterSendGroupMsg.Enable)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user