Files
lijiaoqiao/supply-api/internal/audit/service/alert_service.go
Your Name d5b5a8ece0 fix: 系统性修复安全问题、性能问题和错误处理
安全问题修复:
- X-Forwarded-For越界检查(auth.go)
- checkTokenStatus Context参数传递(auth.go)
- Type Assertion安全检查(auth.go)

性能问题修复:
- TokenCache过期清理机制
- BruteForceProtection过期清理
- InMemoryIdempotencyStore过期清理

错误处理修复:
- AuditStore.Emit返回error
- domain层emitAudit辅助方法
- List方法返回空slice而非nil
- 金额/价格负数验证

架构一致性:
- 统一使用model.RoleHierarchyLevels

新增功能:
- Alert API完整实现(CRUD+Resolve)
- pkg/error错误码集中管理
2026-04-07 07:41:25 +08:00

275 lines
6.3 KiB
Go

package service
import (
"context"
"errors"
"strings"
"sync"
"time"
"github.com/google/uuid"
"lijiaoqiao/supply-api/internal/audit/model"
)
// 错误定义
var (
ErrAlertNotFound = errors.New("alert not found")
ErrInvalidAlertInput = errors.New("invalid alert input")
ErrAlertConflict = errors.New("alert conflict")
)
// AlertStoreInterface 告警存储接口
type AlertStoreInterface interface {
Create(ctx context.Context, alert *model.Alert) error
GetByID(ctx context.Context, alertID string) (*model.Alert, error)
Update(ctx context.Context, alert *model.Alert) error
Delete(ctx context.Context, alertID string) error
List(ctx context.Context, filter *model.AlertFilter) ([]*model.Alert, int64, error)
}
// InMemoryAlertStore 内存告警存储
type InMemoryAlertStore struct {
mu sync.RWMutex
alerts map[string]*model.Alert
}
// NewInMemoryAlertStore 创建内存告警存储
func NewInMemoryAlertStore() *InMemoryAlertStore {
return &InMemoryAlertStore{
alerts: make(map[string]*model.Alert),
}
}
// Create 创建告警
func (s *InMemoryAlertStore) Create(ctx context.Context, alert *model.Alert) error {
s.mu.Lock()
defer s.mu.Unlock()
if alert.AlertID == "" {
alert.AlertID = "ALT-" + uuid.New().String()[:8]
}
alert.CreatedAt = time.Now()
alert.UpdatedAt = time.Now()
s.alerts[alert.AlertID] = alert
return nil
}
// GetByID 根据ID获取告警
func (s *InMemoryAlertStore) GetByID(ctx context.Context, alertID string) (*model.Alert, error) {
s.mu.RLock()
defer s.mu.RUnlock()
if alert, ok := s.alerts[alertID]; ok {
return alert, nil
}
return nil, ErrAlertNotFound
}
// Update 更新告警
func (s *InMemoryAlertStore) Update(ctx context.Context, alert *model.Alert) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.alerts[alert.AlertID]; !ok {
return ErrAlertNotFound
}
alert.UpdatedAt = time.Now()
s.alerts[alert.AlertID] = alert
return nil
}
// Delete 删除告警
func (s *InMemoryAlertStore) Delete(ctx context.Context, alertID string) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.alerts[alertID]; !ok {
return ErrAlertNotFound
}
delete(s.alerts, alertID)
return nil
}
// List 查询告警列表
func (s *InMemoryAlertStore) List(ctx context.Context, filter *model.AlertFilter) ([]*model.Alert, int64, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*model.Alert
for _, alert := range s.alerts {
// 按租户过滤
if filter.TenantID > 0 && alert.TenantID != filter.TenantID {
continue
}
// 按供应商过滤
if filter.SupplierID > 0 && alert.SupplierID != filter.SupplierID {
continue
}
// 按类型过滤
if filter.AlertType != "" && alert.AlertType != filter.AlertType {
continue
}
// 按级别过滤
if filter.AlertLevel != "" && alert.AlertLevel != filter.AlertLevel {
continue
}
// 按状态过滤
if filter.Status != "" && alert.Status != filter.Status {
continue
}
// 按时间范围过滤
if !filter.StartTime.IsZero() && alert.CreatedAt.Before(filter.StartTime) {
continue
}
if !filter.EndTime.IsZero() && alert.CreatedAt.After(filter.EndTime) {
continue
}
// 关键字搜索
if filter.Keywords != "" {
kw := filter.Keywords
if !strings.Contains(alert.Title, kw) && !strings.Contains(alert.Message, kw) {
continue
}
}
result = append(result, alert)
}
total := int64(len(result))
// 分页
if filter.Offset > 0 {
if filter.Offset >= len(result) {
return []*model.Alert{}, total, nil
}
result = result[filter.Offset:]
}
if filter.Limit > 0 && filter.Limit < len(result) {
result = result[:filter.Limit]
}
return result, total, nil
}
// AlertService 告警服务
type AlertService struct {
store AlertStoreInterface
}
// NewAlertService 创建告警服务
func NewAlertService(store AlertStoreInterface) *AlertService {
return &AlertService{store: store}
}
// CreateAlert 创建告警
func (s *AlertService) CreateAlert(ctx context.Context, alert *model.Alert) (*model.Alert, error) {
if alert == nil {
return nil, ErrInvalidAlertInput
}
if alert.Title == "" {
return nil, errors.New("alert title is required")
}
// 设置默认值
if alert.AlertID == "" {
alert.AlertID = model.NewAlert("", "", "", "", "", "").AlertID
}
if alert.Status == "" {
alert.Status = model.AlertStatusActive
}
now := time.Now()
if alert.CreatedAt.IsZero() {
alert.CreatedAt = now
}
if alert.UpdatedAt.IsZero() {
alert.UpdatedAt = now
}
if alert.FirstSeenAt.IsZero() {
alert.FirstSeenAt = now
}
if alert.LastSeenAt.IsZero() {
alert.LastSeenAt = now
}
err := s.store.Create(ctx, alert)
if err != nil {
return nil, err
}
return alert, nil
}
// GetAlert 获取告警
func (s *AlertService) GetAlert(ctx context.Context, alertID string) (*model.Alert, error) {
if alertID == "" {
return nil, ErrInvalidAlertInput
}
return s.store.GetByID(ctx, alertID)
}
// UpdateAlert 更新告警
func (s *AlertService) UpdateAlert(ctx context.Context, alert *model.Alert) (*model.Alert, error) {
if alert == nil || alert.AlertID == "" {
return nil, ErrInvalidAlertInput
}
alert.UpdatedAt = time.Now()
err := s.store.Update(ctx, alert)
if err != nil {
return nil, err
}
return alert, nil
}
// DeleteAlert 删除告警
func (s *AlertService) DeleteAlert(ctx context.Context, alertID string) error {
if alertID == "" {
return ErrInvalidAlertInput
}
return s.store.Delete(ctx, alertID)
}
// ListAlerts 列出告警
func (s *AlertService) ListAlerts(ctx context.Context, filter *model.AlertFilter) ([]*model.Alert, int64, error) {
if filter == nil {
filter = &model.AlertFilter{}
}
if filter.Limit == 0 {
filter.Limit = 100
}
return s.store.List(ctx, filter)
}
// ResolveAlert 解决告警
func (s *AlertService) ResolveAlert(ctx context.Context, alertID, resolvedBy, note string) (*model.Alert, error) {
alert, err := s.store.GetByID(ctx, alertID)
if err != nil {
return nil, err
}
alert.Resolve(resolvedBy, note)
err = s.store.Update(ctx, alert)
if err != nil {
return nil, err
}
return alert, nil
}
// AcknowledgeAlert 确认告警
func (s *AlertService) AcknowledgeAlert(ctx context.Context, alertID string) (*model.Alert, error) {
alert, err := s.store.GetByID(ctx, alertID)
if err != nil {
return nil, err
}
alert.Acknowledge()
err = s.store.Update(ctx, alert)
if err != nil {
return nil, err
}
return alert, nil
}