现在我已经找到了完整的入口IP生成流程。让我整理并返回关键信息：

---

## 入口IP生成流程分析

### 1. `buildTunnelInIP` 函数 (创建/更新隧道时)

**位置**: `go-backend/internal/http/handler/mutations.go:2112-2152`

```go
func buildTunnelInIP(inNodes []tunnelRuntimeNode, nodes map[int64]*nodeRecord, ipPreference string) string {
    set := make(map[string]struct{})
    ordered := make([]string, 0)
    preferV6 := strings.TrimSpace(ipPreference) == "v6"
    for _, inNode := range inNodes {
        node := nodes[inNode.NodeID]
        if node == nil {
            continue
        }
        v4 := strings.TrimSpace(node.ServerIPv4)   // 从节点获取 IPv4
        v6 := strings.TrimSpace(node.ServerIPv6)   // 从节点获取 IPv6
        var addrs []string
        if preferV6 {
            if v6 != "" { addrs = append(addrs, v6) }
            if v4 != "" { addrs = append(addrs, v4) }
        } else {
            if v4 != "" { addrs = append(addrs, v4) }
            if v6 != "" { addrs = append(addrs, v6) }
        }
        if len(addrs) == 0 {
            if v := strings.TrimSpace(node.ServerIP); v != "" {  // fallback 到 ServerIP
                addrs = append(addrs, v)
            }
        }
        // ... 去重后拼接
    }
    return strings.Join(ordered, ",")
}
```

**调用位置**:
- 隧道创建: `mutations.go:496`
- 隧道更新: `mutations.go:693`

---

### 2. `resolveForwardIngress` 函数 (查询转发时)

**位置**: `go-backend/internal/store/repo/repository.go:2727-2796`

```go
func resolveForwardIngress(db *gorm.DB, forwardID int64, tunnelID int64) (string, sql.NullInt64, error) {
    // 1. 首先从 tunnel 表获取 in_ip (由 buildTunnelInIP 生成)
    var tunnelInIP sql.NullString
    db.Model(&model.Tunnel{}).Select("in_ip").Where("id = ?", tunnelID).Limit(1).Scan(&tunnelInIP)

    // 2. 从 forward_port + node 表获取端口和节点IP
    type fpRow struct {
        Port     sql.NullInt64
        ServerIP sql.NullString
    }
    var fpRows []fpRow
    err := db.Model(&model.ForwardPort{}).
        Select("forward_port.port, node.server_ip").
        Joins("LEFT JOIN node ON node.id = forward_port.node_id").
        Where("forward_port.forward_id = ?", forwardID).
        Order("forward_port.id ASC").
        Find(&fpRows).Error

    // 3. 组装入口地址
    // - 如果 tunnel.in_ip 存在: "ip1:port,ip2:port..."
    // - 否则使用 forward_port 关联的 node.server_ip: "ip:port"
    // ...
}
```

**调用位置**:
- `repository.go:495` - 用户转发列表
- `repository.go:730` - 管理员转发列表

---

### 3. `forward_port` 表与节点IP关联

**模型定义**: `go-backend/internal/store/model/model.go:50-57`

```go
type ForwardPort struct {
    ID        int64 `gorm:"primaryKey;autoIncrement"`
    ForwardID int64 `gorm:"column:forward_id;not null"`
    NodeID    int64 `gorm:"column:node_id;not null"`   // 关联到 node.id
    Port      int   `gorm:"not null"`
}
```

**关联查询** (在 `resolveForwardIngress` 中):
```sql
SELECT forward_port.port, node.server_ip
FROM forward_port
LEFT JOIN node ON node.id = forward_port.node_id
WHERE forward_port.forward_id = ?
ORDER BY forward_port.id ASC
```

---

### 4. 转发创建API入口分配流程

**入口函数**: `go-backend/internal/http/handler/mutations.go:1108-1194`

```go
func (h *Handler) forwardCreate(w http.ResponseWriter, r *http.Request) {
    // 1. 获取隧道入口节点IDs
    entryNodes, _ := h.tunnelEntryNodeIDs(tunnelID)  // line 1162
    
    // 2. 获取或选择入口端口
    port := asInt(req["inPort"], 0)
    if port <= 0 {
        port = h.pickTunnelPort(tunnelID)  // 自动分配端口 (line 1157)
    }
    
    // 3. 创建转发记录 + forward_port 记录
    forwardID, err := h.repo.CreateForwardTx(..., entryNodes, port, ...)  // line 1179
}
```

**`CreateForwardTx`**: `go-backend/internal/store/repo/repository_mutations.go:1171-1209`
```go
func (r *Repository) CreateForwardTx(..., entryNodeIDs []int64, port int, ...) (int64, error) {
    // 创建 forward 记录
    // 为每个入口节点创建 forward_port 记录
    for _, nodeID := range entryNodeIDs {
        fp := model.ForwardPort{
            ForwardID: forwardID,
            NodeID:    nodeID,    // 入口节点ID
            Port:      port,      // 入口端口
        }
        tx.Create(&fp)
    }
}
```

**入口节点获取**: `repository_mutations.go:535-548`
```go
func (r *Repository) TunnelEntryNodeIDs(tunnelID int64) ([]int64, error) {
    // 从 chain_tunnel 表查询 chain_type = 1 (入口) 的节点
    r.db.Model(&model.ChainTunnel{}).
        Where("tunnel_id = ? AND chain_type = ?", tunnelID, "1").
        Order("inx ASC, id ASC").
        Pluck("node_id", &ids)
}
```

---

## 关键修改位置

| 需求 | 修改文件 | 行号 | 说明 |
|------|----------|------|------|
| **修改隧道入口IP生成逻辑** | `mutations.go` | 2112-2152 | `buildTunnelInIP` 函数，控制IPv4/IPv6优先级和组合 |
| **修改转发入口IP解析** | `repository.go` | 2727-2796 | `resolveForwardIngress`，控制入口地址格式 |
| **修改端口自动分配** | `mutations.go` | 2901-2964 | `pickTunnelPort`，控制端口范围选择 |
| **修改入口节点关联** | `repository_mutations.go` | 1196-1205 | `CreateForwardTx` 中的 forward_port 创建 |
| **节点IP来源** | `model.go` | 516-531 | `NodeRecord` 结构体 |
| **节点IP存储** | `model.go` | 59-65 | `Node` 模型: `ServerIP`, `ServerIPV4`, `ServerIPV6` |

---

## 入口IP数据流

```
1. 节点注册 → Node表存储 server_ip, server_ip_v4, server_ip_v6

2. 创建隧道:
   chain_tunnel (入口节点) → buildTunnelInIP() → tunnel.in_ip 字段

3. 创建转发:
   隧道入口节点IDs → forward_port(forward_id, node_id, port)

4. 查询转发入口:
   forward_port JOIN node → resolveForwardIngress()
   ├─ 优先使用 tunnel.in_ip (多IP组合)
   └─ 否则用 forward_port 关联的 node.server_ip
```