发布 0.1.0:通知渠道、告警规则、令牌版本与安全加固
CI / test (push) Successful in 30s
Release / release (push) Successful in 49s

This commit is contained in:
Wang Defa
2026-07-10 17:38:34 +08:00
parent 4af6a0ca92
commit dbba1f4905
78 changed files with 6898 additions and 551 deletions
+55 -14
View File
@@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"strings"
"time"
"gorm.io/gorm"
@@ -15,10 +16,12 @@ import (
// OciConfigService 管理 API Key 配置的导入、测活与快照同步。
type OciConfigService struct {
db *gorm.DB
cipher *crypto.Cipher
client oci.Client
// auditRaw 暂存审计原始事件(eventId → raw,TTL 10 分钟),
db *gorm.DB
cipher *crypto.Cipher
client oci.Client
cleanupTasks *TaskService
cleanupEvents *LogEventService
// auditRaw 按 configId:eventId 暂存审计原始事件(TTL 10 分钟),
// 列表响应剥离 raw 后详情接口据此秒开;miss 走小窗重查兜底。
auditRaw *cache.Cache
}
@@ -28,6 +31,12 @@ func NewOciConfigService(db *gorm.DB, cipher *crypto.Cipher, client oci.Client)
return &OciConfigService{db: db, cipher: cipher, client: client, auditRaw: cache.New(auditRawMax)}
}
// SetTenantCleanupDeps 注入租户删除提交后的任务与告警内存状态同步依赖。
func (s *OciConfigService) SetTenantCleanupDeps(tasks *TaskService, events *LogEventService) {
s.cleanupTasks = tasks
s.cleanupEvents = events
}
// ImportInput 是导入一份 API Key 的输入:
// ConfigINI 与显式字段二选一(ConfigINI 优先),私钥内容必填。
type ImportInput struct {
@@ -263,17 +272,18 @@ func (s *OciConfigService) Get(ctx context.Context, id uint) (*model.OciConfig,
return &cfg, nil
}
// Delete 删除配置及其区域 / 区间缓存
// Delete 在单一事务中删除配置及全部租户级本地关联数据
func (s *OciConfigService) Delete(ctx context.Context, id uint) error {
res := s.db.WithContext(ctx).Delete(&model.OciConfig{}, id)
if res.Error != nil {
return fmt.Errorf("delete oci config %d: %w", id, res.Error)
unlock := func() {}
if s.cleanupTasks != nil {
unlock = s.cleanupTasks.lockTenantCleanup()
}
if res.RowsAffected == 0 {
return fmt.Errorf("delete oci config %d: %w", id, gorm.ErrRecordNotFound)
defer unlock()
result, err := s.deleteTenant(ctx, id)
if err != nil {
return err
}
s.db.WithContext(ctx).Where("oci_config_id = ?", id).Delete(&model.RegionCache{})
s.db.WithContext(ctx).Where("oci_config_id = ?", id).Delete(&model.CompartmentCache{})
s.afterTenantDelete(ctx, result)
return nil
}
@@ -294,12 +304,43 @@ func (s *OciConfigService) refresh(ctx context.Context, cfg *model.OciConfig, cr
s.applyProfile(ctx, cfg, cred)
s.syncScopeCaches(ctx, cfg, cred)
}
if err := s.db.WithContext(ctx).Save(cfg).Error; err != nil {
return fmt.Errorf("save oci config snapshot: %w", err)
s.applySuspension(ctx, cfg, cred)
return s.saveSnapshot(ctx, cfg)
}
// saveSnapshot 只更新当前版本,避免在途测活把已删租户重新插入。
func (s *OciConfigService) saveSnapshot(ctx context.Context, cfg *model.OciConfig) error {
res := s.db.WithContext(ctx).Model(&model.OciConfig{}).
Where("id = ? AND updated_at = ?", cfg.ID, cfg.UpdatedAt).
Select("*").Omit("id", "created_at").Updates(cfg)
if res.Error != nil {
return fmt.Errorf("save oci config snapshot: %w", res.Error)
}
if res.RowsAffected != 1 {
return fmt.Errorf("save oci config snapshot: %w", gorm.ErrRecordNotFound)
}
return nil
}
// applySuspension 查询账户能力接口:云端标记暂停的租户覆盖测活结论为 suspended。
// 该接口对暂停/已终止租户仍可访问(常规 API 此时多被拒,会误判失联);查询失败不改变结论。
func (s *OciConfigService) applySuspension(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) {
caps, err := s.client.GetAccountCapabilities(ctx, cred, homeRegionOf(cfg))
if err != nil || !caps.Suspended {
return
}
cfg.AliveStatus = model.AliveStatusSuspended
cfg.LastError = strings.TrimSpace("账户已被云端暂停 " + capsStatusNote(caps))
}
// capsStatusNote 生成暂停详情备注(账户状态,如 terminated)。
func capsStatusNote(caps oci.AccountCapabilities) string {
if caps.AccountStatus == "" {
return ""
}
return "(account status: " + caps.AccountStatus + ")"
}
// applyProfile 拉取订阅信息;失败只把类别降级为 unknown,不影响测活结论。
func (s *OciConfigService) applyProfile(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) {
profile, err := s.client.FetchAccountProfile(ctx, cred)