303 lines
6.4 KiB
Go
303 lines
6.4 KiB
Go
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()
|
|
}
|