Skip to content

Commit ad6f8d6

Browse files
committed
feat: 批量创建规则的接口实现
1 parent d06a3e3 commit ad6f8d6

4 files changed

Lines changed: 548 additions & 0 deletions

File tree

internal/api/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func (r *Router) registerRoutes() {
123123
r.router.HandleFunc("/api/tunnels", r.tunnelHandler.HandleGetTunnels).Methods("GET")
124124
r.router.HandleFunc("/api/tunnels", r.tunnelHandler.HandleCreateTunnel).Methods("POST")
125125
r.router.HandleFunc("/api/tunnels/batch", r.tunnelHandler.HandleBatchCreateTunnels).Methods("POST")
126+
r.router.HandleFunc("/api/tunnels/batch-new", r.tunnelHandler.HandleNewBatchCreateTunnels).Methods("POST")
126127
r.router.HandleFunc("/api/tunnels/batch", r.tunnelHandler.HandleBatchDeleteTunnels).Methods("DELETE")
127128
r.router.HandleFunc("/api/tunnels/quick", r.tunnelHandler.HandleQuickCreateTunnel).Methods("POST")
128129
r.router.HandleFunc("/api/tunnels/template", r.tunnelHandler.HandleTemplateCreate).Methods("POST")

internal/api/tunnel.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,3 +1581,214 @@ func (h *TunnelHandler) HandleBatchDeleteTunnels(w http.ResponseWriter, r *http.
15811581

15821582
_ = json.NewEncoder(w).Encode(resp)
15831583
}
1584+
1585+
// HandleNewBatchCreateTunnels 新的批量创建隧道处理
1586+
func (h *TunnelHandler) HandleNewBatchCreateTunnels(w http.ResponseWriter, r *http.Request) {
1587+
if r.Method != http.MethodPost {
1588+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
1589+
return
1590+
}
1591+
1592+
var req tunnel.NewBatchCreateRequest
1593+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
1594+
w.WriteHeader(http.StatusBadRequest)
1595+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1596+
Success: false,
1597+
Error: "无效的请求数据",
1598+
})
1599+
return
1600+
}
1601+
1602+
// 添加调试日志,显示接收到的原始请求数据
1603+
reqBytes, _ := json.MarshalIndent(req, "", " ")
1604+
log.Infof("[API] 接收到新的批量创建请求,原始数据: %s", string(reqBytes))
1605+
1606+
// 验证请求模式
1607+
if req.Mode == "" {
1608+
w.WriteHeader(http.StatusBadRequest)
1609+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1610+
Success: false,
1611+
Error: "请求模式不能为空",
1612+
})
1613+
return
1614+
}
1615+
1616+
// 根据模式验证具体数据
1617+
switch req.Mode {
1618+
case "standard":
1619+
if len(req.Standard) == 0 {
1620+
w.WriteHeader(http.StatusBadRequest)
1621+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1622+
Success: false,
1623+
Error: "标准模式批量创建项目不能为空",
1624+
})
1625+
return
1626+
}
1627+
1628+
// 限制批量创建的数量
1629+
const maxBatchSize = 50
1630+
if len(req.Standard) > maxBatchSize {
1631+
w.WriteHeader(http.StatusBadRequest)
1632+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1633+
Success: false,
1634+
Error: fmt.Sprintf("标准模式批量创建数量不能超过 %d 个", maxBatchSize),
1635+
})
1636+
return
1637+
}
1638+
1639+
// 验证每个项目的必填字段
1640+
for i, item := range req.Standard {
1641+
if item.EndpointID <= 0 {
1642+
w.WriteHeader(http.StatusBadRequest)
1643+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1644+
Success: false,
1645+
Error: fmt.Sprintf("第 %d 项的端点ID无效", i+1),
1646+
})
1647+
return
1648+
}
1649+
if item.TunnelPort <= 0 || item.TunnelPort > 65535 {
1650+
w.WriteHeader(http.StatusBadRequest)
1651+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1652+
Success: false,
1653+
Error: fmt.Sprintf("第 %d 项的隧道端口无效", i+1),
1654+
})
1655+
return
1656+
}
1657+
if item.TargetHost == "" {
1658+
w.WriteHeader(http.StatusBadRequest)
1659+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1660+
Success: false,
1661+
Error: fmt.Sprintf("第 %d 项的目标地址不能为空", i+1),
1662+
})
1663+
return
1664+
}
1665+
if item.TargetPort <= 0 || item.TargetPort > 65535 {
1666+
w.WriteHeader(http.StatusBadRequest)
1667+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1668+
Success: false,
1669+
Error: fmt.Sprintf("第 %d 项的目标端口无效", i+1),
1670+
})
1671+
return
1672+
}
1673+
if item.Name == "" {
1674+
w.WriteHeader(http.StatusBadRequest)
1675+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1676+
Success: false,
1677+
Error: fmt.Sprintf("第 %d 项的隧道名称不能为空", i+1),
1678+
})
1679+
return
1680+
}
1681+
}
1682+
1683+
case "config":
1684+
if len(req.Config) == 0 {
1685+
w.WriteHeader(http.StatusBadRequest)
1686+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1687+
Success: false,
1688+
Error: "配置模式批量创建项目不能为空",
1689+
})
1690+
return
1691+
}
1692+
1693+
// 计算总的配置项数量并验证
1694+
totalConfigs := 0
1695+
for _, configItem := range req.Config {
1696+
totalConfigs += len(configItem.Config)
1697+
}
1698+
1699+
const maxBatchSize = 50
1700+
if totalConfigs > maxBatchSize {
1701+
w.WriteHeader(http.StatusBadRequest)
1702+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1703+
Success: false,
1704+
Error: fmt.Sprintf("配置模式批量创建数量不能超过 %d 个", maxBatchSize),
1705+
})
1706+
return
1707+
}
1708+
1709+
// 验证每个配置项
1710+
for i, configItem := range req.Config {
1711+
if configItem.EndpointID <= 0 {
1712+
w.WriteHeader(http.StatusBadRequest)
1713+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1714+
Success: false,
1715+
Error: fmt.Sprintf("第 %d 个配置组的端点ID无效", i+1),
1716+
})
1717+
return
1718+
}
1719+
1720+
if len(configItem.Config) == 0 {
1721+
w.WriteHeader(http.StatusBadRequest)
1722+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1723+
Success: false,
1724+
Error: fmt.Sprintf("第 %d 个配置组的配置列表不能为空", i+1),
1725+
})
1726+
return
1727+
}
1728+
1729+
for j, config := range configItem.Config {
1730+
if config.ListenPort <= 0 || config.ListenPort > 65535 {
1731+
w.WriteHeader(http.StatusBadRequest)
1732+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1733+
Success: false,
1734+
Error: fmt.Sprintf("第 %d 个配置组第 %d 项的监听端口无效", i+1, j+1),
1735+
})
1736+
return
1737+
}
1738+
if config.Dest == "" {
1739+
w.WriteHeader(http.StatusBadRequest)
1740+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1741+
Success: false,
1742+
Error: fmt.Sprintf("第 %d 个配置组第 %d 项的目标地址不能为空", i+1, j+1),
1743+
})
1744+
return
1745+
}
1746+
if config.Name == "" {
1747+
w.WriteHeader(http.StatusBadRequest)
1748+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1749+
Success: false,
1750+
Error: fmt.Sprintf("第 %d 个配置组第 %d 项的隧道名称不能为空", i+1, j+1),
1751+
})
1752+
return
1753+
}
1754+
}
1755+
}
1756+
1757+
default:
1758+
w.WriteHeader(http.StatusBadRequest)
1759+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1760+
Success: false,
1761+
Error: "不支持的批量创建模式: " + req.Mode,
1762+
})
1763+
return
1764+
}
1765+
1766+
log.Infof("[API] 接收到新的批量创建隧道请求,模式: %s", req.Mode)
1767+
1768+
// 调用服务层新的批量创建
1769+
response, err := h.tunnelService.NewBatchCreateTunnels(req)
1770+
if err != nil {
1771+
w.WriteHeader(http.StatusInternalServerError)
1772+
json.NewEncoder(w).Encode(tunnel.NewBatchCreateResponse{
1773+
Success: false,
1774+
Error: "新批量创建失败: " + err.Error(),
1775+
})
1776+
return
1777+
}
1778+
1779+
// 根据结果设置HTTP状态码
1780+
if response.Success {
1781+
if response.FailCount > 0 {
1782+
// 部分成功
1783+
w.WriteHeader(http.StatusPartialContent)
1784+
} else {
1785+
// 全部成功
1786+
w.WriteHeader(http.StatusOK)
1787+
}
1788+
} else {
1789+
// 全部失败
1790+
w.WriteHeader(http.StatusBadRequest)
1791+
}
1792+
1793+
json.NewEncoder(w).Encode(response)
1794+
}

internal/tunnel/models.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,47 @@ type BatchCreateResult struct {
141141
TunnelID int64 `json:"tunnelId,omitempty"`
142142
}
143143

144+
// StandardBatchCreateItem 标准模式批量创建项
145+
type StandardBatchCreateItem struct {
146+
Log string `json:"log" validate:"required"`
147+
Name string `json:"name" validate:"required"`
148+
EndpointID int64 `json:"endpointId" validate:"required"`
149+
TunnelPort int `json:"tunnel_port" validate:"required"`
150+
TargetHost string `json:"target_host" validate:"required"`
151+
TargetPort int `json:"target_port" validate:"required"`
152+
}
153+
154+
// ConfigBatchCreateConfig 配置模式的单个配置项
155+
type ConfigBatchCreateConfig struct {
156+
Dest string `json:"dest" validate:"required"`
157+
ListenPort int `json:"listen_port" validate:"required"`
158+
Name string `json:"name" validate:"required"`
159+
}
160+
161+
// ConfigBatchCreateItem 配置模式批量创建项
162+
type ConfigBatchCreateItem struct {
163+
Log string `json:"log" validate:"required"`
164+
EndpointID int64 `json:"endpointId" validate:"required"`
165+
Config []ConfigBatchCreateConfig `json:"config" validate:"required,dive"`
166+
}
167+
168+
// NewBatchCreateRequest 新的批量创建请求
169+
type NewBatchCreateRequest struct {
170+
Mode string `json:"mode" validate:"required,oneof=standard config"`
171+
Standard []StandardBatchCreateItem `json:"standard,omitempty"`
172+
Config []ConfigBatchCreateItem `json:"config,omitempty"`
173+
}
174+
175+
// NewBatchCreateResponse 新的批量创建响应
176+
type NewBatchCreateResponse struct {
177+
Success bool `json:"success"`
178+
Message string `json:"message,omitempty"`
179+
Error string `json:"error,omitempty"`
180+
Results []BatchCreateResult `json:"results,omitempty"`
181+
SuccessCount int `json:"successCount"`
182+
FailCount int `json:"failCount"`
183+
}
184+
144185
// UpdateTunnelRequest 更新隧道请求
145186
type UpdateTunnelRequest struct {
146187
ID int64 `json:"id" validate:"required"`

0 commit comments

Comments
 (0)