From ec3fc77403470b5e98bda07fdcd07cee53ef66a0 Mon Sep 17 00:00:00 2001 From: zyj <18107291228@163.com> Date: Sat, 22 Nov 2025 17:48:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=AC=E5=9C=B0=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/site-clone.db | Bin 0 -> 32768 bytes go.mod | 1 + go.sum | 4 + storage/download_store.go | 171 ++++++++++++++++++++++++++++++++++ storage/site_store.go | 107 ++++++++++++++++++++++ storage/store.go | 187 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 470 insertions(+) create mode 100644 data/site-clone.db create mode 100644 storage/download_store.go create mode 100644 storage/site_store.go create mode 100644 storage/store.go diff --git a/data/site-clone.db b/data/site-clone.db new file mode 100644 index 0000000000000000000000000000000000000000..d4a7cbbbeabec9c96fe9262e5bc2efcc77dfa445 GIT binary patch literal 32768 zcmeI(zi!h&9Ki8VC?HW#A0fJdh1B?O;#v$m1Y3n7*LI1KV^^`Q09APbMxKKgU}s@t zMPdSpsS>;aKi9r0K-z#{Xsd79pYD9Qv+q9b^1EDmz4mpjD~~>I9IZ9=&9>oc*RG58 zu5!IA?{ww6{LTAM56Z8)A0+|^Ab2RxLxCDE5? zu6HDw;{bh$=K4V5rbJ(&xqdCtoD29a7hx17WjW4@C#AG3ed$NW#XQf7Fdom6sF=ko z`orTOanmT8roaWnn+cK$bHif_AVevooQ;)_I_?1gn!9jG)`fl>pNhtouAUAr{I z#WYOwQUyC|tR4^4ele?5;G0qlt4TX3&Ew=*TTSwCH%sJZ)vQhiDw-u>mBiyv#wgpg zy)Dbyvh7FKC~!RaHGFeq`HpkX^aInB$JP7sX#-q0aE6BKJKMf*bpsAxOzP+L(>gbz zV*1-E7T^4NDV9AlO!=`Em*m?gi<-~p_kS6!7kOM7lWLmDEl-xa7iQ(z>vg@r_Kcy- z-W}P0v|cZ8WtO9n>-fH2Z}&H(KmY**5I_I{1Q0*~0R#|OE}+l%|Bw9t+a<~WKMNK4 z|H78WzvTZbw>$nm{U6GXh@}79o@eU)k;CQx-LxWr00IagfB*srAb 0 && limit < len(allRecords) { + return allRecords[:limit], nil + } + return allRecords, nil +} + +// GetDownloadStats 获取下载统计信息 +func (s *Store) GetDownloadStats() (map[string]interface{}, error) { + records, err := s.GetAllDownloadRecords() + if err != nil { + return nil, err + } + + stats := map[string]interface{}{ + "total": len(records), + "success": 0, + "failed": 0, + "processing": 0, + "total_files": 0, + } + + for _, record := range records { + switch record.Status { + case "success": + stats["success"] = stats["success"].(int) + 1 + case "failed": + stats["failed"] = stats["failed"].(int) + 1 + case "processing": + stats["processing"] = stats["processing"].(int) + 1 + } + stats["total_files"] = stats["total_files"].(int) + record.TotalFiles + } + + return stats, nil +} + +// ClearOldDownloadRecords 清理指定天数之前的下载记录 +func (s *Store) ClearOldDownloadRecords(days int) (int, error) { + records, err := s.GetAllDownloadRecords() + if err != nil { + return 0, err + } + + cutoffTime := time.Now().AddDate(0, 0, -days) + deleted := 0 + + for _, record := range records { + if record.StartTime.Before(cutoffTime) { + if err := s.DeleteDownloadRecord(record.ID); err == nil { + deleted++ + } + } + } + + return deleted, nil +} diff --git a/storage/site_store.go b/storage/site_store.go new file mode 100644 index 0000000..ba93a19 --- /dev/null +++ b/storage/site_store.go @@ -0,0 +1,107 @@ +package storage + +import ( + "fmt" + "go-site-clone/types" + "time" +) + +// SiteRecord 站点数据记录(带时间戳) +type SiteRecord struct { + types.NginxSiteConfig + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// AddSite 添加站点配置 +func (s *Store) AddSite(site types.NginxSiteConfig) error { + record := SiteRecord{ + NginxSiteConfig: site, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return s.Set(BucketSites, site.Name, record) +} + +// GetSite 获取站点配置 +func (s *Store) GetSite(siteName string) (*types.NginxSiteConfig, error) { + var record SiteRecord + if err := s.Get(BucketSites, siteName, &record); err != nil { + return nil, err + } + return &record.NginxSiteConfig, nil +} + +// UpdateSite 更新站点配置 +func (s *Store) UpdateSite(site types.NginxSiteConfig) error { + var record SiteRecord + + // 尝试获取现有记录以保留创建时间 + if err := s.Get(BucketSites, site.Name, &record); err == nil { + record.NginxSiteConfig = site + record.UpdatedAt = time.Now() + } else { + // 如果不存在,创建新记录 + record = SiteRecord{ + NginxSiteConfig: site, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + } + + return s.Set(BucketSites, site.Name, record) +} + +// DeleteSite 删除站点配置 +func (s *Store) DeleteSite(siteName string) error { + return s.Delete(BucketSites, siteName) +} + +// GetAllSites 获取所有站点配置 +func (s *Store) GetAllSites() ([]types.NginxSiteConfig, error) { + var records []SiteRecord + if err := s.GetAll(BucketSites, &records); err != nil { + return nil, err + } + + sites := make([]types.NginxSiteConfig, len(records)) + for i, record := range records { + sites[i] = record.NginxSiteConfig + } + return sites, nil +} + +// SiteExists 检查站点是否存在 +func (s *Store) SiteExists(siteName string) bool { + return s.Exists(BucketSites, siteName) +} + +// GetSitesByStatus 根据启用状态获取站点 +func (s *Store) GetSitesByStatus(enabled bool) ([]types.NginxSiteConfig, error) { + allSites, err := s.GetAllSites() + if err != nil { + return nil, err + } + + var filtered []types.NginxSiteConfig + for _, site := range allSites { + if site.Enabled == enabled { + filtered = append(filtered, site) + } + } + return filtered, nil +} // UpdateSiteStatus 更新站点启用状态 +func (s *Store) UpdateSiteStatus(siteName string, enabled bool) error { + site, err := s.GetSite(siteName) + if err != nil { + return fmt.Errorf("站点不存在: %v", err) + } + + site.Enabled = enabled + return s.UpdateSite(*site) +} + +// GetSitesCount 获取站点总数 +func (s *Store) GetSitesCount() int { + return s.Count(BucketSites) +} diff --git a/storage/store.go b/storage/store.go new file mode 100644 index 0000000..0f939bc --- /dev/null +++ b/storage/store.go @@ -0,0 +1,187 @@ +package storage + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "time" + + bolt "go.etcd.io/bbolt" +) + +// Bucket 名称常量 +var ( + BucketSites = []byte("sites") + BucketDownload = []byte("download_records") + BucketLogs = []byte("access_logs") + BucketSettings = []byte("settings") +) + +// Store BBolt 存储服务 +type Store struct { + db *bolt.DB +} + +// NewStore 创建新的存储服务 +func NewStore(dbPath string) (*Store, error) { + // 确保数据库目录存在 + dir := filepath.Dir(dbPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, fmt.Errorf("创建数据库目录失败: %v", err) + } + + // 打开数据库 + db, err := bolt.Open(dbPath, 0600, &bolt.Options{ + Timeout: 1 * time.Second, + }) + if err != nil { + return nil, fmt.Errorf("打开数据库失败: %v", err) + } + + // 创建必要的 buckets + err = db.Update(func(tx *bolt.Tx) error { + buckets := [][]byte{BucketSites, BucketDownload, BucketLogs, BucketSettings} + for _, bucket := range buckets { + if _, err := tx.CreateBucketIfNotExists(bucket); err != nil { + return fmt.Errorf("创建 bucket %s 失败: %v", string(bucket), err) + } + } + return nil + }) + + if err != nil { + db.Close() + return nil, err + } + + log.Printf("数据库初始化成功: %s", dbPath) + + return &Store{db: db}, nil +} + +// Close 关闭数据库连接 +func (s *Store) Close() error { + if s.db != nil { + return s.db.Close() + } + return nil +} + +// Set 存储键值对到指定 bucket +func (s *Store) Set(bucket []byte, key string, value interface{}) error { + data, err := json.Marshal(value) + if err != nil { + return fmt.Errorf("序列化数据失败: %v", err) + } + + return s.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + if b == nil { + return fmt.Errorf("bucket %s 不存在", string(bucket)) + } + return b.Put([]byte(key), data) + }) +} + +// Get 从指定 bucket 获取值 +func (s *Store) Get(bucket []byte, key string, dest interface{}) error { + return s.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + if b == nil { + return fmt.Errorf("bucket %s 不存在", string(bucket)) + } + + data := b.Get([]byte(key)) + if data == nil { + return fmt.Errorf("键 %s 不存在", key) + } + + return json.Unmarshal(data, dest) + }) +} + +// Delete 删除指定键 +func (s *Store) Delete(bucket []byte, key string) error { + return s.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + if b == nil { + return fmt.Errorf("bucket %s 不存在", string(bucket)) + } + return b.Delete([]byte(key)) + }) +} + +// GetAll 获取指定 bucket 的所有数据 +func (s *Store) GetAll(bucket []byte, destSlice interface{}) error { + var items []json.RawMessage + + err := s.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + if b == nil { + return fmt.Errorf("bucket %s 不存在", string(bucket)) + } + + return b.ForEach(func(k, v []byte) error { + items = append(items, json.RawMessage(v)) + return nil + }) + }) + + if err != nil { + return err + } + + // 将 items 转换为目标切片类型 + data, err := json.Marshal(items) + if err != nil { + return err + } + + return json.Unmarshal(data, destSlice) +} + +// Exists 检查键是否存在 +func (s *Store) Exists(bucket []byte, key string) bool { + exists := false + s.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + if b == nil { + return nil + } + exists = b.Get([]byte(key)) != nil + return nil + }) + return exists +} + +// Count 统计指定 bucket 的键数量 +func (s *Store) Count(bucket []byte) int { + count := 0 + s.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucket) + if b == nil { + return nil + } + b.ForEach(func(k, v []byte) error { + count++ + return nil + }) + return nil + }) + return count +} + +// Backup 备份数据库到指定路径 +func (s *Store) Backup(backupPath string) error { + // 确保备份目录存在 + dir := filepath.Dir(backupPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("创建备份目录失败: %v", err) + } + + return s.db.View(func(tx *bolt.Tx) error { + return tx.CopyFile(backupPath, 0600) + }) +}