mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
Merge branch 'main' into opencode/shiny-falcon
# Conflicts: # go-backend/internal/http/handler/mutations.go # go-backend/tests/contract/issue313_entry_port_conflict_contract_test.go
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# 042 - SQLite 隧道编辑添加入口节点卡死排查
|
||||
|
||||
## Issue
|
||||
- 现象:SQLite 数据库下,编辑已有隧道并新增入口节点时接口卡住;PostgreSQL 下同样操作正常。
|
||||
- 初步判断:`tunnelUpdate` 在事务尚未提交时触发了额外 repository 读查询,SQLite 配置 `MaxOpenConns(1)`,容易在同一请求内形成自锁等待。
|
||||
|
||||
## Goal
|
||||
- 找出 SQLite 与 PostgreSQL 行为差异的根因。
|
||||
- 修复隧道编辑新增入口节点时的阻塞问题,同时不破坏现有的入口端口冲突校验。
|
||||
- 补充最小回归测试,锁定“事务内校验不可再次占用根连接”的场景。
|
||||
|
||||
## Checklist
|
||||
- [x] 复核 `tunnelUpdate` 在新增入口节点路径上的调用链,确认事务内哪些查询绕过了 `tx`。
|
||||
- [x] 为相关 repository 查询补齐 `Tx` 版本,避免 SQLite 单连接下的自锁等待。
|
||||
- [x] 调整 handler 中入口端口冲突校验,保证事务内全程复用同一个 `tx`。
|
||||
- [x] 增加针对 SQLite 的回归测试,验证事务内校验不会阻塞。
|
||||
- [x] 运行相关 handler/backend 测试并记录结果。
|
||||
|
||||
## Notes
|
||||
- 重点关注 `validateTunnelEntryPortConflictsForNewEntries`:当前它在 `tx.Commit()` 前执行,但内部调用 `ListForwardsByTunnel` / `ListForwardPorts` / `HasOtherForwardOnNodePort` 等非事务查询。
|
||||
- SQLite 在 `go-backend/internal/store/repo/repository.go` 中显式设置了 `SetMaxOpenConns(1)`,因此这种模式在 SQLite 下会比 PostgreSQL 更容易表现为“卡死”。
|
||||
|
||||
## 实际改动
|
||||
- `go-backend/internal/store/repo/repository_control.go`
|
||||
- 新增 `ListForwardsByTunnelTx`、`ListForwardPortsTx`、`HasOtherForwardOnNodePortTx`,并让原有非事务方法复用统一实现。
|
||||
- `go-backend/internal/http/handler/mutations.go`
|
||||
- `tunnelUpdate` 在事务内执行入口端口冲突校验时显式传入当前 `tx`。
|
||||
- `validateTunnelEntryPortConflictsForNewEntries` 改为全程使用事务查询。
|
||||
- 新增 `validateForwardPortAvailabilityTx`,避免事务内回落到根连接查询。
|
||||
- `go-backend/internal/http/handler/tunnel_entry_sqlite_test.go`
|
||||
- 新增 SQLite 回归测试,验证开启事务后执行新增入口校验不会阻塞。
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 通过
|
||||
```bash
|
||||
cd go-backend && go test ./internal/http/handler/...
|
||||
```
|
||||
- 结果:通过。
|
||||
|
||||
### 额外检查
|
||||
```bash
|
||||
cd go-backend && go test ./tests/contract/...
|
||||
```
|
||||
- 结果:未全绿;当前失败集中在既有的入口端口语义合同用例:
|
||||
- `TestIssue313_EntryPortCrossTunnelConflictContract`
|
||||
- `TestTunnelUpdateChangesEntryNodeButLeavesOldForwardRuntimeContract`
|
||||
- `TestTunnelUpdateEntryTransitionsCleanupForwardRuntimeContract`
|
||||
- 备注:这些失败反映的是“新增/切换入口时端口校验预期”与现有合同用例之间的行为差异,不是本次 SQLite 事务自锁修复本身的编译或阻塞问题。
|
||||
@@ -0,0 +1,49 @@
|
||||
# 043 - Entry Transition Contract Semantics Alignment
|
||||
|
||||
## Issue
|
||||
- SQLite 阻塞修复完成后,`go test ./tests/contract/...` 暴露出 3 个入口变更相关合同用例失败。
|
||||
- 失败原因分成两类:
|
||||
- Issue 313 用例的数据构造没有真正制造“新增入口节点已被其他转发占用”的冲突。
|
||||
- Issue 281 回归用例在后续引入“入口端口必须落在节点端口范围内”后,仍沿用旧的非重叠端口范围数据,和当前产品语义不一致。
|
||||
|
||||
## Goal
|
||||
- 对齐这 3 个合同测试与当前后端语义。
|
||||
- 保持 SQLite 自锁修复不回退。
|
||||
- 让入口节点切换/新增相关合同测试重新稳定通过。
|
||||
|
||||
## Checklist
|
||||
- [x] 复核 3 个失败用例的测试数据与当前后端校验语义差异。
|
||||
- [x] 调整 Issue 313 用例,确保新增入口节点确实命中“其他转发已占用同节点同端口”。
|
||||
- [x] 调整 Issue 281 两个回归用例,使入口切换场景使用与保留端口兼容的节点端口范围。
|
||||
- [x] 运行相关合同测试与 handler 测试并记录结果。
|
||||
|
||||
## Notes
|
||||
- `validateTunnelEntryPortConflictsForNewEntries` 的占用判定复用 `HasOtherForwardOnNodePort`,语义是“同节点 + 同端口 + 其他转发”,不是全局无节点维度的端口唯一性。
|
||||
- 入口切换测试当前更关注 `forward_port` 重建和旧节点运行时清理,因此测试数据应避免被端口范围校验提前拦截。
|
||||
|
||||
## 实际调整
|
||||
- `go-backend/tests/contract/issue313_entry_port_conflict_contract_test.go`
|
||||
- 将隧道 A 的入口节点改为复用即将添加到隧道 B 的 `entryB2`,确保新增入口时真正命中“同节点同端口已被其他转发占用”。
|
||||
- 修正错误消息断言,直接检查 `out.Msg`,避免 JSON 解码后再读 `res.Body` 导致误判。
|
||||
- `go-backend/tests/contract/limiter_sync_failure_contract_test.go`
|
||||
- 将 issue 281 的新入口节点端口范围调整为包含原有保留端口,保持测试关注点在运行时清理/同步,而不是被后续引入的端口范围校验拦截。
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 定向回归
|
||||
```bash
|
||||
cd go-backend && go test ./tests/contract/... -run 'TestIssue313_EntryPortCrossTunnelConflictContract|TestTunnelUpdateChangesEntryNodeButLeavesOldForwardRuntimeContract|TestTunnelUpdateEntryTransitionsCleanupForwardRuntimeContract' -v
|
||||
```
|
||||
- 结果:3/3 通过。
|
||||
|
||||
### Handler
|
||||
```bash
|
||||
cd go-backend && go test ./internal/http/handler/...
|
||||
```
|
||||
- 结果:通过。
|
||||
|
||||
### 全量合同测试
|
||||
```bash
|
||||
cd go-backend && go test ./tests/contract/...
|
||||
```
|
||||
- 结果:通过。
|
||||
@@ -0,0 +1,183 @@
|
||||
# 044 - 隧道删除时的规则依赖处理设计
|
||||
|
||||
## Goal
|
||||
- 删除隧道前识别关联规则,避免默认级联误删。
|
||||
- 在隧道页提供两种处理方式:迁移规则到其他隧道,或连同规则一起删除。
|
||||
- 第一阶段只覆盖单条隧道删除,先把核心交互和执行链路做稳。
|
||||
|
||||
## Checklist
|
||||
- [x] 复核当前 `tunnel.tsx` 删除交互与 `DeleteTunnelCascade` 行为。
|
||||
- [x] 梳理可复用的规则换隧道能力(`forward/batch-change-tunnel`)。
|
||||
- [x] 产出前端交互、接口与执行链路设计。
|
||||
- [x] 明确第一阶段范围、异常处理与回滚要求。
|
||||
|
||||
## Implementation Checklist
|
||||
- [x] 新增后端删除预检与带规则处理的删除接口。
|
||||
- [x] 在后端补齐规则迁移/失败明细/回滚链路。
|
||||
- [x] 接入前端隧道删除预检、替换/删除规则弹窗与结果展示。
|
||||
- [x] 运行定向测试并记录结果。
|
||||
|
||||
## Current State
|
||||
- `vite-frontend/src/pages/tunnel.tsx` 的单条删除当前直接调用 `/tunnel/delete`,没有前置依赖检查。
|
||||
- `go-backend/internal/store/repo/repository_mutations.go` 里的 `DeleteTunnelCascade` 会直接删除该隧道下的 `forward`、`forward_port`、`user_tunnel`、`chain_tunnel` 等关联数据。
|
||||
- `vite-frontend/src/pages/forward.tsx` 已经有“批量换隧道”交互和 `/forward/batch-change-tunnel`,但如果前端直接串联“先换规则、再删隧道”,中间任一步失败都会留下半完成状态,不适合作为删除流程的最终方案。
|
||||
|
||||
## Proposed UX
|
||||
- 点击隧道删除按钮后,前端先请求“删除预检”接口,不直接进入最终确认。
|
||||
- 若关联规则数为 `0`,继续沿用当前简单确认弹框。
|
||||
- 若关联规则数大于 `0`,改为展示“处理关联规则”弹框:
|
||||
- 顶部 `Alert` 提示:`隧道 "A" 正被 12 条规则使用`。
|
||||
- 展示前 5 条规则摘要:规则名、所属用户、入口端口;剩余规则用“还有 N 条未展开”提示即可。
|
||||
- 提供 `RadioGroup` 两个动作:
|
||||
- `替换到其他隧道`(推荐,默认)
|
||||
- `删除这些规则`
|
||||
- 选择“替换到其他隧道”时显示 `Select`:
|
||||
- 数据源直接复用当前页已经加载的 `tunnels`。
|
||||
- 仅显示 `status === 1` 且 `id !== 当前隧道` 的选项。
|
||||
- 若没有可选隧道,则禁用该选项并自动回退到“删除这些规则”。
|
||||
- 确认按钮文案动态变化:
|
||||
- `替换规则并删除隧道`
|
||||
- `删除规则并删除隧道`
|
||||
- 提交时展示 loading 状态;成功后关闭弹框、刷新隧道列表并 toast 成功。
|
||||
- 如果后端返回规则级失败明细,前端复用现有 `BatchActionResultModal` 展示失败项,而不是只给一个通用 toast。
|
||||
|
||||
## API Design
|
||||
### 1. 删除预检
|
||||
`POST /api/v1/tunnel/delete-preview`
|
||||
|
||||
Request:
|
||||
```json
|
||||
{ "id": 12 }
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"tunnelId": 12,
|
||||
"tunnelName": "HK-Entry",
|
||||
"forwardCount": 12,
|
||||
"sampleForwards": [
|
||||
{
|
||||
"id": 101,
|
||||
"name": "web-1",
|
||||
"userId": 9,
|
||||
"userName": "alice",
|
||||
"inPort": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `sampleForwards` 只需要返回前 5 条,前端用 `forwardCount` 决定是否显示“更多”提示。
|
||||
- 预检只描述现状,不负责最终授权或一致性保证;真正提交删除时,后端必须再校验一次。
|
||||
|
||||
### 2. 带规则处理的删除
|
||||
`POST /api/v1/tunnel/delete-with-forwards`
|
||||
|
||||
Request:
|
||||
```json
|
||||
{
|
||||
"id": 12,
|
||||
"action": "replace",
|
||||
"targetTunnelId": 18
|
||||
}
|
||||
```
|
||||
|
||||
或:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 12,
|
||||
"action": "delete_forwards"
|
||||
}
|
||||
```
|
||||
|
||||
Success response:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"forwardCount": 12,
|
||||
"migratedCount": 12,
|
||||
"deletedForwardCount": 0,
|
||||
"portAdjustedCount": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Failure response:
|
||||
```json
|
||||
{
|
||||
"code": -2,
|
||||
"msg": "部分规则迁移失败",
|
||||
"data": {
|
||||
"successCount": 9,
|
||||
"failCount": 3,
|
||||
"failures": [
|
||||
{
|
||||
"id": 101,
|
||||
"name": "web-1",
|
||||
"reason": "目标隧道入口节点端口 443 已占用"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 保持现有 `/tunnel/delete` 不动,兼容旧调用和当前批量删除逻辑。
|
||||
- `vite-frontend/src/pages/tunnel.tsx` 的单条删除新流程全部走新接口。
|
||||
|
||||
## Backend Execution Strategy
|
||||
- `delete-preview`
|
||||
- 校验 tunnel 是否存在。
|
||||
- 查询 `forward.tunnel_id = 当前隧道` 的数量和前 5 条摘要。
|
||||
- `delete-with-forwards`
|
||||
- 再次查询依赖规则,避免 preview 与提交之间状态变化导致误判。
|
||||
- `action = delete_forwards`
|
||||
- 直接复用当前级联删除逻辑。
|
||||
- `action = replace`
|
||||
- 校验 `targetTunnelId`:存在、启用、且不等于当前隧道。
|
||||
- 先对全部关联规则做一次前置校验:目标隧道入口节点、端口范围、端口冲突、监听 IP 约束。
|
||||
- 全部校验通过后,再逐条迁移规则并同步运行时。
|
||||
- 任一规则迁移失败时,返回失败明细并中止删除;对已迁移规则做回滚,保证用户感知为“要么都成功,要么都不删”。
|
||||
- 规则处理完成后,再删除隧道本身及非 `forward` 关联数据。
|
||||
|
||||
## Frontend Implementation Notes
|
||||
- `vite-frontend/src/pages/tunnel.tsx`
|
||||
- 新增删除预检 loading、依赖摘要、处理动作等 state。
|
||||
- 复用现有 `Modal`,根据 preview 结果切换普通确认视图和依赖处理视图。
|
||||
- 复用 `Select`、`RadioGroup`、`Alert`。
|
||||
- 成功后继续沿用当前 `setTunnels`、`setTunnelOrder`、`setSelectedIds` 清理逻辑。
|
||||
- `vite-frontend/src/api/index.ts`
|
||||
- 新增 `previewTunnelDelete`。
|
||||
- 新增 `deleteTunnelWithForwards`。
|
||||
- 删除失败且返回批量明细时,复用 `BatchActionResultModal`,避免具体失败规则原因被 toast 吞掉。
|
||||
|
||||
## Scope Decision
|
||||
- 第一阶段只改“隧道页单条删除”。
|
||||
- `批量删除隧道` 先保持现有“级联删除规则”行为和提示文案不变。
|
||||
- 如果第一阶段确认体验和后端回滚链路都稳定,再补第二阶段:批量删除时按每条隧道分别预检和处理。
|
||||
|
||||
## Edge Cases
|
||||
- 没有可替换隧道:只允许“删除这些规则”。
|
||||
- 目标隧道在提交前被禁用或删除:后端返回明确错误,前端保留当前弹框和用户选择。
|
||||
- preview 时没有规则、提交时新建了规则:以后端最终校验为准,返回需要重新处理的提示。
|
||||
- 迁移时若原入口端口在目标隧道不可用,沿用现有“换隧道”语义:优先保留原端口,不可用时自动分配可用端口,并通过 `portAdjustedCount` 给前端一个非阻塞提示。
|
||||
- 第一阶段不自动补发目标隧道的 `user_tunnel` 授权,先与现有“批量换隧道”语义保持一致;如果后续需要让用户也获得目标隧道的新增规则权限,再单独评估自动补授权。
|
||||
|
||||
## Implementation Notes
|
||||
- 后端新增 `/api/v1/tunnel/delete-preview` 和 `/api/v1/tunnel/delete-with-forwards`,单条隧道删除改为先预检再执行。
|
||||
- 后端新增 `/api/v1/tunnel/batch-delete-preview` 和 `/api/v1/tunnel/batch-delete-with-forwards`,批量删除支持统一预检并选择“批量替换规则”或“批量删除规则”。
|
||||
- 规则替换删除在后端先做端口/节点占用预检,再执行逐条迁移;执行阶段若任一规则失败,会回滚已迁移规则,并把失败明细返回给前端弹窗展示。
|
||||
- 前端 `tunnel.tsx` 删除弹窗已支持三种状态:预检中、普通删除确认、有依赖规则时的“替换/删除规则”决策视图。
|
||||
- 批量删除弹窗现在会汇总每条选中隧道的依赖规则数量,并在批量替换失败时按“隧道级”返回失败原因,避免整批操作信息丢失。
|
||||
|
||||
## Verification
|
||||
- `cd go-backend && go test ./internal/http/handler/...`
|
||||
- `cd go-backend && go test ./tests/contract/... -run 'TestTunnelDeletePreviewIncludesDependentRulesContract|TestTunnelDeleteWithForwardsDeleteActionRemovesTunnelAndRulesContract|TestTunnelDeleteWithForwardsReplaceReturnsFailureDetailsContract'`
|
||||
- `cd vite-frontend && npm run build`
|
||||
Reference in New Issue
Block a user