新增本地持久化存储
This commit is contained in:
BIN
data/site-clone.db
Normal file
BIN
data/site-clone.db
Normal file
Binary file not shown.
1
go.mod
1
go.mod
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/go-rod/stealth v0.4.9
|
github.com/go-rod/stealth v0.4.9
|
||||||
github.com/mattn/go-sqlite3 v1.14.32
|
github.com/mattn/go-sqlite3 v1.14.32
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.27
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.27
|
||||||
|
go.etcd.io/bbolt v1.4.3
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -137,6 +137,8 @@ github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY
|
|||||||
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
|
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
|
||||||
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
|
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
@@ -173,6 +175,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|||||||
171
storage/download_store.go
Normal file
171
storage/download_store.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DownloadRecord 下载记录
|
||||||
|
type DownloadRecord struct {
|
||||||
|
ID string `json:"id"` // 唯一标识
|
||||||
|
URL string `json:"url"` // 下载的 URL
|
||||||
|
SiteName string `json:"site_name"` // 站点名称
|
||||||
|
Status string `json:"status"` // 状态:success, failed, processing
|
||||||
|
TotalFiles int `json:"total_files"` // 总文件数
|
||||||
|
Downloaded int `json:"downloaded"` // 已下载数
|
||||||
|
CSSCount int `json:"css_count"` // CSS 文件数
|
||||||
|
ScriptCount int `json:"script_count"` // JS 文件数
|
||||||
|
ImageCount int `json:"image_count"` // 图片文件数
|
||||||
|
VideoCount int `json:"video_count"` // 视频文件数
|
||||||
|
ErrorMsg string `json:"error_msg"` // 错误信息
|
||||||
|
StartTime time.Time `json:"start_time"` // 开始时间
|
||||||
|
EndTime time.Time `json:"end_time"` // 结束时间
|
||||||
|
Duration int64 `json:"duration"` // 耗时(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDownloadRecord 添加下载记录
|
||||||
|
func (s *Store) AddDownloadRecord(record DownloadRecord) error {
|
||||||
|
if record.ID == "" {
|
||||||
|
record.ID = fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
if record.StartTime.IsZero() {
|
||||||
|
record.StartTime = time.Now()
|
||||||
|
}
|
||||||
|
return s.Set(BucketDownload, record.ID, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownloadRecord 获取下载记录
|
||||||
|
func (s *Store) GetDownloadRecord(id string) (*DownloadRecord, error) {
|
||||||
|
var record DownloadRecord
|
||||||
|
if err := s.Get(BucketDownload, id, &record); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDownloadRecord 更新下载记录
|
||||||
|
func (s *Store) UpdateDownloadRecord(record DownloadRecord) error {
|
||||||
|
if record.ID == "" {
|
||||||
|
return fmt.Errorf("记录 ID 不能为空")
|
||||||
|
}
|
||||||
|
return s.Set(BucketDownload, record.ID, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDownloadRecord 删除下载记录
|
||||||
|
func (s *Store) DeleteDownloadRecord(id string) error {
|
||||||
|
return s.Delete(BucketDownload, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllDownloadRecords 获取所有下载记录
|
||||||
|
func (s *Store) GetAllDownloadRecords() ([]DownloadRecord, error) {
|
||||||
|
var records []DownloadRecord
|
||||||
|
if err := s.GetAll(BucketDownload, &records); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownloadRecordsBySite 根据站点名称获取下载记录
|
||||||
|
func (s *Store) GetDownloadRecordsBySite(siteName string) ([]DownloadRecord, error) {
|
||||||
|
allRecords, err := s.GetAllDownloadRecords()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered []DownloadRecord
|
||||||
|
for _, record := range allRecords {
|
||||||
|
if record.SiteName == siteName {
|
||||||
|
filtered = append(filtered, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownloadRecordsByStatus 根据状态获取下载记录
|
||||||
|
func (s *Store) GetDownloadRecordsByStatus(status string) ([]DownloadRecord, error) {
|
||||||
|
allRecords, err := s.GetAllDownloadRecords()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered []DownloadRecord
|
||||||
|
for _, record := range allRecords {
|
||||||
|
if record.Status == status {
|
||||||
|
filtered = append(filtered, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecentDownloadRecords 获取最近的 N 条下载记录
|
||||||
|
func (s *Store) GetRecentDownloadRecords(limit int) ([]DownloadRecord, error) {
|
||||||
|
allRecords, err := s.GetAllDownloadRecords()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间倒序排序
|
||||||
|
for i := 0; i < len(allRecords)-1; i++ {
|
||||||
|
for j := i + 1; j < len(allRecords); j++ {
|
||||||
|
if allRecords[i].StartTime.Before(allRecords[j].StartTime) {
|
||||||
|
allRecords[i], allRecords[j] = allRecords[j], allRecords[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit > 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
|
||||||
|
}
|
||||||
107
storage/site_store.go
Normal file
107
storage/site_store.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
187
storage/store.go
Normal file
187
storage/store.go
Normal file
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user