package app import ( "database/sql" "encoding/json" "fmt" "os" "path/filepath" _ "modernc.org/sqlite" ) type Store struct{ db *sql.DB } func OpenStore(dir string) (*Store, error) { if err := os.MkdirAll(dir, 0700); err != nil { return nil, err } p := filepath.Join(dir, "clashweb.sqlite") f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } f.Close() db, err := sql.Open("sqlite", p) if err != nil { return nil, err } db.SetMaxOpenConns(1) _, err = db.Exec(`PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000; CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS sessions (token_hash TEXT PRIMARY KEY, csrf TEXT NOT NULL, expires INTEGER NOT NULL); PRAGMA user_version=1;`) if err != nil { db.Close() return nil, err } return &Store{db: db}, nil } func (s *Store) Close() error { return s.db.Close() } func (s *Store) Get(key string) (string, error) { var value string err := s.db.QueryRow("SELECT value FROM kv WHERE key=?", key).Scan(&value) if err == sql.ErrNoRows { return "", nil } return value, err } func (s *Store) Set(key, value string) error { _, err := s.db.Exec("INSERT INTO kv(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", key, value) return err } func (s *Store) Load() (State, error) { text, err := s.Get("state") if err != nil { return State{}, err } if text == "" { return defaultState(), nil } var state State if err := json.Unmarshal([]byte(text), &state); err != nil { return State{}, fmt.Errorf("数据库中的配置损坏: %w", err) } return state, validateState(state) } func (s *Store) Save(state State) error { b, err := json.Marshal(state) if err != nil { return err } return s.Set("state", string(b)) }