chore: sync local changes (2026-03-12)
This commit is contained in:
@@ -1,285 +1,302 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user