package storage import ( "encoding/json" "os" "path/filepath" "sync" "time" "mengyaping-backend/config" "mengyaping-backend/models" ) // Storage 数据存储 type Storage struct { dataPath string mu sync.RWMutex websites []models.Website records map[string][]models.MonitorRecord // key: websiteID_urlID groups []models.Group } var ( store *Storage once sync.Once ) // GetStorage 获取存储单例 func GetStorage() *Storage { once.Do(func() { cfg := config.GetConfig() store = &Storage{ dataPath: cfg.DataPath, websites: []models.Website{}, records: make(map[string][]models.MonitorRecord), groups: models.DefaultGroups, } store.ensureDataDir() store.load() }) return store } // ensureDataDir 确保数据目录存在 func (s *Storage) ensureDataDir() { os.MkdirAll(s.dataPath, 0755) } // load 加载数据 func (s *Storage) load() { s.loadWebsites() s.loadRecords() s.loadGroups() } // loadWebsites 加载网站数据 func (s *Storage) loadWebsites() { filePath := filepath.Join(s.dataPath, "websites.json") data, err := os.ReadFile(filePath) if err != nil { return } json.Unmarshal(data, &s.websites) s.migrateWebsiteGroups() } // migrateWebsiteGroups 将旧的单分组字段迁移到多分组数组 func (s *Storage) migrateWebsiteGroups() { migrated := false for i := range s.websites { w := &s.websites[i] if len(w.Groups) == 0 && w.Group != "" { w.Groups = []string{w.Group} w.Group = "" migrated = true } } if migrated { s.saveWebsites() } } // loadRecords 加载监控记录 func (s *Storage) loadRecords() { filePath := filepath.Join(s.dataPath, "records.json") data, err := os.ReadFile(filePath) if err != nil { return } json.Unmarshal(data, &s.records) // 清理过期记录 s.cleanOldRecords() } // loadGroups 加载分组 func (s *Storage) loadGroups() { filePath := filepath.Join(s.dataPath, "groups.json") data, err := os.ReadFile(filePath) if err != nil { // 使用默认分组 s.groups = models.DefaultGroups s.saveGroups() return } json.Unmarshal(data, &s.groups) } // saveWebsites 保存网站数据 func (s *Storage) saveWebsites() error { filePath := filepath.Join(s.dataPath, "websites.json") data, err := json.MarshalIndent(s.websites, "", " ") if err != nil { return err } return os.WriteFile(filePath, data, 0644) } // saveRecords 保存监控记录 func (s *Storage) saveRecords() error { filePath := filepath.Join(s.dataPath, "records.json") data, err := json.MarshalIndent(s.records, "", " ") if err != nil { return err } return os.WriteFile(filePath, data, 0644) } // saveGroups 保存分组 func (s *Storage) saveGroups() error { filePath := filepath.Join(s.dataPath, "groups.json") data, err := json.MarshalIndent(s.groups, "", " ") if err != nil { return err } return os.WriteFile(filePath, data, 0644) } // cleanOldRecords 清理过期记录 func (s *Storage) cleanOldRecords() { cfg := config.GetConfig() cutoff := time.Now().AddDate(0, 0, -cfg.Monitor.HistoryDays) for key, records := range s.records { var newRecords []models.MonitorRecord for _, r := range records { if r.CheckedAt.After(cutoff) { newRecords = append(newRecords, r) } } s.records[key] = newRecords } } // GetWebsites 获取所有网站 func (s *Storage) GetWebsites() []models.Website { s.mu.RLock() defer s.mu.RUnlock() result := make([]models.Website, len(s.websites)) copy(result, s.websites) return result } // GetWebsite 获取单个网站 func (s *Storage) GetWebsite(id string) *models.Website { s.mu.RLock() defer s.mu.RUnlock() for _, w := range s.websites { if w.ID == id { website := w return &website } } return nil } // AddWebsite 添加网站 func (s *Storage) AddWebsite(website models.Website) error { s.mu.Lock() defer s.mu.Unlock() s.websites = append(s.websites, website) return s.saveWebsites() } // UpdateWebsite 更新网站 func (s *Storage) UpdateWebsite(website models.Website) error { s.mu.Lock() defer s.mu.Unlock() for i, w := range s.websites { if w.ID == website.ID { s.websites[i] = website return s.saveWebsites() } } return nil } // DeleteWebsite 删除网站 func (s *Storage) DeleteWebsite(id string) error { s.mu.Lock() defer s.mu.Unlock() for i, w := range s.websites { if w.ID == id { s.websites = append(s.websites[:i], s.websites[i+1:]...) // 删除相关记录 for key := range s.records { if len(key) > len(id) && key[:len(id)] == id { delete(s.records, key) } } s.saveRecords() return s.saveWebsites() } } return nil } // AddRecord 添加监控记录 func (s *Storage) AddRecord(record models.MonitorRecord) error { s.mu.Lock() defer s.mu.Unlock() key := record.WebsiteID + "_" + record.URLID s.records[key] = append(s.records[key], record) // 每100条记录保存一次 if len(s.records[key])%100 == 0 { return s.saveRecords() } return nil } // GetRecords 获取监控记录 func (s *Storage) GetRecords(websiteID, urlID string, since time.Time) []models.MonitorRecord { s.mu.RLock() defer s.mu.RUnlock() key := websiteID + "_" + urlID records := s.records[key] var result []models.MonitorRecord for _, r := range records { if r.CheckedAt.After(since) { result = append(result, r) } } return result } // GetLatestRecord 获取最新记录 func (s *Storage) GetLatestRecord(websiteID, urlID string) *models.MonitorRecord { s.mu.RLock() defer s.mu.RUnlock() key := websiteID + "_" + urlID records := s.records[key] if len(records) == 0 { return nil } latest := records[len(records)-1] return &latest } // GetGroups 获取所有分组 func (s *Storage) GetGroups() []models.Group { s.mu.RLock() defer s.mu.RUnlock() result := make([]models.Group, len(s.groups)) copy(result, s.groups) return result } // AddGroup 添加分组 func (s *Storage) AddGroup(group models.Group) error { s.mu.Lock() defer s.mu.Unlock() s.groups = append(s.groups, group) return s.saveGroups() } // SaveAll 保存所有数据 func (s *Storage) SaveAll() error { s.mu.Lock() defer s.mu.Unlock() if err := s.saveWebsites(); err != nil { return err } if err := s.saveRecords(); err != nil { return err } return s.saveGroups() }