重构:优化记录格式并修复核心功能
- 修改记录存储格式为 [4B len][8B offset][4B CRC][16B UUID][data] - 修复 TopicProcessor 中 WaitGroup 使用错误导致 handler 不执行的问题 - 修复写入保护逻辑,避免 dirtyOffset=-1 时误判为写入中 - 添加统计信息定期持久化功能 - 改进 UTF-8 字符截断处理,防止 CJK 字符乱码 - 优化 Web UI:显示人类可读的文件大小,支持点击外部关闭弹窗 - 重构示例代码,添加 webui 和 webui_integration 示例 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
27
cursor.go
27
cursor.go
@@ -19,11 +19,13 @@ type ProcessCursor struct {
|
||||
startIdx int // 窗口开始索引(已处理的记录索引)
|
||||
endIdx int // 窗口结束索引(当前读到的记录索引)
|
||||
index *RecordIndex // 索引管理器(来自外部)
|
||||
writer *LogWriter // 写入器引用(用于检查写入位置)
|
||||
}
|
||||
|
||||
// NewCursor 创建一个新的日志游标
|
||||
// index: 外部提供的索引管理器,用于快速定位记录
|
||||
func NewCursor(path string, index *RecordIndex) (*ProcessCursor, error) {
|
||||
// writer: 外部提供的写入器引用,用于检查写入位置(可选,为 nil 时不进行写入保护检查)
|
||||
func NewCursor(path string, index *RecordIndex, writer *LogWriter) (*ProcessCursor, error) {
|
||||
if index == nil {
|
||||
return nil, NewValidationError("index", "index cannot be nil", ErrNilParameter)
|
||||
}
|
||||
@@ -40,6 +42,7 @@ func NewCursor(path string, index *RecordIndex) (*ProcessCursor, error) {
|
||||
startIdx: 0,
|
||||
endIdx: 0,
|
||||
index: index,
|
||||
writer: writer,
|
||||
}
|
||||
// 尝试恢复上次位置
|
||||
c.loadPosition()
|
||||
@@ -64,31 +67,39 @@ func (c *ProcessCursor) Next() (*Record, error) {
|
||||
return nil, fmt.Errorf("get offset from index: %w", err)
|
||||
}
|
||||
|
||||
// 写入保护:检查读取位置是否超过当前写入位置
|
||||
dirtyOffset := c.writer.GetDirtyOffset()
|
||||
// 如果正在写入(dirtyOffset >= 0)且记录起始位置 >= 写入位置,说明数据还未完全写入,返回 EOF 等待
|
||||
if dirtyOffset >= 0 && offset >= dirtyOffset {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
// Seek 到记录位置
|
||||
if _, err := c.fd.Seek(offset, 0); err != nil {
|
||||
return nil, fmt.Errorf("seek to offset %d: %w", offset, err)
|
||||
}
|
||||
|
||||
// 读取头部:[4B len][4B CRC][16B UUID] = 24 字节
|
||||
hdr := c.rbuf[:24]
|
||||
// 读取头部:[4B len][8B offset][4B CRC][16B UUID] = 32 字节
|
||||
hdr := c.rbuf[:32]
|
||||
if _, err := io.ReadFull(c.fd, hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rec Record
|
||||
rec.Len = binary.LittleEndian.Uint32(hdr[0:4])
|
||||
rec.CRC = binary.LittleEndian.Uint32(hdr[4:8])
|
||||
// hdr[4:12] 是 offset,读取时不需要使用
|
||||
rec.CRC = binary.LittleEndian.Uint32(hdr[12:16])
|
||||
|
||||
// 读取并校验 UUID
|
||||
copy(rec.UUID[:], hdr[8:24])
|
||||
copy(rec.UUID[:], hdr[16:32])
|
||||
if _, err := uuid.FromBytes(rec.UUID[:]); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrInvalidUUID, err)
|
||||
}
|
||||
|
||||
// 如果数据大于缓冲区,分配新的 buffer
|
||||
var payload []byte
|
||||
if int(rec.Len) <= len(c.rbuf)-24 {
|
||||
payload = c.rbuf[24 : 24+rec.Len]
|
||||
if int(rec.Len) <= len(c.rbuf)-32 {
|
||||
payload = c.rbuf[32 : 32+rec.Len]
|
||||
} else {
|
||||
payload = make([]byte, rec.Len)
|
||||
}
|
||||
@@ -97,7 +108,7 @@ func (c *ProcessCursor) Next() (*Record, error) {
|
||||
return nil, err
|
||||
}
|
||||
if crc32.ChecksumIEEE(payload) != rec.CRC {
|
||||
return nil, ErrCRCMismatch
|
||||
return nil, fmt.Errorf("%w: offset=%d", ErrCRCMismatch, offset)
|
||||
}
|
||||
rec.Data = append([]byte(nil), payload...) // 复制出去,复用 buffer
|
||||
|
||||
|
||||
Reference in New Issue
Block a user