重构:拆出 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:
2026-06-18 13:56:36 +08:00
co-authored by Claude
parent 6f6b06e37d
commit eff4972668
32 changed files with 1759 additions and 1462 deletions
+133
View File
@@ -0,0 +1,133 @@
package store
import (
"fmt"
"strings"
"time"
"gorm.io/gorm"
"meshtastic_mqtt_server/internal/config"
)
func (s *Store) ListSigns(opts ListOptions) ([]SignRecord, error) {
opts = NormalizeListOptions(opts)
var rows []SignRecord
q := applySignFilters(s.db.Model(&SignRecord{}), opts).
Order("sign_time DESC").
Order("id DESC").
Limit(opts.Limit).
Offset(opts.Offset)
return rows, q.Find(&rows).Error
}
type SignDayCount struct {
Date string `gorm:"column:sign_date"`
Count int64 `gorm:"column:count"`
}
func (s *Store) CountSigns(opts ListOptions) (int64, error) {
var total int64
q := applySignFilters(s.db.Model(&SignRecord{}), opts)
return total, q.Count(&total).Error
}
func (s *Store) CountSignsByDay(opts ListOptions) ([]SignDayCount, error) {
var rows []SignDayCount
dateExpr := "strftime('%Y-%m-%d', sign_time)"
if s.driver == config.DriverMySQL {
dateExpr = "DATE_FORMAT(sign_time, '%Y-%m-%d')"
}
q := applySignFilters(s.db.Model(&SignRecord{}), opts).
Select(dateExpr + " AS sign_date, COUNT(*) AS count").
Group(dateExpr).
Order("sign_date DESC")
return rows, q.Scan(&rows).Error
}
func (s *Store) GetSignByID(id uint64) (*SignRecord, error) {
var row SignRecord
if err := s.db.Where("id = ?", id).Take(&row).Error; err != nil {
return nil, err
}
return &row, nil
}
func (s *Store) CreateSign(nodeID string, longName, shortName *string, signText string, signTime time.Time) (*SignRecord, error) {
nodeID = strings.TrimSpace(nodeID)
signText = strings.TrimSpace(signText)
if nodeID == "" {
return nil, fmt.Errorf("node id is required")
}
if signText == "" {
return nil, fmt.Errorf("sign text is required")
}
if signTime.IsZero() {
signTime = time.Now()
}
row := SignRecord{NodeID: nodeID, LongName: trimNullableString(longName), ShortName: trimNullableString(shortName), SignText: signText, SignTime: signTime}
if err := s.db.Create(&row).Error; err != nil {
return nil, err
}
return &row, nil
}
func (s *Store) UpdateSign(id uint64, nodeID string, longName, shortName *string, signText string, signTime time.Time) (*SignRecord, error) {
if id == 0 {
return nil, fmt.Errorf("sign id is required")
}
nodeID = strings.TrimSpace(nodeID)
signText = strings.TrimSpace(signText)
if nodeID == "" {
return nil, fmt.Errorf("node id is required")
}
if signText == "" {
return nil, fmt.Errorf("sign text is required")
}
if signTime.IsZero() {
signTime = time.Now()
}
if _, err := s.GetSignByID(id); err != nil {
return nil, err
}
updates := map[string]any{"node_id": nodeID, "long_name": trimNullableString(longName), "short_name": trimNullableString(shortName), "sign_text": signText, "sign_time": signTime}
if err := s.db.Model(&SignRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return nil, err
}
return s.GetSignByID(id)
}
func (s *Store) DeleteSign(id uint64) error {
result := s.db.Where("id = ?", id).Delete(&SignRecord{})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
func applySignFilters(q *gorm.DB, opts ListOptions) *gorm.DB {
if opts.NodeID != "" {
q = q.Where("node_id = ?", opts.NodeID)
}
if opts.Since != nil {
q = q.Where("sign_time >= ?", *opts.Since)
}
if opts.Until != nil {
q = q.Where("sign_time <= ?", *opts.Until)
}
return q
}
func trimNullableString(value *string) *string {
if value == nil {
return nil
}
trimmed := strings.TrimSpace(*value)
if trimmed == "" {
return nil
}
return &trimmed
}