aidedudp.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. package waf
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "maps"
  7. "net"
  8. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  9. "github.com/go-nunu/nunu-layout-advanced/internal/model"
  10. "github.com/go-nunu/nunu-layout-advanced/internal/repository/api/waf"
  11. "github.com/go-nunu/nunu-layout-advanced/internal/service"
  12. "github.com/go-nunu/nunu-layout-advanced/internal/service/api/flexCdn"
  13. )
  14. // AidedUdpService UDP转发辅助服务接口
  15. type AidedUdpService interface {
  16. // 验证相关
  17. ValidateAddRequest(ctx context.Context, req *v1.UdpForwardingRequest, require RequireResponse) error
  18. ValidateEditRequest(ctx context.Context, req *v1.UdpForwardingRequest, require RequireResponse, oldData *model.UdpForWarding) error
  19. ValidateDeletePermission(oldData *model.UdpForWarding, hostId int) error
  20. // CDN操作相关
  21. CreateCdnWebsite(ctx context.Context, formData v1.WebsiteSend) (int64, error)
  22. UpdateCdnConfiguration(ctx context.Context, req *v1.UdpForwardingRequest, oldData *model.UdpForWarding, require RequireResponse, formData v1.WebsiteSend) error
  23. DeleteCdnServer(ctx context.Context, cdnWebId int) error
  24. // 源站操作相关
  25. AddOriginsToWebsite(ctx context.Context, req *v1.UdpForwardingRequest, udpId int64) (map[string]int64, error)
  26. UpdateOriginServers(ctx context.Context, req *v1.UdpForwardingRequest, oldData *model.UdpForWarding, ipData *model.UdpForwardingRule) error
  27. // 代理协议配置
  28. ConfigureProxyProtocol(ctx context.Context, req *v1.UdpForwardingRequest, udpId int64) error
  29. // 异步任务处理
  30. ProcessAsyncTasks(req *v1.UdpForwardingRequest)
  31. ProcessIpWhitelistChanges(ctx context.Context, req *v1.UdpForwardingRequest, ipData *model.UdpForwardingRule) error
  32. ProcessDeleteIpWhitelist(ctx context.Context, id int) error
  33. // 数据准备和配置
  34. PrepareWafData(ctx context.Context, req *v1.UdpForwardingRequest) (RequireResponse, v1.WebsiteSend, error)
  35. BuildUdpListenConfig(gatewayIps []string, port string) ([]byte, error)
  36. // 模型构建
  37. BuildUdpForwardingModel(req *v1.UdpForwardingDataRequest, ruleId int, require RequireResponse) *model.UdpForWarding
  38. BuildUdpRuleModel(reqData *v1.UdpForwardingDataRequest, require RequireResponse, localDbId int, cdnOriginIds map[string]int64) *model.UdpForwardingRule
  39. // 数据库操作
  40. SaveToDatabase(ctx context.Context, req *v1.UdpForwardingRequest, require RequireResponse, udpId int64, cdnOriginIds map[string]int64) (int, error)
  41. UpdateDatabaseRecords(ctx context.Context, req *v1.UdpForwardingRequest, oldData *model.UdpForWarding, require RequireResponse, ipData *model.UdpForwardingRule) error
  42. CleanupDatabaseRecords(ctx context.Context, id int) error
  43. // 工具函数
  44. ExtractIpsFromBackends(backends []string) []string
  45. }
  46. type aidedUdpService struct {
  47. *service.Service
  48. wafformatter WafFormatterService
  49. cdn flexCdn.CdnService
  50. proxy flexCdn.ProxyService
  51. globalRep waf.GlobalLimitRepository
  52. udpRepository waf.UdpForWardingRepository
  53. }
  54. func NewAidedUdpService(
  55. service *service.Service,
  56. wafformatter WafFormatterService,
  57. cdn flexCdn.CdnService,
  58. proxy flexCdn.ProxyService,
  59. globalRep waf.GlobalLimitRepository,
  60. udpRepository waf.UdpForWardingRepository,
  61. ) AidedUdpService {
  62. return &aidedUdpService{
  63. Service: service,
  64. wafformatter: wafformatter,
  65. cdn: cdn,
  66. proxy: proxy,
  67. globalRep: globalRep,
  68. udpRepository: udpRepository,
  69. }
  70. }
  71. // ValidateAddRequest 验证添加 UDP 转发请求的合法性
  72. // 该函数验证以下内容:
  73. // 1. 验证 WAF 端口数量限制(防止超出配额)
  74. // 2. 验证端口号是否已被占用(确保端口唯一性)
  75. func (s *aidedUdpService) ValidateAddRequest(ctx context.Context, req *v1.UdpForwardingRequest, require RequireResponse) error {
  76. // 验证端口数量限制
  77. if err := s.wafformatter.validateWafPortCount(ctx, require.HostId); err != nil {
  78. return fmt.Errorf("端口数量验证失败: %w", err)
  79. }
  80. // 验证端口占用情况
  81. if err := s.wafformatter.VerifyPort(ctx, "udp", int64(req.UdpForwardingData.Id), req.UdpForwardingData.Port, int64(require.HostId), ""); err != nil {
  82. return fmt.Errorf("端口 %s 已被占用或不合法: %w", req.UdpForwardingData.Port, err)
  83. }
  84. return nil
  85. }
  86. // ValidateEditRequest 验证编辑 UDP 转发请求的合法性
  87. // 该函数仅在端口发生变更时才验证端口冲突,提高性能并避免不必要的检查
  88. func (s *aidedUdpService) ValidateEditRequest(ctx context.Context, req *v1.UdpForwardingRequest, require RequireResponse, oldData *model.UdpForWarding) error {
  89. // 只有端口发生变更时才需要验证端口冲突
  90. if oldData.Port != req.UdpForwardingData.Port {
  91. if err := s.wafformatter.VerifyPort(ctx, "udp", int64(req.UdpForwardingData.Id), req.UdpForwardingData.Port, int64(require.HostId), ""); err != nil {
  92. return fmt.Errorf("新端口 %s 已被占用或不合法: %w", req.UdpForwardingData.Port, err)
  93. }
  94. }
  95. return nil
  96. }
  97. // ValidateDeletePermission 验证删除 UDP 转发配置的权限
  98. // 该函数确保用户只能删除属于自己主机的配置,防止越权操作
  99. func (s *aidedUdpService) ValidateDeletePermission(oldData *model.UdpForWarding, hostId int) error {
  100. if oldData == nil {
  101. return fmt.Errorf("UDP转发配置数据不存在")
  102. }
  103. if oldData.HostId != hostId {
  104. return fmt.Errorf("用户权限不足,无法删除不属于自己主机的配置(主机ID: %d)", hostId)
  105. }
  106. return nil
  107. }
  108. // CreateCdnWebsite 在 CDN 系统中创建 UDP 代理网站
  109. // 该函数调用 CDN 服务接口创建一个新的 UDP 代理网站,返回网站ID用于后续配置
  110. func (s *aidedUdpService) CreateCdnWebsite(ctx context.Context, formData v1.WebsiteSend) (int64, error) {
  111. udpId, err := s.cdn.CreateWebsite(ctx, formData)
  112. if err != nil {
  113. return 0, fmt.Errorf("在CDN系统中创建 UDP 代理网站失败: %w", err)
  114. }
  115. if udpId <= 0 {
  116. return 0, fmt.Errorf("CDN系统返回了无效的网站ID: %d", udpId)
  117. }
  118. return udpId, nil
  119. }
  120. // UpdateCdnConfiguration 更新 CDN 系统中的 UDP 代理配置
  121. // 该函数根据变更内容智能更新:
  122. // 1. 端口变更 - 更新网站监听配置
  123. // 2. 名称变更 - 更新网站基本信息
  124. // 3. 代理协议变更 - 开启/关闭 Proxy Protocol
  125. func (s *aidedUdpService) UpdateCdnConfiguration(ctx context.Context, req *v1.UdpForwardingRequest, oldData *model.UdpForWarding, require RequireResponse, formData v1.WebsiteSend) error {
  126. // 更新网站端口
  127. if oldData.Port != req.UdpForwardingData.Port {
  128. if err := s.cdn.EditServerType(ctx, v1.EditWebsite{
  129. Id: int64(oldData.CdnWebId),
  130. TypeJSON: formData.UdpJSON,
  131. }, "udp"); err != nil {
  132. return fmt.Errorf("更新网站端口失败: %w", err)
  133. }
  134. }
  135. // 更新网站名称
  136. if oldData.Comment != req.UdpForwardingData.Comment {
  137. nodeId, err := s.globalRep.GetNodeId(ctx, oldData.CdnWebId)
  138. if err != nil {
  139. return fmt.Errorf("获取节点ID失败: %w", err)
  140. }
  141. if err := s.cdn.EditServerBasic(ctx, int64(oldData.CdnWebId), require.Tag, nodeId); err != nil {
  142. return fmt.Errorf("更新网站名称失败: %w", err)
  143. }
  144. }
  145. // 更新代理协议
  146. if oldData.Proxy != req.UdpForwardingData.Proxy {
  147. if err := s.proxy.EditProxy(ctx, int64(oldData.CdnWebId), v1.ProxyProtocolJSON{
  148. IsOn: req.UdpForwardingData.Proxy,
  149. Version: 1,
  150. }); err != nil {
  151. return fmt.Errorf("更新代理协议失败: %w", err)
  152. }
  153. }
  154. return nil
  155. }
  156. // DeleteCdnServer 从 CDN 系统中删除 UDP 代理网站
  157. // 该函数删除 CDN 中的网站配置,释放端口资源和相关配置
  158. func (s *aidedUdpService) DeleteCdnServer(ctx context.Context, cdnWebId int) error {
  159. if err := s.cdn.DelServer(ctx, int64(cdnWebId)); err != nil {
  160. return fmt.Errorf("删除CDN服务器失败: %w", err)
  161. }
  162. return nil
  163. }
  164. // AddOriginsToWebsite 为 UDP 代理网站添加后端源站
  165. // 该函数执行以下操作:
  166. // 1. 批量创建后端服务器的源站记录
  167. // 2. 将源站关联到 UDP 代理网站
  168. // 3. 返回后端地址与源站ID的映射关系
  169. func (s *aidedUdpService) AddOriginsToWebsite(ctx context.Context, req *v1.UdpForwardingRequest, udpId int64) (map[string]int64, error) {
  170. cdnOriginIds := make(map[string]int64)
  171. // 批量创建源站
  172. for _, backend := range req.UdpForwardingData.BackendList {
  173. id, err := s.wafformatter.AddOrigin(ctx, v1.WebJson{
  174. ApiType: "udp",
  175. BackendList: backend,
  176. Comment: req.UdpForwardingData.Comment,
  177. })
  178. if err != nil {
  179. return nil, fmt.Errorf("添加源站失败 %s: %w", backend, err)
  180. }
  181. cdnOriginIds[backend] = id
  182. }
  183. // 批量关联源站到网站
  184. for _, originId := range cdnOriginIds {
  185. if err := s.cdn.AddServerOrigin(ctx, udpId, originId); err != nil {
  186. return nil, fmt.Errorf("关联源站到网站失败: %w", err)
  187. }
  188. }
  189. return cdnOriginIds, nil
  190. }
  191. // UpdateOriginServers 更新 UDP 代理的后端源站配置
  192. // 该函数智能对比新旧后端列表,只处理变更的部分:
  193. // 1. 添加新增的后端服务器
  194. // 2. 删除不再需要的后端服务器
  195. // 3. 更新源站ID映射关系
  196. func (s *aidedUdpService) UpdateOriginServers(ctx context.Context, req *v1.UdpForwardingRequest, oldData *model.UdpForWarding, ipData *model.UdpForwardingRule) error {
  197. addOrigins, delOrigins := s.wafformatter.findIpDifferences(ipData.BackendList, req.UdpForwardingData.BackendList)
  198. // 添加新源站
  199. addedIds := make(map[string]int64)
  200. for _, origin := range addOrigins {
  201. id, err := s.wafformatter.AddOrigin(ctx, v1.WebJson{
  202. ApiType: "udp",
  203. BackendList: origin,
  204. Comment: req.UdpForwardingData.Comment,
  205. })
  206. if err != nil {
  207. return fmt.Errorf("添加源站失败 %s: %w", origin, err)
  208. }
  209. addedIds[origin] = id
  210. }
  211. // 关联新源站到网站
  212. for _, originId := range addedIds {
  213. if err := s.cdn.AddServerOrigin(ctx, int64(oldData.CdnWebId), originId); err != nil {
  214. return fmt.Errorf("关联源站到网站失败: %w", err)
  215. }
  216. }
  217. // 更新源站ID映射
  218. maps.Copy(ipData.CdnOriginIds, addedIds)
  219. // 删除不需要的源站
  220. for backend, originId := range ipData.CdnOriginIds {
  221. for _, delOrigin := range delOrigins {
  222. if backend == delOrigin {
  223. if err := s.cdn.DelServerOrigin(ctx, int64(oldData.CdnWebId), originId); err != nil {
  224. return fmt.Errorf("删除源站失败: %w", err)
  225. }
  226. delete(ipData.CdnOriginIds, backend)
  227. break
  228. }
  229. }
  230. }
  231. return nil
  232. }
  233. // ConfigureProxyProtocol 配置 UDP 代理的 Proxy Protocol 协议
  234. // 该函数根据请求参数决定是否开启 Proxy Protocol,用于传递客户端真实IP
  235. func (s *aidedUdpService) ConfigureProxyProtocol(ctx context.Context, req *v1.UdpForwardingRequest, udpId int64) error {
  236. // 如果不需要开启 Proxy Protocol,直接返回
  237. if !req.UdpForwardingData.Proxy {
  238. return nil
  239. }
  240. // 配置 Proxy Protocol v1
  241. if err := s.proxy.EditProxy(ctx, udpId, v1.ProxyProtocolJSON{
  242. IsOn: true,
  243. Version: 1,
  244. }); err != nil {
  245. return fmt.Errorf("开启 Proxy Protocol 失败: %w", err)
  246. }
  247. return nil
  248. }
  249. // ProcessAsyncTasks 处理 UDP 转发相关的异步任务
  250. // 该函数主要处理新增后端服务器时的IP白名单添加任务,确保后端服务器能正常访问
  251. func (s *aidedUdpService) ProcessAsyncTasks(req *v1.UdpForwardingRequest) {
  252. // 检查是否有后端服务器需要处理
  253. if req == nil || len(req.UdpForwardingData.BackendList) == 0 {
  254. return
  255. }
  256. // 提取后端 IP 地址
  257. ips := s.ExtractIpsFromBackends(req.UdpForwardingData.BackendList)
  258. if len(ips) > 0 {
  259. // 异步添加到白名单
  260. go s.wafformatter.PublishIpWhitelistTask(ips, "add", "", "white")
  261. }
  262. }
  263. // ProcessIpWhitelistChanges 处理 UDP 转发编辑时的IP白名单变更
  264. // 该函数对比新旧后端列表,智能处理IP白名单:
  265. // 1. 将新增的后端 IP 加入白名单
  266. // 2. 将不再使用的后端 IP 从白名单移除
  267. func (s *aidedUdpService) ProcessIpWhitelistChanges(ctx context.Context, req *v1.UdpForwardingRequest, ipData *model.UdpForwardingRule) error {
  268. addedIps, removedIps, err := s.wafformatter.WashEditWafIp(ctx, req.UdpForwardingData.BackendList, ipData.BackendList)
  269. if err != nil {
  270. return fmt.Errorf("处理IP变更失败: %w", err)
  271. }
  272. // 处理新增IP
  273. if len(addedIps) > 0 {
  274. go s.wafformatter.PublishIpWhitelistTask(addedIps, "add", "", "white")
  275. }
  276. // 处理移除IP
  277. if len(removedIps) > 0 {
  278. ipsToDelist, err := s.wafformatter.WashDelIps(ctx, removedIps)
  279. if err != nil {
  280. return fmt.Errorf("处理移除IP失败: %w", err)
  281. }
  282. if len(ipsToDelist) > 0 {
  283. go s.wafformatter.PublishIpWhitelistTask(ipsToDelist, "del", "0", "white")
  284. }
  285. }
  286. return nil
  287. }
  288. // ProcessDeleteIpWhitelist 处理 UDP 转发删除时的IP白名单清理
  289. // 该函数在删除 UDP 转发配置时,智能清理不再需要的后端 IP 白名单条目
  290. func (s *aidedUdpService) ProcessDeleteIpWhitelist(ctx context.Context, id int) error {
  291. ipData, err := s.udpRepository.GetUdpForwardingIpsByID(ctx, id)
  292. if err != nil {
  293. return fmt.Errorf("获取IP数据失败: %w", err)
  294. }
  295. if ipData == nil || len(ipData.BackendList) == 0 {
  296. return nil
  297. }
  298. ips, err := s.wafformatter.WashDeleteWafIp(ctx, ipData.BackendList)
  299. if err != nil {
  300. return fmt.Errorf("处理删除IP失败: %w", err)
  301. }
  302. if len(ips) > 0 {
  303. ipsToDelist, err := s.wafformatter.WashDelIps(ctx, ips)
  304. if err != nil {
  305. return fmt.Errorf("清理IP列表失败: %w", err)
  306. }
  307. if len(ipsToDelist) > 0 {
  308. go s.wafformatter.PublishIpWhitelistTask(ipsToDelist, "del", "0", "white")
  309. }
  310. }
  311. return nil
  312. }
  313. // PrepareWafData 准备WAF配置数据
  314. // 该函数的作用是根据请求参数准备创建CDN网站所需的所有配置数据,包括:
  315. // 1. 获取全局配置信息(用户、主机、网关等)
  316. // 2. 构建 UDP 代理的监听配置 JSON
  317. // 3. 组装 CDN 创建网站的表单数据
  318. func (s *aidedUdpService) PrepareWafData(ctx context.Context, req *v1.UdpForwardingRequest) (RequireResponse, v1.WebsiteSend, error) {
  319. // 获取全局配置信息,包括用户信息、网关IP等
  320. require, err := s.wafformatter.Require(ctx, v1.GlobalRequire{
  321. HostId: req.HostId,
  322. Uid: req.Uid,
  323. Comment: req.UdpForwardingData.Comment,
  324. })
  325. if err != nil {
  326. return RequireResponse{}, v1.WebsiteSend{}, fmt.Errorf("获取全局配置信息失败: %w", err)
  327. }
  328. // 验证实例配置是否完整
  329. if require.Uid == 0 {
  330. return RequireResponse{}, v1.WebsiteSend{}, fmt.Errorf("请先配置实例,确保用户信息和网关配置正确")
  331. }
  332. // 构建 UDP 监听配置
  333. udpConfig, err := s.BuildUdpListenConfig(require.GatewayIps, req.UdpForwardingData.Port)
  334. if err != nil {
  335. return RequireResponse{}, v1.WebsiteSend{}, fmt.Errorf("构建 UDP 监听配置失败: %w", err)
  336. }
  337. // 组装 CDN 创建网站的表单数据
  338. formData := v1.WebsiteSend{
  339. UserId: int64(require.CdnUid),
  340. Type: "udpProxy",
  341. Name: require.Tag,
  342. Description: req.UdpForwardingData.Comment,
  343. UdpJSON: udpConfig,
  344. ServerGroupIds: []int64{int64(require.GroupId)},
  345. NodeClusterId: 2, // 默认节点集群ID
  346. }
  347. return require, formData, nil
  348. }
  349. // BuildUdpListenConfig 构建 UDP 监听配置 JSON
  350. // 该函数将网关IP列表和端口转换为 CDN 所需的 JSON 配置格式
  351. func (s *aidedUdpService) BuildUdpListenConfig(gatewayIps []string, port string) ([]byte, error) {
  352. if len(gatewayIps) == 0 {
  353. return nil, fmt.Errorf("网关IP列表不能为空")
  354. }
  355. // 验证端口字符串不为空
  356. if port == "" {
  357. return nil, fmt.Errorf("端口号不能为空")
  358. }
  359. // 构建监听配置
  360. var listenConfigs []v1.Listen
  361. for _, gatewayIp := range gatewayIps {
  362. if gatewayIp == "" {
  363. continue // 跳过空的IP
  364. }
  365. listenConfigs = append(listenConfigs, v1.Listen{
  366. Protocol: "udp",
  367. Host: gatewayIp,
  368. Port: port,
  369. })
  370. }
  371. if len(listenConfigs) == 0 {
  372. return nil, fmt.Errorf("没有有效的网关IP配置")
  373. }
  374. // 组装最终的 JSON 配置
  375. udpJSON := v1.TypeJSON{
  376. IsOn: true,
  377. Listen: listenConfigs,
  378. }
  379. byteData, err := json.Marshal(udpJSON)
  380. if err != nil {
  381. return nil, fmt.Errorf("序列化 UDP 配置失败: %w", err)
  382. }
  383. return byteData, nil
  384. }
  385. // BuildUdpForwardingModel 构建 UDP 转发主记录模型
  386. // 该函数将请求数据转换为数据库模型,包含主机、CDN网站、端口等信息
  387. func (s *aidedUdpService) BuildUdpForwardingModel(req *v1.UdpForwardingDataRequest, ruleId int, require RequireResponse) *model.UdpForWarding {
  388. return &model.UdpForWarding{
  389. HostId: require.HostId,
  390. CdnWebId: ruleId,
  391. Port: req.Port,
  392. Comment: req.Comment,
  393. Proxy: req.Proxy,
  394. }
  395. }
  396. // BuildUdpRuleModel 构建 UDP 转发规则记录模型
  397. // 该函数构建包含后端服务器列表和 CDN 源站ID映射的规则记录
  398. func (s *aidedUdpService) BuildUdpRuleModel(reqData *v1.UdpForwardingDataRequest, require RequireResponse, localDbId int, cdnOriginIds map[string]int64) *model.UdpForwardingRule {
  399. return &model.UdpForwardingRule{
  400. Uid: require.Uid,
  401. HostId: require.HostId,
  402. UdpId: localDbId,
  403. CdnOriginIds: cdnOriginIds,
  404. BackendList: reqData.BackendList,
  405. }
  406. }
  407. // SaveToDatabase 保存 UDP 转发配置到数据库
  408. // 该函数分别保存主记录(基本信息)和规则记录(后端服务器和源站ID映射)
  409. func (s *aidedUdpService) SaveToDatabase(ctx context.Context, req *v1.UdpForwardingRequest, require RequireResponse, udpId int64, cdnOriginIds map[string]int64) (int, error) {
  410. // 保存主记录
  411. udpModel := s.BuildUdpForwardingModel(&req.UdpForwardingData, int(udpId), require)
  412. id, err := s.udpRepository.AddUdpForwarding(ctx, udpModel)
  413. if err != nil {
  414. return 0, fmt.Errorf("保存UDP转发记录失败: %w", err)
  415. }
  416. // 保存规则记录
  417. udpRuleModel := s.BuildUdpRuleModel(&req.UdpForwardingData, require, id, cdnOriginIds)
  418. if _, err := s.udpRepository.AddUdpForwardingIps(ctx, *udpRuleModel); err != nil {
  419. return 0, fmt.Errorf("保存UDP转发规则失败: %w", err)
  420. }
  421. return id, nil
  422. }
  423. // UpdateDatabaseRecords 更新数据库记录
  424. // 该函数更新 UDP 转发的主记录和规则记录,同步最新的配置变更
  425. func (s *aidedUdpService) UpdateDatabaseRecords(ctx context.Context, req *v1.UdpForwardingRequest, oldData *model.UdpForWarding, require RequireResponse, ipData *model.UdpForwardingRule) error {
  426. // 更新主记录
  427. udpModel := s.BuildUdpForwardingModel(&req.UdpForwardingData, oldData.CdnWebId, require)
  428. udpModel.Id = req.UdpForwardingData.Id
  429. if err := s.udpRepository.EditUdpForwarding(ctx, udpModel); err != nil {
  430. return fmt.Errorf("更新UDP转发记录失败: %w", err)
  431. }
  432. // 更新规则记录
  433. udpRuleModel := s.BuildUdpRuleModel(&req.UdpForwardingData, require, req.UdpForwardingData.Id, ipData.CdnOriginIds)
  434. if err := s.udpRepository.EditUdpForwardingIps(ctx, *udpRuleModel); err != nil {
  435. return fmt.Errorf("更新UDP转发规则失败: %w", err)
  436. }
  437. return nil
  438. }
  439. // CleanupDatabaseRecords 清理数据库记录
  440. // 该函数删除 UDP 转发相关的所有数据库记录,包括主记录和规则记录
  441. func (s *aidedUdpService) CleanupDatabaseRecords(ctx context.Context, id int) error {
  442. if err := s.udpRepository.DeleteUdpForwarding(ctx, int64(id)); err != nil {
  443. return fmt.Errorf("删除UDP转发主记录失败: %w", err)
  444. }
  445. if err := s.udpRepository.DeleteUdpForwardingIpsById(ctx, id); err != nil {
  446. return fmt.Errorf("删除UDP转发规则记录失败: %w", err)
  447. }
  448. return nil
  449. }
  450. // ExtractIpsFromBackends 从后端服务器地址列表中提取纯IP地址
  451. // 该函数解析 "IP:端口" 格式的后端地址,提取出纯IP地址用于白名单处理
  452. func (s *aidedUdpService) ExtractIpsFromBackends(backends []string) []string {
  453. var ips []string
  454. for _, backend := range backends {
  455. // 跳过空字符串
  456. if backend == "" {
  457. continue
  458. }
  459. // 解析 "IP:端口" 格式
  460. if ip, _, err := net.SplitHostPort(backend); err == nil && ip != "" {
  461. ips = append(ips, ip)
  462. }
  463. }
  464. return ips
  465. }