refactor(repository): 收敛 model/repository 分层为唯一持久化入口

将 OpenFlare 与平台业务的数据访问从 model 与 apps 直连迁入 repository,
model 仅保留实体与无 IO 规则;补充 code-check 架构守卫与开发规范。
This commit is contained in:
ryan
2026-07-24 17:00:17 +08:00
parent 23a5488203
commit 943818f7d4
184 changed files with 5592 additions and 4364 deletions
+4 -2
View File
@@ -10,6 +10,8 @@ import (
"strconv"
"strings"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/relay"
"github.com/Rain-kl/Wavelet/internal/model"
)
@@ -51,7 +53,7 @@ func normalizeFlaredHeartbeatPayload(payload HeartbeatPayload) HeartbeatPayload
}
func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
version, err := model.GetActiveConfigVersion(ctx)
version, err := repository.GetActiveConfigVersion(ctx)
if err != nil {
return nil, err
}
@@ -62,7 +64,7 @@ func getActiveConfigMeta(ctx context.Context) (*ActiveConfigMeta, error) {
}
func listTunnelRelayNodes(ctx context.Context) ([]model.OpenFlareNode, error) {
nodes, err := model.ListOpenFlareNodes(ctx)
nodes, err := repository.ListOpenFlareNodes(ctx)
if err != nil {
return nil, err
}
+8 -40
View File
@@ -10,8 +10,9 @@ import (
"strings"
"time"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/agent"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
"gorm.io/gorm"
)
@@ -74,7 +75,7 @@ func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload Heartbeat
node.LastSeenAt = &lastSeen
node.Status = nodeStatusOnline
if err := db.DB(ctx).Model(node).Updates(changes).Error; err != nil {
if err := repository.UpdateOpenFlareNodeColumns(ctx, node, changes); err != nil {
return nil, fmt.Errorf("update flared heartbeat: %w", err)
}
agent.RefreshAccessTokenCache(ctx, node)
@@ -102,7 +103,7 @@ func GetTunnelConfig(ctx context.Context, node *model.OpenFlareNode) (*TunnelCon
return nil, fmt.Errorf("no active config version: %w", err)
}
routes, err := model.ListProxyRoutes(ctx)
routes, err := repository.ListProxyRoutes(ctx)
if err != nil {
return nil, fmt.Errorf("get proxy routes: %w", err)
}
@@ -133,7 +134,7 @@ func GetTunnelConfig(ctx context.Context, node *model.OpenFlareNode) (*TunnelCon
if !route.Enabled {
continue
}
zoneDomains, domainErr := model.ListZoneDomainsByRouteID(ctx, route.ID)
zoneDomains, domainErr := repository.ListZoneDomainsByRouteID(ctx, route.ID)
if domainErr != nil || len(zoneDomains) == 0 {
continue
}
@@ -177,12 +178,12 @@ func ReportApplyLog(ctx context.Context, payload ApplyLogPayload) (*model.OpenFl
return nil, errors.New("result 仅支持 success、warning 或 failed")
}
latest, err := model.GetLatestOpenFlareApplyLogByNodeID(ctx, payload.NodeID)
latest, err := repository.GetLatestOpenFlareApplyLogByNodeID(ctx, payload.NodeID)
if err != nil {
return nil, err
}
if model.IsRepeatSuccessApplyLog(latest, payload.Version, payload.Checksum, payload.Result) {
if err := updateFlaredNodeFromApplyLog(ctx, payload, now); err != nil {
if err := repository.UpdateOpenFlareNodeFromApplyResult(ctx, payload.NodeID, payload.Result, payload.Version, payload.Message, now); err != nil {
return nil, err
}
return latest, nil
@@ -200,45 +201,12 @@ func ReportApplyLog(ctx context.Context, payload ApplyLogPayload) (*model.OpenFl
CreatedAt: now,
}
err = db.DB(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(log).Error; err != nil {
return err
}
return updateFlaredNodeFromApplyLogTx(tx, payload, now)
})
if err != nil {
if err := repository.CreateOpenFlareApplyLogAndUpdateNode(ctx, log, payload.Result, payload.Version, payload.Message); err != nil {
return nil, err
}
return log, nil
}
func updateFlaredNodeFromApplyLog(ctx context.Context, payload ApplyLogPayload, now time.Time) error {
conn := db.DB(ctx)
if conn == nil {
return errors.New("database not initialized")
}
return conn.Transaction(func(tx *gorm.DB) error {
return updateFlaredNodeFromApplyLogTx(tx, payload, now)
})
}
func updateFlaredNodeFromApplyLogTx(tx *gorm.DB, payload ApplyLogPayload, now time.Time) error {
var node model.OpenFlareNode
if err := tx.Where("node_id = ?", payload.NodeID).First(&node).Error; err != nil {
return err
}
node.Status = nodeStatusOnline
lastSeen := now
node.LastSeenAt = &lastSeen
if payload.Result == applyResultOK {
node.CurrentVersion = payload.Version
node.LastError = ""
} else {
node.LastError = payload.Message
}
return tx.Model(&node).Select("status", "last_seen_at", "current_version", "last_error").Updates(&node).Error
}
func normalizeApplyLogPayload(payload ApplyLogPayload) ApplyLogPayload {
payload.NodeID = strings.TrimSpace(payload.NodeID)
payload.Version = strings.TrimSpace(payload.Version)
+3 -1
View File
@@ -8,6 +8,8 @@ import (
"errors"
"strings"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/shared/response"
"github.com/gin-gonic/gin"
@@ -38,7 +40,7 @@ func authenticateAccessToken(ctx context.Context, token string) (*model.OpenFlar
if token == "" {
return nil, errors.New("missing tunnel token")
}
node, err := model.GetOpenFlareNodeByAccessToken(ctx, token)
node, err := repository.GetOpenFlareNodeByAccessToken(ctx, token)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("invalid tunnel token")
@@ -9,6 +9,8 @@ import (
"net/http/httptest"
"testing"
"github.com/Rain-kl/Wavelet/internal/repository"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/shared/response"
@@ -44,7 +46,7 @@ func seedFlaredNode(t *testing.T, nodeType, accessToken string) *model.OpenFlare
NodeType: nodeType,
AccessToken: accessToken,
}
require.NoError(t, model.CreateOpenFlareNode(ctx, node))
require.NoError(t, repository.CreateOpenFlareNode(ctx, node))
return node
}
@@ -10,7 +10,6 @@ import (
"time"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/agent"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"go.uber.org/zap"
)
@@ -40,11 +39,7 @@ func persistFlaredObservability(ctx context.Context, nodeID string, payload Hear
},
})
}
conn := db.DB(ctx)
if conn == nil {
return
}
if err := agent.ReconcileScopedNodeHealthEvents(conn, nodeID, events, reportedAt, managedTypes); err != nil {
if err := agent.ReconcileScopedNodeHealthEvents(ctx, nodeID, events, reportedAt, managedTypes); err != nil {
zap.L().Error("persist flared health events failed", zap.String("node_id", nodeID), zap.Error(err))
}
}
@@ -7,6 +7,8 @@ import (
"context"
"testing"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/agent"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
@@ -62,7 +64,7 @@ func TestHeartbeatFlaredEmitsHealthEventOnUnhealthy(t *testing.T) {
})
require.NoError(t, err)
events, err := model.ListOpenFlareHealthEvents(ctx, node.NodeID, false, 20)
events, err := repository.ListOpenFlareHealthEvents(ctx, node.NodeID, false, 20)
require.NoError(t, err)
require.Len(t, events, 1)
assert.Equal(t, flaredRuntimeUnhealthyEventType, events[0].EventType)