feat(M4): 生命周期 + 审计 — 到期锁定/回收 cron、审计查询/CSV 导出/每日归档、settings 动态策略
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"ws_usernode/internal/config"
|
||||
"ws_usernode/internal/model"
|
||||
)
|
||||
|
||||
// 系统设置错误。
|
||||
var ErrSettingKeyUnknown = errors.New("service: 未知设置项")
|
||||
var ErrSettingValueInvalid = errors.New("service: 设置值不合法")
|
||||
|
||||
// 设置项 key 白名单(settings 表可覆盖 config 的策略字段,PLAN F7)。
|
||||
// 其余设置(SMTP、会话时长等)为启动时配置,不支持动态覆盖。
|
||||
var settingKeys = map[string]bool{
|
||||
"policy.default_ttl": true, // 新账号默认有效期(时长)
|
||||
"policy.recycle_period": true, // 到期后回收期(时长)
|
||||
"policy.audit_retention": true, // 审计保留时长(时长)
|
||||
}
|
||||
|
||||
// SettingItem 一个设置项的生效值。
|
||||
type SettingItem struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"` // 生效值(settings 覆盖优先,否则 config 默认)
|
||||
Overridden bool `json:"overridden"` // 是否被 settings 表覆盖
|
||||
}
|
||||
|
||||
// SettingService 系统设置读写(settings 表,config 提供默认值)。
|
||||
type SettingService struct {
|
||||
db *gorm.DB
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewSettingService 创建设置服务。
|
||||
func NewSettingService(db *gorm.DB, cfg *config.Config) *SettingService {
|
||||
return &SettingService{db: db, cfg: cfg}
|
||||
}
|
||||
|
||||
// GetAll 返回全部设置项的生效值(未覆盖的展示 config 默认值)。
|
||||
func (s *SettingService) GetAll(ctx context.Context) ([]SettingItem, error) {
|
||||
var rows []model.Setting
|
||||
if err := s.db.WithContext(ctx).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
overridden := make(map[string]string, len(rows))
|
||||
for _, r := range rows {
|
||||
if settingKeys[r.Key] {
|
||||
overridden[r.Key] = r.Value
|
||||
}
|
||||
}
|
||||
items := make([]SettingItem, 0, len(settingKeys))
|
||||
for _, key := range sortedSettingKeys() {
|
||||
if v, ok := overridden[key]; ok {
|
||||
items = append(items, SettingItem{Key: key, Value: v, Overridden: true})
|
||||
} else {
|
||||
items = append(items, SettingItem{Key: key, Value: s.defaultValue(key), Overridden: false})
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Set 更新一个设置项:校验 key 白名单与值格式(均为时长)。全部校验通过才写入。
|
||||
func (s *SettingService) Set(ctx context.Context, key, value string) error {
|
||||
if !settingKeys[key] {
|
||||
return fmt.Errorf("%w: %q", ErrSettingKeyUnknown, key)
|
||||
}
|
||||
value = strings.TrimSpace(value)
|
||||
if _, err := time.ParseDuration(value); err != nil {
|
||||
return fmt.Errorf("%w: %q(需为时长,如 2160h)", ErrSettingValueInvalid, value)
|
||||
}
|
||||
return s.db.WithContext(ctx).Save(&model.Setting{Key: key, Value: value}).Error
|
||||
}
|
||||
|
||||
// Duration 读取策略时长设置:settings 覆盖优先,无覆盖或解析失败用 fallback。
|
||||
func (s *SettingService) Duration(ctx context.Context, key string, fallback time.Duration) time.Duration {
|
||||
if !settingKeys[key] {
|
||||
return fallback
|
||||
}
|
||||
var row model.Setting
|
||||
err := s.db.WithContext(ctx).First(&row, "key = ?", key).Error
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
d, err := time.ParseDuration(row.Value)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// DefaultTTL 新账号默认有效期(settings 覆盖 config.policy.default_ttl)。
|
||||
func (s *SettingService) DefaultTTL(ctx context.Context) time.Duration {
|
||||
return s.Duration(ctx, "policy.default_ttl", s.cfg.Policy.DefaultTTL)
|
||||
}
|
||||
|
||||
// RecyclePeriod 到期后回收期。
|
||||
func (s *SettingService) RecyclePeriod(ctx context.Context) time.Duration {
|
||||
return s.Duration(ctx, "policy.recycle_period", s.cfg.Policy.RecyclePeriod)
|
||||
}
|
||||
|
||||
// AuditRetention 审计保留时长。
|
||||
func (s *SettingService) AuditRetention(ctx context.Context) time.Duration {
|
||||
return s.Duration(ctx, "policy.audit_retention", s.cfg.Policy.AuditRetention)
|
||||
}
|
||||
|
||||
// defaultValue 返回设置项的 config 默认值(保证遍历顺序稳定)。
|
||||
func (s *SettingService) defaultValue(key string) string {
|
||||
switch key {
|
||||
case "policy.default_ttl":
|
||||
return s.cfg.Policy.DefaultTTL.String()
|
||||
case "policy.recycle_period":
|
||||
return s.cfg.Policy.RecyclePeriod.String()
|
||||
case "policy.audit_retention":
|
||||
return s.cfg.Policy.AuditRetention.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sortedSettingKeys() []string {
|
||||
// 固定顺序展示,避免 map 遍历随机
|
||||
return []string{"policy.default_ttl", "policy.recycle_period", "policy.audit_retention"}
|
||||
}
|
||||
Reference in New Issue
Block a user