feat(server/stats): stats-overhaul Phase2 — 每设备归因 + GB 综合配额
服务端记账从「账户」细到「每设备」(每设备独立 dp_uuid),配额单位分钟→GB 按账户综合卡控。000015_per_device_usage 迁移(mysql+sqlite 双份):devices.dp_uuid + usage_device_daily 表 + plans.daily_mb。handler_grpc 按 dp_uuid 回映射 (user_id,device_id) 双写账户+每设备;usage 服务/handler 暴露 /v1/usage(/devices)。 含 sqlite_per_device / usage handler 测试。 注:此迁移 prod 已 migrate up 运行、部署二进制已内嵌;本次补提交使 git 与线上一致。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -293,7 +293,7 @@ func (h *Handler) ReportUsage(ctx context.Context, req *agentv1.UsageReport) (*a
|
||||
if entry.DpUUID == "" {
|
||||
continue
|
||||
}
|
||||
userID, found, err := h.store.UserIDByDpUUID(ctx, entry.DpUUID)
|
||||
userID, deviceID, found, err := h.store.UserDeviceByDpUUID(ctx, entry.DpUUID)
|
||||
if err != nil {
|
||||
slog.Warn("nodes.Handler.ReportUsage: dp_uuid lookup failed",
|
||||
"dp_uuid", entry.DpUUID, "err", err)
|
||||
@@ -302,12 +302,23 @@ func (h *Handler) ReportUsage(ctx context.Context, req *agentv1.UsageReport) (*a
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
// Account-level rollup (always).
|
||||
if err := h.store.AccumulateUsage(ctx, userID, date,
|
||||
entry.BytesUp, entry.BytesDown, entry.SessionMinutes,
|
||||
); err != nil {
|
||||
slog.Warn("nodes.Handler.ReportUsage: accumulate failed",
|
||||
"user_id", userID, "err", err)
|
||||
}
|
||||
// Per-device attribution (only when the dp_uuid maps to a registered device;
|
||||
// deviceID==0 means a legacy account-level credential — no device dimension).
|
||||
if deviceID > 0 {
|
||||
if err := h.store.AccumulateDeviceUsage(ctx, userID, deviceID, date,
|
||||
entry.BytesUp, entry.BytesDown, entry.SessionMinutes,
|
||||
); err != nil {
|
||||
slog.Warn("nodes.Handler.ReportUsage: device accumulate failed",
|
||||
"user_id", userID, "device_id", deviceID, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &agentv1.UsageAck{}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user