重构:拆出 internal/config 与 internal/store 子包
把工程根目录中和"配置加载"、"数据存储"两个领域相关的全部 Go 文件迁到 internal/ 下的子包,按功能分组的第一阶段成果。 internal/config/ - 从 config.go 抽出 Config / MQTTConfig / WebConfig / DatabaseConfig 等 类型并大写导出,函数改名 Default/Load/Write/Validate/BuildTLS 等。 - 测试随被测代码迁移成 package config 的内部测试。 - 根目录留 config.go 作桥接:用 type alias 让旧的小写名(config / mqttConfig / databaseConfig 等)继续可用,避免修改 30+ 处调用方。 internal/store/ - 把 db.go、store_query.go、db_write_queue.go 与 13 个 *_store.go 一并 迁入;26 个 *Record 类型与 store 同包以避免循环依赖。 - store -> Store;50+ 标识符从小写未导出改为大写导出(包括 record、 ListOptions、错误变量、bot/llm/runtime 常量、helpers 等)。 - 新增 DB() / Driver() 访问器供 ai 子系统使用,避免直接访问私有字段。 - bot_pki_store.go 独立出来,把 PKI 解密所需的 store 方法集中归类。 - helpers.go 提供 hashPassword / uint32FromRecord / printJSON 等以前在 其他根目录文件中的辅助;test_helpers_test.go 提供 verifyPassword 与 publicMapTileSourceDTO 让测试可以本地运行而不依赖 main 包。 根目录新增: - store_bridge.go:完整 type-alias / 函数包装层,把 internal/store 的 导出名映射回旧的小写名,让 admin_*_routes.go、web.go、bot_service.go 等仍未迁出的文件继续编译。后续步骤把它们迁到各自领域包后可逐步删除。 - test_helpers_test.go:根目录测试沿用 openTestStore 的入口。 go build ./... 与 go test ./... 全部通过;测试数量与重构前一致。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package store
|
||||
|
||||
import "sync"
|
||||
|
||||
type WriteQueue struct {
|
||||
store *Store
|
||||
jobs chan writeJob
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
type writeJob struct {
|
||||
typeName string
|
||||
from any
|
||||
run func() error
|
||||
errorEvent map[string]any
|
||||
}
|
||||
|
||||
func NewWriteQueue(s *Store) *WriteQueue {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
q := &WriteQueue{
|
||||
store: s,
|
||||
jobs: make(chan writeJob, 1024),
|
||||
}
|
||||
q.wg.Add(1)
|
||||
go q.run()
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *WriteQueue) EnqueueRecord(record map[string]any, clientInfo MQTTClientInfo) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
record = cloneDBWriteRecord(record)
|
||||
switch record["type"] {
|
||||
case "nodeinfo":
|
||||
q.enqueue(writeJob{typeName: "nodeinfo", from: record["from"], run: func() error {
|
||||
return q.store.UpsertNodeInfo(record)
|
||||
}})
|
||||
case "map_report":
|
||||
q.enqueue(writeJob{typeName: "map_report", from: record["from"], run: func() error {
|
||||
return q.store.UpsertMapReport(record)
|
||||
}})
|
||||
case "text_message":
|
||||
// 私聊(PKI 加密、发往受管 bot)单独走 bot_direct_messages 表,
|
||||
// 不再写入 text_message 以避免和频道消息混在一起。
|
||||
if isInboundBotDirectMessage(q.store, record) {
|
||||
q.enqueue(writeJob{typeName: "bot_direct_message_inbound", from: record["from"], run: func() error {
|
||||
return insertInboundBotDirectMessage(q.store, record, clientInfo)
|
||||
}})
|
||||
return
|
||||
}
|
||||
// 频道消息同时也写入 LLM 队列(如果启用的话)
|
||||
q.enqueue(writeJob{typeName: "llm_channel_message", from: record["from"], run: func() error {
|
||||
return enqueueChannelMessageToLLM(q.store, record)
|
||||
}})
|
||||
q.enqueue(writeJob{typeName: "text_message", from: record["from"], run: func() error {
|
||||
return q.store.InsertTextMessage(record, clientInfo)
|
||||
}})
|
||||
case "position":
|
||||
q.enqueue(writeJob{typeName: "position", from: record["from"], run: func() error {
|
||||
return q.store.InsertPosition(record, clientInfo)
|
||||
}})
|
||||
case "telemetry":
|
||||
q.enqueue(writeJob{typeName: "telemetry", from: record["from"], run: func() error {
|
||||
return q.store.InsertTelemetry(record, clientInfo)
|
||||
}})
|
||||
case "routing":
|
||||
q.enqueue(writeJob{typeName: "routing", from: record["from"], run: func() error {
|
||||
return q.store.InsertRouting(record, clientInfo)
|
||||
}})
|
||||
case "traceroute":
|
||||
q.enqueue(writeJob{typeName: "traceroute", from: record["from"], run: func() error {
|
||||
return q.store.InsertTraceroute(record, clientInfo)
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
||||
func (q *WriteQueue) EnqueueDiscard(record map[string]any, raw []byte, clientInfo MQTTClientInfo) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
record = cloneDBWriteRecord(record)
|
||||
raw = append([]byte(nil), raw...)
|
||||
q.enqueue(writeJob{typeName: "discard_details", from: record["from"], errorEvent: map[string]any{"event": "db_error", "type": "discard_details", "topic": record["topic"]}, run: func() error {
|
||||
return q.store.InsertDiscardDetails(record, raw, clientInfo)
|
||||
}})
|
||||
}
|
||||
|
||||
func (q *WriteQueue) Close() {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
close(q.jobs)
|
||||
q.wg.Wait()
|
||||
}
|
||||
|
||||
func (q *WriteQueue) Len() int {
|
||||
if q == nil {
|
||||
return 0
|
||||
}
|
||||
return len(q.jobs)
|
||||
}
|
||||
|
||||
func (q *WriteQueue) enqueue(job writeJob) {
|
||||
q.jobs <- job
|
||||
}
|
||||
|
||||
func (q *WriteQueue) run() {
|
||||
defer q.wg.Done()
|
||||
for job := range q.jobs {
|
||||
if err := job.run(); err != nil {
|
||||
event := job.errorEvent
|
||||
if event == nil {
|
||||
event = map[string]any{"event": "db_error", "type": job.typeName, "from": job.from}
|
||||
} else {
|
||||
event = cloneDBWriteRecord(event)
|
||||
}
|
||||
event["error"] = err.Error()
|
||||
printJSON(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cloneDBWriteRecord(record map[string]any) map[string]any {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
cloned := make(map[string]any, len(record))
|
||||
for key, value := range record {
|
||||
cloned[key] = value
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
Reference in New Issue
Block a user