feat(M4): 生命周期 + 审计 — 到期锁定/回收 cron、审计查询/CSV 导出/每日归档、settings 动态策略

This commit is contained in:
2026-08-30 10:17:22 +08:00
parent 1ea18490e0
commit c0e2ff975a
18 changed files with 1088 additions and 75 deletions
+31 -12
View File
@@ -25,9 +25,10 @@ var (
// UserService 外部用户生命周期服务:DB 记录 + system.Manager 系统账号操作。
type UserService struct {
db *gorm.DB
sys system.Manager
cfg *config.Config
db *gorm.DB
sys system.Manager
cfg *config.Config
settings *SettingService // 可选:默认 TTL 等策略覆盖(settings 表)
}
// NewUserService 创建用户服务。
@@ -35,6 +36,20 @@ func NewUserService(db *gorm.DB, sys system.Manager, cfg *config.Config) *UserSe
return &UserService{db: db, sys: sys, cfg: cfg}
}
// WithSettings 注入设置服务(settings 表覆盖策略默认值,PLAN F7)。nil 安全。
func (s *UserService) WithSettings(settings *SettingService) *UserService {
s.settings = settings
return s
}
// effectiveDefaultTTL 新账号默认有效期:settings 覆盖优先,否则用配置默认。
func (s *UserService) effectiveDefaultTTL(ctx context.Context) time.Duration {
if s.settings != nil {
return s.settings.DefaultTTL(ctx)
}
return s.cfg.Policy.DefaultTTL
}
// GetByUsername 按用户名查询外部用户(含或不含 ext_ 前缀均可)。
func (s *UserService) GetByUsername(ctx context.Context, username string) (*model.User, error) {
full := normalizeName(username, s.cfg.System.UserPrefix)
@@ -118,7 +133,7 @@ func (s *UserService) Create(ctx context.Context, username, email, supervisor, p
return nil, ErrUserExists
}
if ttl <= 0 {
ttl = s.cfg.Policy.DefaultTTL
ttl = s.effectiveDefaultTTL(ctx)
}
expireAt := time.Now().Add(ttl)
u := &model.User{
@@ -222,33 +237,37 @@ func (s *UserService) Enable(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Update("status", model.UserStatusActive).Error
}
// Extend 延长有效期:重设 expire_atdays<=0 用配置默认 TTL)。
// Extend 延长有效期:重设 expire_atdays<=0 用默认 TTLsettings 覆盖优先)。
// 已过期用户在回收期内可经此恢复(PLAN §2.2),恢复后同步密钥。
func (s *UserService) Extend(ctx context.Context, id uint, days int) error {
// 返回新的过期时间,供 handler 响应。
func (s *UserService) Extend(ctx context.Context, id uint, days int) (time.Time, error) {
u, err := s.GetByID(ctx, id)
if err != nil {
return err
return time.Time{}, err
}
ttl := time.Duration(days) * 24 * time.Hour
if days <= 0 {
ttl = s.cfg.Policy.DefaultTTL
ttl = s.effectiveDefaultTTL(ctx)
}
newExpire := time.Now().Add(ttl)
updates := map[string]any{"expire_at": newExpire}
if u.Status == model.UserStatusExpired {
if !s.systemAccountOK(ctx, u.Username) {
return ErrSystemAccountMissing
return time.Time{}, ErrSystemAccountMissing
}
keys, err := activeUserKeys(s.db, ctx, u.ID, 0)
if err != nil {
return err
return time.Time{}, err
}
if err := s.sys.SyncAuthorizedKeys(ctx, u.Username, keys); err != nil {
return err
return time.Time{}, err
}
updates["status"] = model.UserStatusActive
}
return s.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Updates(updates).Error
if err := s.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return time.Time{}, err
}
return newExpire, nil
}
// Delete 删除并回收用户:删除系统账号(userdel -r)+ 家目录 + 密钥记录,