重构:重命名核心组件并增强查询功能

主要更改:

1. 核心重命名
   - Seqlog -> LogHub (更准确地反映其作为日志中枢的角色)
   - NewSeqlog() -> NewLogHub()
   - LogCursor -> ProcessCursor (更准确地反映其用于处理场景)
   - seqlog_manager.go -> loghub.go (文件名与结构体名对应)

2. TopicProcessor.Reset 增强
   - 如果正在运行且没有待处理的日志,会自动停止后重置
   - 如果有待处理的日志,返回详细错误(显示已处理/总记录数)
   - 简化了 LogHub.ResetTopic,移除显式 Stop 调用

3. 新增查询方法
   - TopicProcessor.QueryFromFirst(count) - 从第一条记录向索引递增方向查询
   - TopicProcessor.QueryFromLast(count) - 从最后一条记录向索引递减方向查询
   - LogHub.QueryFromFirst(topic, count)
   - LogHub.QueryFromLast(topic, count)

4. 测试覆盖
   - 添加 query_test.go - QueryFromProcessing 测试
   - 添加 TestQueryFromFirstAndLast - TopicProcessor 查询测试
   - 添加 TestLogHubQueryFromFirstAndLast - LogHub 查询测试
   - 添加 TestTopicResetWithPendingRecords - Reset 增强功能测试

5. 示例代码
   - 添加 example/get_record/ - 演示 QueryFromProcessing 用法
   - 更新所有示例以使用 LogHub 和新 API

所有测试通过 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 13:26:21 +08:00
parent dfdc27c67f
commit 90cc9e21c9
14 changed files with 795 additions and 275 deletions

View File

@@ -14,7 +14,7 @@ import (
)
var (
seq *seqlog.Seqlog
seq *seqlog.LogHub
logger *slog.Logger
)
@@ -25,7 +25,7 @@ func main() {
}))
// 创建 Seqlog
seq = seqlog.NewSeqlog("logs", logger, func(topic string, rec *seqlog.Record) error {
seq = seqlog.NewLogHub("logs", logger, func(topic string, rec *seqlog.Record) error {
// 简单的日志处理:只打印摘要信息
dataPreview := string(rec.Data)
if len(dataPreview) > 100 {
@@ -155,6 +155,12 @@ func formatBytes(bytes int64) string {
return fmt.Sprintf("%.2f MB", float64(bytes)/1024/1024)
}
type Record struct {
Index int `json:"index"`
Status string `json:"status"`
Data string `json:"data"`
}
// 首页
func handleIndex(w http.ResponseWriter, r *http.Request) {
html := `<!DOCTYPE html>
@@ -366,7 +372,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
// 选择 topic
function selectTopic(topic) {
currentTopic = topic;
direction = 'forward'; // 切换 topic 时重置方向
direction = ''; // 切换 topic 时重置方向
startIndex = null;
endIndex = null;
@@ -405,14 +411,14 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
const forward = document.getElementById('forwardCount').value;
// 构建查询 URL
let url = '/api/query?topic=' + currentTopic +
'&backward=' + backward + '&forward=' + forward +
'&direction=' + direction;
let url = '/api/query?topic=' + currentTopic;
if (direction === 'backward' && startIndex != null) {
url += '&index=' + startIndex;
url += '&direction=backward&index=' + startIndex + '&count=' + backward;
} else if (direction === 'forward' && endIndex != null) {
url += '&index=' + endIndex;
url += '&direction=forward&index=' + endIndex + '&count=' + forward;
} else {
url += '&count=10';
}
const response = await fetch(url);
@@ -449,11 +455,16 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
}).join('');
if (data.records.length > 0) {
startIndex = data.records[0].index;
endIndex = data.records[data.records.length - 1].index;
} else {
startIndex = null;
endIndex = null;
if (startIndex === null) {
startIndex = data.records[0].index;
} else {
startIndex = Math.min(startIndex, data.records[0].index);
}
if (endIndex === null) {
endIndex = data.records[data.records.length - 1].index;
} else {
endIndex = Math.max(endIndex, data.records[data.records.length - 1].index);
}
}
container.innerHTML = html;
@@ -526,14 +537,10 @@ func handleQuery(w http.ResponseWriter, r *http.Request) {
// 获取查询参数
indexParam := r.URL.Query().Get("index")
direction := r.URL.Query().Get("direction")
backward, _ := strconv.Atoi(r.URL.Query().Get("backward"))
forward, _ := strconv.Atoi(r.URL.Query().Get("forward"))
count, _ := strconv.Atoi(r.URL.Query().Get("count"))
if backward == 0 {
backward = 10
}
if forward == 0 {
forward = 10
if count <= 0 {
count = 10
}
// 获取 processor
@@ -546,79 +553,68 @@ func handleQuery(w http.ResponseWriter, r *http.Request) {
// 获取记录总数
totalCount := processor.GetRecordCount()
// 获取当前处理索引和读取索引(用于状态判断)
startIdx := seq.GetProcessingIndex(topic)
endIdx := seq.GetReadIndex(topic)
// 执行查询
var results []*seqlog.RecordWithStatus
var startRecordIndex int
if direction == "backward" {
// 向前翻页:查询更早的记录
var index int
if indexParam == "" {
index = startIdx
} else {
index, _ = strconv.Atoi(indexParam)
if direction == "" {
results, err = processor.QueryFromProcessing(count)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if index > 0 {
var queryErr error
results, queryErr = processor.QueryNewest(index-1, backward)
if queryErr != nil {
http.Error(w, queryErr.Error(), http.StatusInternalServerError)
if len(results) == 0 {
results, err = processor.QueryFromLast(count)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if len(results) > 0 {
// QueryNewest 返回的结果按索引递增排列
startRecordIndex = index - len(results)
}
}
} else {
// 向后翻页:查询更新的记录
var index int
var refIndex int
if indexParam == "" {
index = startIdx
http.Error(w, "参数错误", http.StatusNotFound)
return
} else {
index, _ = strconv.Atoi(indexParam)
refIndex, err = strconv.Atoi(indexParam)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
}
if index < totalCount {
if direction == "backward" {
var queryErr error
results, queryErr = processor.QueryOldest(index, forward)
results, queryErr = processor.QueryNewest(refIndex, count)
if queryErr != nil {
http.Error(w, queryErr.Error(), http.StatusInternalServerError)
return
}
if len(results) > 0 {
// QueryOldest 返回的结果按索引递增排列
startRecordIndex = index
} else if direction == "forward" {
var queryErr error
results, queryErr = processor.QueryOldest(refIndex, count)
if queryErr != nil {
http.Error(w, queryErr.Error(), http.StatusInternalServerError)
return
}
} else {
http.Error(w, "参数错误", http.StatusNotFound)
return
}
}
type Record struct {
Index int `json:"index"`
Status string `json:"status"`
Data string `json:"data"`
}
records := make([]Record, len(results))
for i, r := range results {
for i, result := range results {
records[i] = Record{
Index: startRecordIndex + i,
Status: r.Status.String(),
Data: string(r.Record.Data),
Index: result.Index,
Status: result.Status.String(),
Data: string(result.Record.Data),
}
}
json.NewEncoder(w).Encode(map[string]interface{}{
"records": records,
"total": len(records),
"totalCount": totalCount,
"processingIndex": startIdx,
"readIndex": endIdx,
"records": records,
"total": len(records),
"totalCount": totalCount,
})
}