package store import ( "encoding/json" "fmt" "os" "path/filepath" "sync" "time" "openvpn-manager/internal/model" ) // Store 简易 JSON 文件存储: // 适合中小规模运维工具,无需引入数据库。所有变更通过 RWMutex 保护。 type Store struct { mu sync.RWMutex path string data Data writeCh chan struct{} } type Data struct { Instances []model.Instance `json:"instances"` Users []model.VPNUser `json:"users"` Audits []model.AuditLog `json:"audits"` ConnLogs []model.ConnectionLog `json:"conn_logs"` Backups []model.Backup `json:"backups"` Admins []model.AdminUser `json:"admins"` } func Open(path string) (*Store, error) { s := &Store{path: path} if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err } if _, err := os.Stat(path); os.IsNotExist(err) { s.data = Data{} if err := s.flush(); err != nil { return nil, err } return s, nil } b, err := os.ReadFile(path) if err != nil { return nil, err } if len(b) == 0 { s.data = Data{} return s, nil } if err := json.Unmarshal(b, &s.data); err != nil { return nil, fmt.Errorf("parse db: %w", err) } return s, nil } func (s *Store) flush() error { b, err := json.MarshalIndent(s.data, "", " ") if err != nil { return err } tmp := s.path + ".tmp" if err := os.WriteFile(tmp, b, 0o600); err != nil { return err } return os.Rename(tmp, s.path) } // ---- Instances ---- func (s *Store) ListInstances() []model.Instance { s.mu.RLock() defer s.mu.RUnlock() out := make([]model.Instance, len(s.data.Instances)) copy(out, s.data.Instances) return out } func (s *Store) GetInstance(id string) (*model.Instance, error) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.data.Instances { if s.data.Instances[i].ID == id { in := s.data.Instances[i] return &in, nil } } return nil, fmt.Errorf("instance %s not found", id) } func (s *Store) GetInstanceByName(name string) (*model.Instance, error) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.data.Instances { if s.data.Instances[i].Name == name { in := s.data.Instances[i] return &in, nil } } return nil, fmt.Errorf("instance %s not found", name) } func (s *Store) UpsertInstance(in model.Instance) error { s.mu.Lock() defer s.mu.Unlock() for i := range s.data.Instances { if s.data.Instances[i].ID == in.ID { s.data.Instances[i] = in return s.flush() } } s.data.Instances = append(s.data.Instances, in) return s.flush() } func (s *Store) DeleteInstance(id string) error { s.mu.Lock() defer s.mu.Unlock() idx := -1 for i := range s.data.Instances { if s.data.Instances[i].ID == id { idx = i break } } if idx < 0 { return fmt.Errorf("instance %s not found", id) } s.data.Instances = append(s.data.Instances[:idx], s.data.Instances[idx+1:]...) // 同步删除其用户 users := s.data.Users[:0] for _, u := range s.data.Users { if u.InstanceID != id { users = append(users, u) } } s.data.Users = users return s.flush() } // ---- Users ---- func (s *Store) ListUsers(instanceID string) []model.VPNUser { s.mu.RLock() defer s.mu.RUnlock() out := []model.VPNUser{} for _, u := range s.data.Users { if instanceID == "" || u.InstanceID == instanceID { u.PasswordHash = "" // 不向外暴露 hash out = append(out, u) } } return out } func (s *Store) GetUser(id string) (*model.VPNUser, error) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.data.Users { if s.data.Users[i].ID == id { u := s.data.Users[i] return &u, nil } } return nil, fmt.Errorf("user %s not found", id) } func (s *Store) GetUserByCN(instanceID, cn string) (*model.VPNUser, error) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.data.Users { if s.data.Users[i].InstanceID == instanceID && s.data.Users[i].Username == cn { u := s.data.Users[i] return &u, nil } } return nil, fmt.Errorf("user %s/%s not found", instanceID, cn) } func (s *Store) UpsertUser(u model.VPNUser) error { s.mu.Lock() defer s.mu.Unlock() for i := range s.data.Users { if s.data.Users[i].ID == u.ID { s.data.Users[i] = u return s.flush() } } s.data.Users = append(s.data.Users, u) return s.flush() } func (s *Store) DeleteUser(id string) error { s.mu.Lock() defer s.mu.Unlock() idx := -1 for i := range s.data.Users { if s.data.Users[i].ID == id { idx = i break } } if idx < 0 { return fmt.Errorf("user %s not found", id) } s.data.Users = append(s.data.Users[:idx], s.data.Users[idx+1:]...) return s.flush() } // ---- Audit ---- func (s *Store) AppendAudit(a model.AuditLog) error { s.mu.Lock() defer s.mu.Unlock() s.data.Audits = append(s.data.Audits, a) // 仅保留最近 5000 条 if len(s.data.Audits) > 5000 { s.data.Audits = s.data.Audits[len(s.data.Audits)-5000:] } return s.flush() } func (s *Store) ListAudits(limit int) []model.AuditLog { s.mu.RLock() defer s.mu.RUnlock() if limit <= 0 || limit > len(s.data.Audits) { limit = len(s.data.Audits) } out := make([]model.AuditLog, limit) copy(out, s.data.Audits[len(s.data.Audits)-limit:]) // 倒序 for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 { out[i], out[j] = out[j], out[i] } return out } // ---- Connection Logs ---- func (s *Store) AppendConnLog(c model.ConnectionLog) error { s.mu.Lock() defer s.mu.Unlock() s.data.ConnLogs = append(s.data.ConnLogs, c) if len(s.data.ConnLogs) > 20000 { s.data.ConnLogs = s.data.ConnLogs[len(s.data.ConnLogs)-20000:] } return s.flush() } func (s *Store) ListConnLogs(instanceID string, limit int) []model.ConnectionLog { s.mu.RLock() defer s.mu.RUnlock() out := []model.ConnectionLog{} for i := len(s.data.ConnLogs) - 1; i >= 0 && len(out) < limit; i-- { c := s.data.ConnLogs[i] if instanceID == "" || c.InstanceID == instanceID { out = append(out, c) } } return out } func (s *Store) FindActiveConn(instanceID, commonName string) *model.ConnectionLog { s.mu.RLock() defer s.mu.RUnlock() for i := len(s.data.ConnLogs) - 1; i >= 0; i-- { c := s.data.ConnLogs[i] if c.InstanceID == instanceID && c.CommonName == commonName && c.DisconnectedAt == nil { cc := c return &cc } } return nil } func (s *Store) CloseActiveConn(instanceID, commonName string, at time.Time) error { s.mu.Lock() defer s.mu.Unlock() for i := len(s.data.ConnLogs) - 1; i >= 0; i-- { c := &s.data.ConnLogs[i] if c.InstanceID == instanceID && c.CommonName == commonName && c.DisconnectedAt == nil { c.DisconnectedAt = &at return s.flush() } } return nil } // ListActiveConns 返回指定实例所有未断开的连接。 func (s *Store) ListActiveConns(instanceID string) []model.ConnectionLog { s.mu.RLock() defer s.mu.RUnlock() var out []model.ConnectionLog for i := len(s.data.ConnLogs) - 1; i >= 0; i-- { c := s.data.ConnLogs[i] if c.InstanceID == instanceID && c.DisconnectedAt == nil { out = append(out, c) } } return out } // UpdateActiveConnStats 更新活跃连接的流量统计。 func (s *Store) UpdateActiveConnStats(instanceID, commonName string, bytesIn, bytesOut int64) error { s.mu.Lock() defer s.mu.Unlock() for i := len(s.data.ConnLogs) - 1; i >= 0; i-- { c := &s.data.ConnLogs[i] if c.InstanceID == instanceID && c.CommonName == commonName && c.DisconnectedAt == nil { c.BytesIn = bytesIn c.BytesOut = bytesOut return nil // 不 flush,避免高频写盘 } } return nil } // ---- Backups ---- func (s *Store) ListBackups() []model.Backup { s.mu.RLock() defer s.mu.RUnlock() out := make([]model.Backup, len(s.data.Backups)) copy(out, s.data.Backups) return out } func (s *Store) AddBackup(b model.Backup) error { s.mu.Lock() defer s.mu.Unlock() s.data.Backups = append(s.data.Backups, b) return s.flush() } func (s *Store) DeleteBackup(id string) error { s.mu.Lock() defer s.mu.Unlock() idx := -1 for i := range s.data.Backups { if s.data.Backups[i].ID == id { idx = i break } } if idx < 0 { return fmt.Errorf("backup %s not found", id) } s.data.Backups = append(s.data.Backups[:idx], s.data.Backups[idx+1:]...) return s.flush() } // ---- Admins ---- func (s *Store) ListAdmins() []model.AdminUser { s.mu.RLock() defer s.mu.RUnlock() out := make([]model.AdminUser, len(s.data.Admins)) copy(out, s.data.Admins) // 不返回哈希,调用方负责清空 PasswordHash for i := range out { out[i].PasswordHash = "" } return out } func (s *Store) GetAdmin(id string) (*model.AdminUser, error) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.data.Admins { if s.data.Admins[i].ID == id { a := s.data.Admins[i] return &a, nil } } return nil, fmt.Errorf("admin %s not found", id) } // GetAdminByUsername 包含哈希的内部副本。仅供登录校验使用。 func (s *Store) GetAdminByUsername(username string) (*model.AdminUser, error) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.data.Admins { if s.data.Admins[i].Username == username { a := s.data.Admins[i] return &a, nil } } return nil, fmt.Errorf("admin %s not found", username) } func (s *Store) UpsertAdmin(a model.AdminUser) error { s.mu.Lock() defer s.mu.Unlock() for i := range s.data.Admins { if s.data.Admins[i].ID == a.ID { s.data.Admins[i] = a return s.flush() } } s.data.Admins = append(s.data.Admins, a) return s.flush() } func (s *Store) DeleteAdmin(id string) error { s.mu.Lock() defer s.mu.Unlock() idx := -1 for i := range s.data.Admins { if s.data.Admins[i].ID == id { idx = i break } } if idx < 0 { return fmt.Errorf("admin %s not found", id) } s.data.Admins = append(s.data.Admins[:idx], s.data.Admins[idx+1:]...) return s.flush() } func (s *Store) AdminCount() int { s.mu.RLock() defer s.mu.RUnlock() return len(s.data.Admins) }