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,83 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
endTag = "NNNN"
|
||||
expression = "(?s)ZCZC.*?NNNN"
|
||||
maxBufferSize = 65536
|
||||
bufferTrimSize = 32768
|
||||
)
|
||||
|
||||
// Parser handles telegram extraction from raw serial data.
|
||||
type Parser struct {
|
||||
mu sync.Mutex
|
||||
buffer strings.Builder
|
||||
exp *regexp.Regexp
|
||||
rawLog io.Writer
|
||||
}
|
||||
|
||||
// New creates a new Parser.
|
||||
func New(rawLog io.Writer) *Parser {
|
||||
return &Parser{
|
||||
exp: regexp.MustCompile(expression),
|
||||
rawLog: rawLog,
|
||||
}
|
||||
}
|
||||
|
||||
// Append adds raw data to the internal buffer and returns any complete
|
||||
// telegrams that were found. Returns nil if no complete telegram is ready.
|
||||
func (p *Parser) Append(data string) []string {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.buffer.Len() > maxBufferSize {
|
||||
// Truncate to prevent unbounded growth
|
||||
existing := p.buffer.String()
|
||||
p.buffer.Reset()
|
||||
p.buffer.WriteString(existing[len(existing)-bufferTrimSize:])
|
||||
}
|
||||
|
||||
p.buffer.WriteString(data)
|
||||
p.buffer.WriteByte('\n')
|
||||
|
||||
return p.extract()
|
||||
}
|
||||
|
||||
// extract finds and returns all complete telegrams in the buffer.
|
||||
func (p *Parser) extract() []string {
|
||||
content := p.buffer.String()
|
||||
|
||||
if !strings.Contains(content, endTag) {
|
||||
return nil
|
||||
}
|
||||
|
||||
loc := p.exp.FindStringIndex(content)
|
||||
if loc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
telegram := content[loc[0]:loc[1]]
|
||||
telegram = removeEmpty(telegram) + "\n\n\n"
|
||||
|
||||
// Write raw log
|
||||
if p.rawLog != nil {
|
||||
p.rawLog.Write([]byte(telegram + "\n"))
|
||||
}
|
||||
|
||||
// Keep remaining data after the matched telegram
|
||||
remaining := content[loc[1]:]
|
||||
p.buffer.Reset()
|
||||
p.buffer.WriteString(remaining)
|
||||
|
||||
return []string{telegram}
|
||||
}
|
||||
|
||||
func removeEmpty(s string) string {
|
||||
return regexp.MustCompile(`[\t\r\n]+`).ReplaceAllString(strings.TrimSpace(s), "\n")
|
||||
}
|
||||
Reference in New Issue
Block a user