mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-06 20:02:41 +08:00
* 功能: v2.1 可观测性与流控 — Prometheus + 节点带宽 + 审计 Webhook 核心能力: - Prometheus /metrics 端点:11 类指标(任务/存储/节点/SLA/验证/恢复/复制) - 节点级带宽限速生效:model.Node.BandwidthLimit 覆盖全局默认 - 审计日志 Webhook 外输:HMAC-SHA256 签名,配合 SIEM 合规留档 实现: - server/internal/metrics/ 独立 Registry + 异步 Gauge Collector(30s) - backup/restore/verify/replication 服务注入 metrics 钩子,nil 安全 - resolveProviderForNode() 按 task.NodeID 解析 BandwidthLimit - AuditService.SetWebhook + 动态 settings 推送,无需重启 测试: - metrics/registry_test.go: 注册/采集/nil safety/HTTP handler - service/audit_service_webhook_test.go: 签名正确性/异步投递/禁用路径 - go test ./... 全部通过 * chore: 触发 CodeQL 扫描
99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
|
||
"backupx/server/internal/apperror"
|
||
"backupx/server/internal/model"
|
||
"backupx/server/internal/repository"
|
||
)
|
||
|
||
// AuditWebhookConfigurer 抽象审计 webhook 配置接口,由 AuditService 实现。
|
||
// 用接口解耦避免 settings_service 直接依赖 AuditService 具体类型。
|
||
type AuditWebhookConfigurer interface {
|
||
SetWebhook(url, secret string)
|
||
}
|
||
|
||
type SettingsService struct {
|
||
configs repository.SystemConfigRepository
|
||
auditWebhook AuditWebhookConfigurer
|
||
}
|
||
|
||
func NewSettingsService(configs repository.SystemConfigRepository) *SettingsService {
|
||
return &SettingsService{configs: configs}
|
||
}
|
||
|
||
// SetAuditWebhookConfigurer 注入 audit webhook 配置接收方。
|
||
// 启动时立即用当前 DB 中的设置调用一次,后续每次 Update 变更 webhook key 时同步推送。
|
||
func (s *SettingsService) SetAuditWebhookConfigurer(ctx context.Context, configurer AuditWebhookConfigurer) {
|
||
if s == nil || configurer == nil {
|
||
return
|
||
}
|
||
s.auditWebhook = configurer
|
||
// 启动时同步一次,保证重启后配置不丢失
|
||
all, err := s.GetAll(ctx)
|
||
if err == nil {
|
||
configurer.SetWebhook(all[SettingKeyAuditWebhookURL], all[SettingKeyAuditWebhookSecret])
|
||
}
|
||
}
|
||
|
||
// 可被前端写入的系统设置键。新增键必须同步加入此清单,
|
||
// 否则 Update 会忽略(安全原则:显式 allow-list)。
|
||
const (
|
||
SettingKeySiteName = "site_name"
|
||
SettingKeyLanguage = "language"
|
||
SettingKeyTimezone = "timezone"
|
||
SettingKeyBackupNotificationEnabled = "backup_notification_enabled"
|
||
SettingKeyBandwidthLimit = "bandwidth_limit"
|
||
SettingKeyAuditWebhookURL = "audit_webhook_url"
|
||
SettingKeyAuditWebhookSecret = "audit_webhook_secret"
|
||
)
|
||
|
||
var settingsKeys = []string{
|
||
SettingKeySiteName,
|
||
SettingKeyLanguage,
|
||
SettingKeyTimezone,
|
||
SettingKeyBackupNotificationEnabled,
|
||
SettingKeyBandwidthLimit,
|
||
SettingKeyAuditWebhookURL,
|
||
SettingKeyAuditWebhookSecret,
|
||
}
|
||
|
||
func (s *SettingsService) GetAll(ctx context.Context) (map[string]string, error) {
|
||
items, err := s.configs.List(ctx)
|
||
if err != nil {
|
||
return nil, apperror.Internal("SETTINGS_LIST_FAILED", "无法获取系统设置", err)
|
||
}
|
||
result := make(map[string]string, len(items))
|
||
for _, item := range items {
|
||
result[item.Key] = item.Value
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (s *SettingsService) Update(ctx context.Context, settings map[string]string) (map[string]string, error) {
|
||
allowed := make(map[string]bool, len(settingsKeys))
|
||
for _, key := range settingsKeys {
|
||
allowed[key] = true
|
||
}
|
||
auditWebhookTouched := false
|
||
for key, value := range settings {
|
||
if !allowed[key] {
|
||
continue
|
||
}
|
||
item := &model.SystemConfig{Key: key, Value: value}
|
||
if err := s.configs.Upsert(ctx, item); err != nil {
|
||
return nil, apperror.Internal("SETTINGS_UPDATE_FAILED", "无法更新系统设置", err)
|
||
}
|
||
if key == SettingKeyAuditWebhookURL || key == SettingKeyAuditWebhookSecret {
|
||
auditWebhookTouched = true
|
||
}
|
||
}
|
||
// audit webhook 配置变化:立即同步到 AuditService,避免重启才生效
|
||
if auditWebhookTouched && s.auditWebhook != nil {
|
||
all, _ := s.GetAll(ctx)
|
||
s.auditWebhook.SetWebhook(all[SettingKeyAuditWebhookURL], all[SettingKeyAuditWebhookSecret])
|
||
}
|
||
return s.GetAll(ctx)
|
||
}
|