fix: comprehensive bug fixes and architecture restructuring
Phase 1 — Bug fixes (7 bugs): - Bug 1: Pulsar mode data loss — always persist to SQLite regardless of mode - Bug 2: PulsarSend closing TCP client — use independent resetPulsarProducer() - Bug 3: Greedy regex — use non-greedy (?s)ZCZC.*?NNNN - Bug 4: Data after NNNN discarded — keep remaining buffer data - Bug 5: strings.Index > 0 boundary — use strings.Contains - Bug 6: Variable shadowing in PulsarSend — use = not := - Bug 7: Accept failure nil panic — add continue + retry logic Phase 2 — Architecture restructuring: - Split utils/ into config/, serial/, telegram/, storage/, transport/ - Define Sender, Repository, Reader interfaces - Introduce app/ layer with context.Context lifecycle - Replace spinlock with sync.Mutex - Unified Config struct replaces 15+ global vars Phase 3 — Testing & tooling: - telegram/parser_test.go (7 test cases) - storage/store_test.go (5 test cases) - transport/transport_test.go (4 test cases) - Taskfile: add test, test-race, test-cover tasks - Go version: 1.15 -> 1.21
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Repository defines the interface for telegram persistence.
|
||||
type Repository interface {
|
||||
Insert(telegram string) error
|
||||
LoadUnprocessed() ([]Telegram, error)
|
||||
MarkProcessed(id int64) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Telegram represents a stored telegram record.
|
||||
type Telegram struct {
|
||||
ID int64
|
||||
Text string
|
||||
}
|
||||
|
||||
// Store implements Repository using SQLite.
|
||||
type Store struct {
|
||||
mu sync.Mutex
|
||||
dbFile string
|
||||
db *sql.DB
|
||||
initTable bool
|
||||
}
|
||||
|
||||
const (
|
||||
driverName = "sqlite3"
|
||||
tableDDL = `
|
||||
create table IF NOT EXISTS telegram (
|
||||
[tele_id] INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
[tele_recv_time] TIMESTAMP NOT NULL DEFAULT (datetime('now', 'localtime')),
|
||||
[tele_processed] int(1) NOT NULL DEFAULT 0,
|
||||
[tele_text] TEXT NOT NULL
|
||||
)
|
||||
`
|
||||
insertSQL = "insert into telegram (tele_text) values (?)"
|
||||
countSQL = "select count(*) from telegram where tele_processed=0"
|
||||
loadSQL = "select tele_id, tele_text from telegram where tele_processed=0 Limit 100"
|
||||
updateSQL = "update telegram set tele_processed = 1 where tele_id=?"
|
||||
)
|
||||
|
||||
// New opens or creates a SQLite store.
|
||||
func New(dbFile string, init bool) (*Store, error) {
|
||||
if init {
|
||||
os.Remove(dbFile)
|
||||
}
|
||||
|
||||
db, err := sql.Open(driverName, dbFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := db.Exec(tableDDL); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Store{
|
||||
dbFile: dbFile,
|
||||
db: db,
|
||||
initTable: init,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Insert saves a telegram to the database.
|
||||
func (s *Store) Insert(telegram string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
stmt, err := s.db.Prepare(insertSQL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(telegram)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadUnprocessed returns up to 100 unprocessed telegrams.
|
||||
func (s *Store) LoadUnprocessed() ([]Telegram, error) {
|
||||
rows, err := s.db.Query(loadSQL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var telegrams []Telegram
|
||||
for rows.Next() {
|
||||
var t Telegram
|
||||
if err := rows.Scan(&t.ID, &t.Text); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
telegrams = append(telegrams, t)
|
||||
}
|
||||
return telegrams, nil
|
||||
}
|
||||
|
||||
// MarkProcessed marks a telegram as processed.
|
||||
func (s *Store) MarkProcessed(id int64) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
stmt, err := s.db.Prepare(updateSQL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(id)
|
||||
return err
|
||||
}
|
||||
|
||||
// CountUnprocessed returns the number of unprocessed telegrams.
|
||||
func (s *Store) CountUnprocessed() (int64, error) {
|
||||
var count int64
|
||||
err := s.db.QueryRow(countSQL).Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Close closes the database connection.
|
||||
func (s *Store) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user