mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
refactor: 0.1.0-beta
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
// store.go 管理调试捕获的内存索引、SQLite 持久化和订阅通知。
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// exchangeStore 保存有限内存窗口以及可选的持久化数据库连接。
|
||||
type exchangeStore struct {
|
||||
mu sync.RWMutex
|
||||
max int
|
||||
order []string
|
||||
exchanges map[string]*Exchange
|
||||
subscribers map[chan storeEvent]struct{}
|
||||
db *sql.DB
|
||||
databasePath string
|
||||
lastError string
|
||||
}
|
||||
|
||||
// newExchangeStore 创建仅使用内存的捕获存储。
|
||||
func newExchangeStore(max int) *exchangeStore {
|
||||
return &exchangeStore{
|
||||
max: max,
|
||||
exchanges: make(map[string]*Exchange),
|
||||
subscribers: make(map[chan storeEvent]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// newPersistentExchangeStore 创建 SQLite 持久化捕获存储并恢复最近记录。
|
||||
func newPersistentExchangeStore(path string, max int) (*exchangeStore, error) {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("SQLite 数据库路径不能为空")
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return nil, fmt.Errorf("创建 SQLite 数据目录失败: %w", err)
|
||||
}
|
||||
db, err := sql.Open("sqlite", path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开 SQLite 数据库失败: %w", err)
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(1)
|
||||
store := newExchangeStore(max)
|
||||
store.db = db
|
||||
store.databasePath = path
|
||||
if err := store.initializeDatabase(context.Background()); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := store.backfillDecodedExchanges(context.Background()); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := store.loadRecent(context.Background()); err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// backfillDecodedExchanges 为旧记录补齐解码视图并写回数据库。
|
||||
func (store *exchangeStore) backfillDecodedExchanges(ctx context.Context) error {
|
||||
rows, err := store.db.QueryContext(ctx, "SELECT payload_json FROM exchanges")
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取待回填的 SQLite 抓包记录失败: %w", err)
|
||||
}
|
||||
var exchanges []Exchange
|
||||
for rows.Next() {
|
||||
var payload []byte
|
||||
if err := rows.Scan(&payload); err != nil {
|
||||
_ = rows.Close()
|
||||
return err
|
||||
}
|
||||
var exchange Exchange
|
||||
if err := json.Unmarshal(payload, &exchange); err != nil {
|
||||
_ = rows.Close()
|
||||
return fmt.Errorf("解析待回填的 SQLite 抓包记录失败: %w", err)
|
||||
}
|
||||
if hydrateStoredExchange(&exchange) {
|
||||
exchanges = append(exchanges, exchange)
|
||||
}
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(exchanges) == 0 {
|
||||
return nil
|
||||
}
|
||||
tx, err := store.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
committed := false
|
||||
defer func() {
|
||||
if !committed {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
for index := range exchanges {
|
||||
payload, marshalErr := json.Marshal(&exchanges[index])
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `UPDATE exchanges SET payload_json = ?, conversation_id = ?,
|
||||
request_id = ?, updated_at_ms = ? WHERE id = ?`, payload, exchanges[index].ConversationID,
|
||||
exchanges[index].RequestID, time.Now().UnixMilli(), exchanges[index].ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
committed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// initializeDatabase 创建调试器使用的 SQLite 表结构。
|
||||
func (store *exchangeStore) initializeDatabase(ctx context.Context) error {
|
||||
for _, statement := range []string{
|
||||
"PRAGMA journal_mode = WAL",
|
||||
"PRAGMA busy_timeout = 5000",
|
||||
"PRAGMA secure_delete = ON",
|
||||
`CREATE TABLE IF NOT EXISTS exchanges (
|
||||
id TEXT PRIMARY KEY,
|
||||
started_at_ms INTEGER NOT NULL,
|
||||
conversation_id TEXT NOT NULL DEFAULT '',
|
||||
request_id TEXT NOT NULL DEFAULT '',
|
||||
state TEXT NOT NULL DEFAULT '',
|
||||
request_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
response_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
payload_json BLOB NOT NULL,
|
||||
updated_at_ms INTEGER NOT NULL
|
||||
)`,
|
||||
"CREATE INDEX IF NOT EXISTS exchanges_conversation_started_idx ON exchanges(conversation_id, started_at_ms DESC)",
|
||||
"CREATE INDEX IF NOT EXISTS exchanges_request_idx ON exchanges(request_id)",
|
||||
} {
|
||||
if _, err := store.db.ExecContext(ctx, statement); err != nil {
|
||||
return fmt.Errorf("初始化 SQLite 数据库失败: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadRecent 从数据库恢复内存窗口中的最新捕获。
|
||||
func (store *exchangeStore) loadRecent(ctx context.Context) error {
|
||||
rows, err := store.db.QueryContext(ctx, `SELECT payload_json, conversation_id
|
||||
FROM exchanges ORDER BY started_at_ms DESC, id DESC LIMIT ?`, store.max)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取 SQLite 抓包记录失败: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var payload []byte
|
||||
var conversationID string
|
||||
if err := rows.Scan(&payload, &conversationID); err != nil {
|
||||
return err
|
||||
}
|
||||
var exchange Exchange
|
||||
if err := json.Unmarshal(payload, &exchange); err != nil {
|
||||
return fmt.Errorf("解析 SQLite 抓包记录失败: %w", err)
|
||||
}
|
||||
exchange.ConversationID = conversationID
|
||||
store.exchanges[exchange.ID] = &exchange
|
||||
store.order = append(store.order, exchange.ID)
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
// create 添加一条新的捕获并通知订阅者。
|
||||
func (store *exchangeStore) create(exchange *Exchange) {
|
||||
store.mu.Lock()
|
||||
store.exchanges[exchange.ID] = exchange
|
||||
store.order = append([]string{exchange.ID}, store.order...)
|
||||
for len(store.order) > store.max {
|
||||
oldest := store.order[len(store.order)-1]
|
||||
store.order = store.order[:len(store.order)-1]
|
||||
delete(store.exchanges, oldest)
|
||||
}
|
||||
store.persistLocked(exchange)
|
||||
store.mu.Unlock()
|
||||
store.publish(storeEvent{Type: "created", ID: exchange.ID})
|
||||
}
|
||||
|
||||
// update 持久化修改并发布最终捕获快照。
|
||||
func (store *exchangeStore) update(id string, apply func(*Exchange)) {
|
||||
store.updateWithPersistence(id, apply, true)
|
||||
}
|
||||
|
||||
// updateTransient 只更新内存并发布流式过程快照。
|
||||
func (store *exchangeStore) updateTransient(id string, apply func(*Exchange)) {
|
||||
store.updateWithPersistence(id, apply, false)
|
||||
}
|
||||
|
||||
// updateWithPersistence 在统一锁内完成修改、关联和可选持久化。
|
||||
func (store *exchangeStore) updateWithPersistence(id string, apply func(*Exchange), persist bool) {
|
||||
store.mu.Lock()
|
||||
exchange := store.exchanges[id]
|
||||
if exchange == nil && store.db != nil {
|
||||
var err error
|
||||
exchange, err = store.loadPersistedLocked(id)
|
||||
if err != nil {
|
||||
store.lastError = err.Error()
|
||||
}
|
||||
if exchange != nil {
|
||||
store.exchanges[id] = exchange
|
||||
store.order = append([]string{id}, store.order...)
|
||||
for len(store.order) > store.max {
|
||||
oldest := store.order[len(store.order)-1]
|
||||
store.order = store.order[:len(store.order)-1]
|
||||
delete(store.exchanges, oldest)
|
||||
}
|
||||
}
|
||||
}
|
||||
if exchange != nil {
|
||||
previousRequestID := exchange.RequestID
|
||||
previousConversationID := exchange.ConversationID
|
||||
apply(exchange)
|
||||
if exchange.RequestID != previousRequestID || exchange.ConversationID != previousConversationID {
|
||||
store.associateConversationLocked(exchange)
|
||||
}
|
||||
if persist {
|
||||
store.persistLocked(exchange)
|
||||
}
|
||||
}
|
||||
store.mu.Unlock()
|
||||
store.publish(storeEvent{Type: "updated", ID: id})
|
||||
}
|
||||
|
||||
// summaries 返回按时间倒序排列的请求摘要。
|
||||
Reference in New Issue
Block a user