refactor: 审查修复 — netload 消除重复 /sys 读 + 全项目 gofmt

- netload_linux.go: SampleNetLoad 聚合循环不再对每接口重复
  readIfaceSpeed (snapshot 已汇总 cur.capMbps), 每次采样省 N 次 /sys 读
- gofmt -w: ring.go/ring_engine.go/auth.go/handlers.go/handlers_logs.go/
  handlers_users.go/store.go 结构体字段对齐与注释缩进
- README.md: markdownlint 自动修复 (MD028/MD040)

审查结论: 令牌环本身即互斥协议 — OnToken(收令牌)与 StartRing(发令牌)
在同一节点上由令牌串行化, 不存在需要加锁的竞争; WatchLeader 的读为
良性读, 无需 mutex
This commit is contained in:
JianFeeeee
2026-08-24 22:24:35 +08:00
parent 0596678c67
commit 4a41608d94
9 changed files with 45 additions and 37 deletions

View File

@ -8,6 +8,7 @@
## 特性 ## 特性
### 画布配置 ### 画布配置
- **Scratch 风格画布**:本地转发项(local)与远程服务器(remote)可视化连线,一个 local 可连多个 remote,一个 remote 可连多个 local - **Scratch 风格画布**:本地转发项(local)与远程服务器(remote)可视化连线,一个 local 可连多个 remote,一个 remote 可连多个 local
- **曲线连线 + 端口标签**:每条连线独立曲线,标签为远程端口,可拖拽调整曲线位置,端口可点开编辑 - **曲线连线 + 端口标签**:每条连线独立曲线,标签为远程端口,可拖拽调整曲线位置,端口可点开编辑
- **边开关与分组**:画布边上可一键禁用/启用单条转发,可打分组标签(状态页按分组管理、一键启停整组) - **边开关与分组**:画布边上可一键禁用/启用单条转发,可打分组标签(状态页按分组管理、一键启停整组)
@ -15,6 +16,7 @@
- **画布导入导出**:支持 JSON 备份/恢复 - **画布导入导出**:支持 JSON 备份/恢复
### frpc 能力 ### frpc 能力
- **代理类型**:tcp(完整)、http/https(customDomains / subdomain / locations / basicAuth / headerRewrite) - **代理类型**:tcp(完整)、http/https(customDomains / subdomain / locations / basicAuth / headerRewrite)
- **传输参数**:useEncryption / useCompression / bandwidthLimit / poolCount / transport.protocol(tcp/quic/kcp/websocket)/ TLS - **传输参数**:useEncryption / useCompression / bandwidthLimit / poolCount / transport.protocol(tcp/quic/kcp/websocket)/ TLS
- **负载均衡与健康检查**:lbGroup + healthCheck 渲染输出;状态页通过 frpc admin API 拉取 per-proxy 真实状态(running / check failed / wait start …) - **负载均衡与健康检查**:lbGroup + healthCheck 渲染输出;状态页通过 frpc admin API 拉取 per-proxy 真实状态(running / check failed / wait start …)
@ -22,6 +24,7 @@
- **frpc 一键安装**:从官方 Releases 下载任意版本 frpc,或手动指定二进制路径 - **frpc 一键安装**:从官方 Releases 下载任意版本 frpc,或手动指定二进制路径
### 集群模式(令牌环) ### 集群模式(令牌环)
- **令牌环协议**:若干 webui4frpc 节点组成协作网络;令牌按固定顺序传递,每节点持完整集群信息,单轮即全网收敛 - **令牌环协议**:若干 webui4frpc 节点组成协作网络;令牌按固定顺序传递,每节点持完整集群信息,单轮即全网收敛
- **负载最低者摘取**:新转发任务附加在令牌中,由负载最低的节点自行摘取并创建 frpc worker - **负载最低者摘取**:新转发任务附加在令牌中,由负载最低的节点自行摘取并创建 frpc worker
- **容错自愈**:leader / 非 leader 节点宕机 → 自动检测 → 环跳过死亡节点、任务重挂;leader 宕机 → 上邻居晋升为新 leader - **容错自愈**:leader / 非 leader 节点宕机 → 自动检测 → 环跳过死亡节点、任务重挂;leader 宕机 → 上邻居晋升为新 leader
@ -30,6 +33,7 @@
- **集群二进制分发**:节点间优先互传 frpc 二进制,外部 URL 兜底 - **集群二进制分发**:节点间优先互传 frpc 二进制,外部 URL 兜底
### 认证与权限 ### 认证与权限
- **Basic Auth**:启动参数配置管理员账号密码(flag-creds 快速路径,不走 bcrypt) - **Basic Auth**:启动参数配置管理员账号密码(flag-creds 快速路径,不走 bcrypt)
- **用户管理**:admin / viewer 两种角色,bcrypt 存储,webui 管理 - **用户管理**:admin / viewer 两种角色,bcrypt 存储,webui 管理
- **API Key**:read / write / admin 三级 scope,`w4f_` 前缀,sha256 存储,创建时明文仅展示一次 - **API Key**:read / write / admin 三级 scope,`w4f_` 前缀,sha256 存储,创建时明文仅展示一次
@ -67,7 +71,7 @@ cd web && npm run build && cd .. && cp -r web/dist/* internal/httpapi/dist/ && g
./webui4frpc -addr 127.0.0.1:7500 -user admin -password admin123 -workdir ./data ./webui4frpc -addr 127.0.0.1:7500 -user admin -password admin123 -workdir ./data
``` ```
打开 http://127.0.0.1:7500/ ,输入账号密码进入画布。首次使用可到「设置」页一键安装 frpc(或手动指定路径),然后回到画布创建 local / remote 并连线、保存。 打开 <http://127.0.0.1:7500/> ,输入账号密码进入画布。首次使用可到「设置」页一键安装 frpc(或手动指定路径),然后回到画布创建 local / remote 并连线、保存。
### 参数 ### 参数

View File

@ -196,8 +196,9 @@ func SampleNetLoad() float64 {
// //
// rx+tx counts BOTH directions — a forward proxies in AND out, so a NIC // rx+tx counts BOTH directions — a forward proxies in AND out, so a NIC
// that's saturated in one direction is saturated for forwarding purposes. // that's saturated in one direction is saturated for forwarding purposes.
// Capacity comes from the CURRENT snapshot's capMbps (already summed in
// snapshotNet) — no extra /sys reads per interface here.
var deltaBits float64 var deltaBits float64
sharedCap := uint64(0)
for name, c := range cur.bytes { for name, c := range cur.bytes {
p, ok := prev.bytes[name] p, ok := prev.bytes[name]
if !ok { if !ok {
@ -209,15 +210,11 @@ func SampleNetLoad() float64 {
continue continue
} }
deltaBits += float64(c-p) * 8 deltaBits += float64(c-p) * 8
// approximate this interface's capacity contribution proportional to
// its share of the summed speed — read back from /sys would be noisy
// per call, so reuse the prior snapshot's capMbps scaling.
sharedCap += readIfaceSpeed(name)
} }
if sharedCap == 0 { if cur.capMbps == 0 {
return 0 return 0
} }
capBitsPerSec := float64(sharedCap) * 1e6 capBitsPerSec := float64(cur.capMbps) * 1e6
pct := (deltaBits / dt) / capBitsPerSec * 100 pct := (deltaBits / dt) / capBitsPerSec * 100
if pct < 0 { if pct < 0 {
return 0 return 0

View File

@ -162,6 +162,7 @@ func (e *Engine) loadSnapshot() Load {
// OnToken is the SINGLE-ROUND token handler. Per the authoritative design // OnToken is the SINGLE-ROUND token handler. Per the authoritative design
// (plan §令牌环协议): on receiving the token a node simultaneously // (plan §令牌环协议): on receiving the token a node simultaneously
//
// (a) ADOPTS the carried cluster picture — incoming state is authoritative // (a) ADOPTS the carried cluster picture — incoming state is authoritative
// for membership + per-node fields. This is safe because structural // for membership + per-node fields. This is safe because structural
// changes (join/remove) are NOT kept in local state waiting to survive // changes (join/remove) are NOT kept in local state waiting to survive

View File

@ -38,7 +38,10 @@ func (h *Handler) handleCanvasGet(w http.ResponseWriter, _ *http.Request) {
for _, r := range remotes { for _, r := range remotes {
remoteNames[r.Name] = true remoteNames[r.Name] = true
} }
type linkKey struct{ local, remote string; port int } type linkKey struct {
local, remote string
port int
}
linkSeen := make(map[linkKey]bool, len(links)) linkSeen := make(map[linkKey]bool, len(links))
for _, l := range links { for _, l := range links {
linkSeen[linkKey{l.Local, l.Remote, l.RemotePort}] = true linkSeen[linkKey{l.Local, l.Remote, l.RemotePort}] = true
@ -201,7 +204,10 @@ func (h *Handler) applyCanvas(w http.ResponseWriter, r *http.Request, canvas *ca
// Build a set of (local, remote, remotePort) triples from the incoming // Build a set of (local, remote, remotePort) triples from the incoming
// canvas links so we can revoke any stale topology entries no longer // canvas links so we can revoke any stale topology entries no longer
// present in the canvas (e.g. links that were deleted from the UI). // present in the canvas (e.g. links that were deleted from the UI).
type triple struct{ local, remote string; port int } type triple struct {
local, remote string
port int
}
canvasTriples := make(map[triple]bool, len(canvas.Links)) canvasTriples := make(map[triple]bool, len(canvas.Links))
for _, ln := range canvas.Links { for _, ln := range canvas.Links {
canvasTriples[triple{ln.Local, ln.Remote, ln.RemotePort}] = true canvasTriples[triple{ln.Local, ln.Remote, ln.RemotePort}] = true