feat(devices): P2 sessions 表 + 在线/最后登录/客户端版本
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 24s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 4s
ci-pangolin / Go — build + test (push) Successful in 11s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m13s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 24s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 4s
ci-pangolin / Go — build + test (push) Successful in 11s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m13s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
migration 000016(mysql+sqlite,含 down):新增 sessions 表(绑 device+refresh JTI) + devices 加 client_version/totp_trusted_until。devices 唯一键改 + platform CHECK 加 linux(需 SQLite 表重建)拆出后续迁移,降风险。 后端:新 internal/sessions Store(Create/Rotate/Revoke/RevokeByDevice/ LastLoginByDevice);TokenManager 外露 refresh JTI(IssueWithJTI/RefreshWithJTI/ ParseRefreshJTI);auth.Service 注入 SessionStore——登录建会话、刷新轮换、登出吊销; DeviceRegistrar 返回 deviceID;ReportUsage 心跳 touch devices.last_seen(在线判定); devices.ListDevices 经 LastLoginSource 注入返回 online(last_seen<3min)/client_version/ last_login;RegisterIfAbsent 存 client_version。 客户端:Device model 加 online/clientVersion/lastLogin(fromJson 自动解析)。 测试:sessions store 3 例 + ListDevices 在线/最后登录 + device model 2 例 + migration v16;全量 go test/flutter test 绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ type mockNodeStore struct {
|
||||
// devicesByDpUUID maps a per-device dp_uuid → {userID, deviceID}.
|
||||
devicesByDpUUID map[string][2]int64
|
||||
deviceUsageAccum []mockDeviceUsageEntry
|
||||
lastSeenTouched []int64
|
||||
}
|
||||
|
||||
type mockUsageEntry struct {
|
||||
@@ -147,6 +148,13 @@ func (m *mockNodeStore) AccumulateDeviceUsage(_ context.Context, userID, deviceI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockNodeStore) TouchDeviceLastSeen(_ context.Context, deviceID int64) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.lastSeenTouched = append(m.lastSeenTouched, deviceID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockNodeStore) deviceUsageLog() []mockDeviceUsageEntry {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -318,6 +318,11 @@ func (h *Handler) ReportUsage(ctx context.Context, req *agentv1.UsageReport) (*a
|
||||
slog.Warn("nodes.Handler.ReportUsage: device accumulate failed",
|
||||
"user_id", userID, "device_id", deviceID, "err", err)
|
||||
}
|
||||
// Heartbeat: keep the device's "online" status fresh while it reports.
|
||||
if err := h.store.TouchDeviceLastSeen(ctx, deviceID); err != nil {
|
||||
slog.Warn("nodes.Handler.ReportUsage: touch last_seen failed",
|
||||
"device_id", deviceID, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &agentv1.UsageAck{}, nil
|
||||
|
||||
@@ -98,6 +98,10 @@ type NodeStore interface {
|
||||
// (deviceID, date); user_id is carried for per-account rollups/queries.
|
||||
AccumulateDeviceUsage(ctx context.Context, userID, deviceID int64, date time.Time,
|
||||
bytesUp, bytesDown int64, minutes int64) error
|
||||
|
||||
// TouchDeviceLastSeen bumps devices.last_seen to now for an active device,
|
||||
// driving the "online" status (connect + periodic usage reports refresh it).
|
||||
TouchDeviceLastSeen(ctx context.Context, deviceID int64) error
|
||||
}
|
||||
|
||||
// SQLNodeStore implements NodeStore against a SQL database (MySQL or SQLite).
|
||||
@@ -396,6 +400,16 @@ func (s *SQLNodeStore) AccumulateDeviceUsage(
|
||||
return nil
|
||||
}
|
||||
|
||||
// TouchDeviceLastSeen bumps devices.last_seen to now (online-status heartbeat).
|
||||
func (s *SQLNodeStore) TouchDeviceLastSeen(ctx context.Context, deviceID int64) error {
|
||||
if _, err := s.db.ExecContext(ctx,
|
||||
`UPDATE devices SET last_seen=? WHERE id=?`, time.Now().UTC(), deviceID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("nodes.SQLNodeStore.TouchDeviceLastSeen: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AccountDayBytes returns the account's total bytes (up+down) for the day.
|
||||
func (s *SQLNodeStore) AccountDayBytes(ctx context.Context, userID int64, date time.Time) (int64, error) {
|
||||
var total sql.NullInt64
|
||||
|
||||
Reference in New Issue
Block a user