fix: /v1/usage 401(统计页全0真因) + 连接页速度改动态字节单位
- /v1/usage 一直 401:usage 包自定义了 ctxKey("user_id") 读 user id,但
auth.RequireAuth 把 user id 存在 codes.CtxKeyUserID 下;Go context key 按
「类型+值」比较,命名类型不同 → 取到 nil → 401 → 统计页本月流量/时长恒 0
(而 /v1/me weekly 走 account.go 直查,有数,故只半边为 0)。改 usage 读
codes.CtxKeyUserID(与 auth.UserIDFromContext 同键,/v1/me 已验证可用)。
同时修好 /v1/ads/unlock(同一函数)。
- 连接页速度:Mb/s(兆比特,小流量显示 0.0)→ 动态字节单位 B/s·KB/s·MB/s·GB/s,
小数据也看得清。
go build/test 通过;flutter analyze 0 error;114 tests passed。server 已部署。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,8 @@ class ConnectPage extends ConsumerWidget {
|
||||
final isFree = ref.watch(isFreePlanProvider);
|
||||
final quota = ref.watch(quotaProvider);
|
||||
final stats = ref.watch(vpnStatsProvider).valueOrNull;
|
||||
final down = _mbps(stats?.downloadSpeed);
|
||||
final up = _mbps(stats?.uploadSpeed);
|
||||
final down = _speed(stats?.downloadSpeed);
|
||||
final up = _speed(stats?.uploadSpeed);
|
||||
// 延迟来源:连接后用内核 urltest 实测(代理真实 RTT,TCP 探针被隧道吞掉时仍准);
|
||||
// 未连接时用节点 TCP 探针 ping。0/无 → 显示 —。
|
||||
final livePing = conn.phase == VpnPhase.on ? _bestUrltest(stats) : node.ping;
|
||||
@@ -201,9 +201,16 @@ class ConnectPage extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 字节/秒 → Mb/s 字符串(null = 未取到,显示 —)。
|
||||
String _mbps(double? bytesPerSec) =>
|
||||
bytesPerSec == null ? '—' : (bytesPerSec * 8 / 1000000).toStringAsFixed(1);
|
||||
/// 字节/秒 → 动态单位 (数值, 单位):B/s · KB/s · MB/s · GB/s(小数据也看得清)。
|
||||
/// null = 未取到,显示 —。
|
||||
(String, String) _speed(double? bytesPerSec) {
|
||||
if (bytesPerSec == null) return ('—', 'KB/s');
|
||||
final b = bytesPerSec;
|
||||
if (b < 1024) return (b.toStringAsFixed(0), 'B/s');
|
||||
if (b < 1024 * 1024) return ((b / 1024).toStringAsFixed(1), 'KB/s');
|
||||
if (b < 1024 * 1024 * 1024) return ((b / (1024 * 1024)).toStringAsFixed(1), 'MB/s');
|
||||
return ((b / (1024 * 1024 * 1024)).toStringAsFixed(2), 'GB/s');
|
||||
}
|
||||
|
||||
/// 内核 urltest 各出站中的最小正延迟(ms);无则 0。
|
||||
int _bestUrltest(VpnStatsEvent? s) {
|
||||
@@ -216,16 +223,16 @@ int _bestUrltest(VpnStatsEvent? s) {
|
||||
class _SpeedRow extends StatelessWidget {
|
||||
const _SpeedRow({required this.t, required this.down, required this.up, required this.latency});
|
||||
final AppText t;
|
||||
final String down;
|
||||
final String up;
|
||||
final (String, String) down;
|
||||
final (String, String) up;
|
||||
final String latency;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.pangolin;
|
||||
final metrics = [
|
||||
(PangolinIcons.arrowDown, t.download, down, 'Mb/s'),
|
||||
(PangolinIcons.arrowUp, t.upload, up, 'Mb/s'),
|
||||
(PangolinIcons.arrowDown, t.download, down.$1, down.$2),
|
||||
(PangolinIcons.arrowUp, t.upload, up.$1, up.$2),
|
||||
(PangolinIcons.zap, t.latency, latency, 'ms'),
|
||||
];
|
||||
return Row(children: [
|
||||
|
||||
@@ -6,19 +6,17 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/wangjia/pangolin/server/internal/apierr"
|
||||
"github.com/wangjia/pangolin/server/internal/codes"
|
||||
)
|
||||
|
||||
// ctxKey is the context key type for request-scoped values. Defined here to
|
||||
// avoid an import cycle; the JWT auth middleware sets the same key.
|
||||
type ctxKey string
|
||||
|
||||
// CtxKeyUserID is the context key carrying the authenticated int64 user ID.
|
||||
const CtxKeyUserID ctxKey = "user_id"
|
||||
|
||||
// userIDFromContext extracts the authenticated user ID set by the auth
|
||||
// middleware, or 0 if absent.
|
||||
//
|
||||
// 关键:必须读 codes.CtxKeyUserID —— auth.RequireAuth 正是把 user id 存在这个键
|
||||
// 下。此前本包自定义了同名常量 ctxKey("user_id"),但 Go 的 context key 按
|
||||
// 「类型+值」比较,命名类型不同 → 取不到 → /v1/usage 恒 401(统计页全 0)。
|
||||
func userIDFromContext(r *http.Request) int64 {
|
||||
id, _ := r.Context().Value(CtxKeyUserID).(int64)
|
||||
id, _ := r.Context().Value(codes.CtxKeyUserID).(int64)
|
||||
return id
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user