aidedweb.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103
  1. package web
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. waf2 "github.com/go-nunu/nunu-layout-advanced/internal/service/api/waf"
  7. "github.com/go-nunu/nunu-layout-advanced/internal/service/api/waf/common"
  8. "net"
  9. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  10. "github.com/go-nunu/nunu-layout-advanced/internal/model"
  11. "github.com/go-nunu/nunu-layout-advanced/internal/repository/api/waf"
  12. "github.com/go-nunu/nunu-layout-advanced/internal/service"
  13. "github.com/go-nunu/nunu-layout-advanced/internal/service/api/flexCdn"
  14. )
  15. // AidedWebService Web转发辅助服务接口
  16. type AidedWebService interface {
  17. // 验证相关
  18. ValidateAddRequest(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse) error
  19. ValidateEditRequest(ctx context.Context, req *v1.WebForwardingRequest) error
  20. ValidateDeletePermission(oldHostId int, hostId int) error
  21. // CDN网站管理
  22. CreateCdnWebsite(ctx context.Context, formData v1.Website) (int64, error)
  23. UpdateCdnConfiguration(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, tag string, formData v1.Website) error
  24. DeleteCdnServer(ctx context.Context, cdnWebId int) error
  25. // 源站管理
  26. AddOriginsToWebsite(ctx context.Context, req *v1.WebForwardingRequest, webId int64) (map[string]int64, error)
  27. UpdateOriginServers(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, ipData *model.WebForwardingRule) error
  28. // 功能配置管理
  29. ConfigureWebsocket(ctx context.Context, webId int64) error
  30. ConfigureProxyProtocol(ctx context.Context, proxy bool, cdnWebId int64) error
  31. ConfigureCCProtection(ctx context.Context, ccConfig v1.CcConfigRequest, webId int64) error
  32. ConfigureWafFirewall(ctx context.Context, webId int64, groupId int) error
  33. // 异步任务处理
  34. ProcessAsyncTasks(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse)
  35. ProcessIpWhitelistChanges(ctx context.Context, req *v1.WebForwardingRequest, ipData *model.WebForwardingRule) error
  36. ProcessDeleteIpWhitelist(ctx context.Context, id int) error
  37. ProcessDomainWhitelistChanges(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, require common.RequireResponse) error
  38. ProcessDeleteDomainWhitelist(ctx context.Context, oldData *model.WebForwarding, uid int) error
  39. // 数据库操作
  40. SaveToDatabase(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse, webId int64, cdnOriginIds map[string]int64) (int, error)
  41. UpdateDatabaseRecords(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse, ipData *model.WebForwardingRule) error
  42. CleanupDatabaseRecords(ctx context.Context, id int) error
  43. // SSL证书管理
  44. ProcessSSLCertificate(ctx context.Context, req *v1.WebForwardingRequest, cdnUid int) error
  45. ProcessSSLCertificateUpdate(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, cdnUid int) error
  46. CleanupSSLCertificate(ctx context.Context, oldData *model.WebForwarding) error
  47. // 数据准备辅助函数
  48. PrepareWafData(ctx context.Context, req *v1.WebForwardingRequest) (common.RequireResponse, v1.Website, error)
  49. BuildProxyConfig(ctx context.Context, req *v1.WebForwardingRequest, gatewayIps []string) (v1.TypeJSON, error)
  50. BulidFormData(ctx context.Context, formData v1.Website) (v1.WebsiteSend, error)
  51. // 协议判断辅助函数
  52. GetProtocolType(isHttps int) string
  53. IsHttpsProtocol(isHttps int) bool
  54. // 模型构建辅助函数
  55. BuildWebForwardingModel(req *v1.WebForwardingDataRequest, ruleId int, require common.RequireResponse) *model.WebForwarding
  56. BuildWebRuleModel(reqData *v1.WebForwardingDataRequest, require common.RequireResponse, localDbId int, cdnOriginIds map[string]int64) *model.WebForwardingRule
  57. // 列表差异处理辅助函数
  58. FindDifferenceList(oldList, newList []v1.BackendList) (added, removed []v1.BackendList)
  59. WashDifferentIp(newIpList []string, oldIpList []string) (addedDenyIps []string, removedDenyIps []string)
  60. // 日志配置辅助函数
  61. EditLog(ctx context.Context, webId int64) error
  62. // 废弃的方法(保持向后兼容)
  63. CreateOriginServers(ctx context.Context, req *v1.WebForwardingRequest) (map[string]int64, error)
  64. }
  65. func NewAidedWebService(
  66. service *service.Service,
  67. webForwardingRepository waf.WebForwardingRepository,
  68. wafformatter common.WafFormatterService,
  69. sslCert flexCdn.SslCertService,
  70. cdn flexCdn.CdnService,
  71. proxy flexCdn.ProxyService,
  72. websocket flexCdn.WebsocketService,
  73. cc waf2.CcService,
  74. ccIpList waf2.CcIpListService,
  75. gatewayIp common.GatewayipService,
  76. globalLimitRep waf.GlobalLimitRepository,
  77. ) AidedWebService {
  78. return &aidedWebService{
  79. Service: service,
  80. webForwardingRepository: webForwardingRepository,
  81. wafformatter: wafformatter,
  82. sslCert: sslCert,
  83. cdn: cdn,
  84. proxy: proxy,
  85. websocket: websocket,
  86. cc: cc,
  87. ccIpList: ccIpList,
  88. gatewayIp: gatewayIp,
  89. globalLimitRep: globalLimitRep,
  90. }
  91. }
  92. type aidedWebService struct {
  93. *service.Service
  94. webForwardingRepository waf.WebForwardingRepository
  95. wafformatter common.WafFormatterService
  96. sslCert flexCdn.SslCertService
  97. cdn flexCdn.CdnService
  98. proxy flexCdn.ProxyService
  99. websocket flexCdn.WebsocketService
  100. cc waf2.CcService
  101. ccIpList waf2.CcIpListService
  102. gatewayIp common.GatewayipService
  103. globalLimitRep waf.GlobalLimitRepository
  104. }
  105. const (
  106. // 协议类型常量
  107. isHttps = 1
  108. isHttp = 0
  109. protocolHttps = "https"
  110. protocolHttp = "http"
  111. // 默认配置常量
  112. defaultNodeClusterId = 2
  113. proxyProtocolVersion = 1
  114. )
  115. // BuildWebForwardingModel 辅助函数,用于构建通用的 WebForwarding 模型
  116. // ruleId 是从 WAF 系统获取的 ID
  117. func (s *aidedWebService) BuildWebForwardingModel(req *v1.WebForwardingDataRequest, ruleId int, require common.RequireResponse) *model.WebForwarding {
  118. return &model.WebForwarding{
  119. HostId: require.HostId,
  120. CdnWebId: ruleId,
  121. Port: req.Port,
  122. Domain: req.Domain,
  123. IsHttps: req.IsHttps,
  124. Comment: req.Comment,
  125. HttpsCert: req.HttpsCert,
  126. HttpsKey: req.HttpsKey,
  127. SslCertId: int(req.SslCertId),
  128. SslPolicyId: int(req.SslPolicyId),
  129. Cc: req.CcConfig.IsOn,
  130. ThresholdMethod: req.CcConfig.ThresholdMethod,
  131. Level: req.CcConfig.Level,
  132. Limit5s: req.CcConfig.Limit5s,
  133. Limit60s: req.CcConfig.Limit60s,
  134. Limit300s: req.CcConfig.Limit300s,
  135. Proxy: req.Proxy,
  136. }
  137. }
  138. // BuildWebRuleModel 构建WebForwardingRule模型
  139. func (s *aidedWebService) BuildWebRuleModel(reqData *v1.WebForwardingDataRequest, require common.RequireResponse, localDbId int, cdnOriginIds map[string]int64) *model.WebForwardingRule {
  140. return &model.WebForwardingRule{
  141. Uid: require.Uid,
  142. HostId: require.HostId,
  143. WebId: localDbId,
  144. CdnOriginIds: cdnOriginIds,
  145. BackendList: reqData.BackendList,
  146. }
  147. }
  148. // getRequire 获取前置配置
  149. func (s *aidedWebService) getRequire (ctx context.Context, req *v1.WebForwardingRequest) (common.RequireResponse, error) {
  150. // 1. 获取基础配置
  151. require, err := s.wafformatter.Require(ctx, v1.GlobalRequire{
  152. HostId: req.HostId,
  153. Uid: req.Uid,
  154. Comment: req.WebForwardingData.Comment,
  155. })
  156. if err != nil {
  157. return common.RequireResponse{}, fmt.Errorf("获取WAF前置配置失败: %w", err)
  158. }
  159. if require.Uid == 0 {
  160. return common.RequireResponse{}, fmt.Errorf("请先配置实例")
  161. }
  162. return require, nil
  163. }
  164. // PrepareWafData 准备WAF数据
  165. // 职责:协调整个流程,负责获取前置配置和组装最终的 formData。
  166. func (s *aidedWebService) PrepareWafData(ctx context.Context, req *v1.WebForwardingRequest) (common.RequireResponse, v1.Website, error) {
  167. // 1. 获取前置配置
  168. require, err := s.getRequire(ctx, req)
  169. if err != nil {
  170. return common.RequireResponse{}, v1.Website{}, err
  171. }
  172. // 2. 调用辅助函数,构建核心的代理配置 (将复杂逻辑封装起来)
  173. byteData, err := s.BuildProxyConfig(ctx, req, require.GatewayIps)
  174. if err != nil {
  175. return common.RequireResponse{}, v1.Website{}, err // 错误信息在辅助函数中已经包装好了
  176. }
  177. type serverNames struct {
  178. ServerNames string `json:"name" form:"name"`
  179. Type string `json:"type" form:"type"`
  180. }
  181. var serverName []serverNames
  182. var serverJson []byte
  183. if req.WebForwardingData.Domain != "" {
  184. serverName = append(serverName, serverNames{
  185. ServerNames: req.WebForwardingData.Domain,
  186. Type: "full",
  187. })
  188. serverJson, err = json.Marshal(serverName)
  189. if err != nil {
  190. return common.RequireResponse{}, v1.Website{}, err
  191. }
  192. }
  193. // 3. 组装最终的 WAF 表单数据
  194. formData := v1.Website{
  195. UserId: int64(require.CdnUid),
  196. Type: "httpProxy",
  197. Name: require.Tag,
  198. ServerNamesJSON: serverJson,
  199. Description: req.WebForwardingData.Comment,
  200. ServerGroupIds: []int64{int64(require.GroupId)},
  201. NodeClusterId: defaultNodeClusterId,
  202. }
  203. // 4. 根据协议类型,填充 HttpJSON 和 HttpsJSON 字段
  204. if req.WebForwardingData.IsHttps == isHttps {
  205. formData.HttpJSON = v1.TypeJSON{IsOn: false}
  206. formData.HttpsJSON = byteData
  207. } else {
  208. formData.HttpJSON = byteData
  209. formData.HttpsJSON = v1.TypeJSON{IsOn: false}
  210. }
  211. return require, formData, nil
  212. }
  213. func (s *aidedWebService) buildSslPolicy(ctx context.Context, data *v1.WebForwardingDataRequest) (v1.SslPolicyRef, error) {
  214. // 如果不是 HTTPS,直接返回关闭状态的 SSL 策略
  215. if data.IsHttps != isHttps {
  216. return v1.SslPolicyRef{
  217. IsOn: false,
  218. SslPolicyId: data.SslPolicyId,
  219. }, nil
  220. }
  221. // --- 以下是 HTTPS 的逻辑 ---
  222. sslPolicyID := data.SslPolicyId
  223. // 如果请求中没有提供 SSL 策略 ID,则为其创建一个新的
  224. if sslPolicyID == 0 {
  225. var err error
  226. sslPolicyID, err = s.sslCert.AddSslPolicy(ctx, nil)
  227. if err != nil {
  228. // 如果创建失败,返回零值和错误
  229. return v1.SslPolicyRef{}, err
  230. }
  231. }
  232. // 返回开启状态的 HTTPS 策略
  233. return v1.SslPolicyRef{
  234. IsOn: true,
  235. SslPolicyId: sslPolicyID,
  236. }, nil
  237. }
  238. // BuildProxyConfig 构建代理配置
  239. // 职责:专门负责处理 HTTP/HTTPS 的差异,并生成对应的 JSON 配置。
  240. func (s *aidedWebService) BuildProxyConfig(ctx context.Context, req *v1.WebForwardingRequest, gatewayIps []string) (v1.TypeJSON, error) {
  241. // 第一步:构建 SSL 策略。所有复杂的 if/else 都被封装在辅助函数中
  242. sslPolicy, err := s.buildSslPolicy(ctx, &req.WebForwardingData)
  243. if err != nil {
  244. return v1.TypeJSON{}, err
  245. }
  246. // 更新请求中的 SSL 策略
  247. req.WebForwardingData.SslPolicyId = sslPolicy.SslPolicyId
  248. // 第二步:根据协议类型确定 apiType
  249. apiType := protocolHttp
  250. if req.WebForwardingData.IsHttps == isHttps {
  251. apiType = protocolHttps
  252. }
  253. // 第三步:构建通用的 Listen 配置
  254. listenConfigs := make([]v1.Listen, 0, len(gatewayIps))
  255. for _, ip := range gatewayIps {
  256. listenConfigs = append(listenConfigs, v1.Listen{
  257. Protocol: apiType,
  258. Host: ip,
  259. Port: req.WebForwardingData.Port,
  260. })
  261. }
  262. // 第四步:组装并返回最终结果
  263. jsonData := v1.TypeJSON{
  264. IsOn: true,
  265. SslPolicyRef: sslPolicy,
  266. Listen: listenConfigs,
  267. }
  268. return jsonData, nil
  269. }
  270. // FindDifferenceList 查找两个列表的差异
  271. func (s *aidedWebService) FindDifferenceList(oldList, newList []v1.BackendList) (added, removed []v1.BackendList) {
  272. diff := make(map[v1.BackendList]int)
  273. // 1. 遍历旧列表,为每个元素计数 +1
  274. for _, item := range oldList {
  275. diff[item]++
  276. }
  277. // 2. 遍历新列表,为每个元素计数 -1
  278. for _, item := range newList {
  279. diff[item]--
  280. }
  281. // 3. 遍历 diff map 来找出差异
  282. for item, count := range diff {
  283. if count > 0 {
  284. // 如果 count > 0,说明这个元素在 oldList 中但不在 newList 中
  285. removed = append(removed, item)
  286. } else if count < 0 {
  287. // 如果 count < 0,说明这个元素在 newList 中但不在 oldList 中
  288. added = append(added, item)
  289. }
  290. // 如果 count == 0,说明元素在两个列表中都存在,不做任何操作
  291. }
  292. return added, removed
  293. }
  294. // WashDifferentIp 清洗IP差异 - 并发版本
  295. func (s *aidedWebService) WashDifferentIp(newIpList []string, oldIpList []string) (addedDenyIps []string, removedDenyIps []string) {
  296. // 并发验证并过滤有效IP
  297. oldAllowIps := s.filterValidIpsConcurrently(oldIpList)
  298. newAllowIps := s.filterValidIpsConcurrently(newIpList)
  299. addedDenyIps, removedDenyIps = s.wafformatter.FindIpDifferences(oldAllowIps, newAllowIps)
  300. return addedDenyIps, removedDenyIps
  301. }
  302. // filterValidIpsConcurrently 并发过滤有效IP地址
  303. func (s *aidedWebService) filterValidIpsConcurrently(ipList []string) []string {
  304. if len(ipList) == 0 {
  305. return nil
  306. }
  307. // 小于10个IP时不使用并发,避免overhead
  308. if len(ipList) < 10 {
  309. return s.filterValidIpsSequentially(ipList)
  310. }
  311. type ipResult struct {
  312. ip string
  313. valid bool
  314. index int
  315. }
  316. resultChan := make(chan ipResult, len(ipList))
  317. semaphore := make(chan struct{}, 20) // 限制并发数为20
  318. // 启动goroutine验证IP
  319. for i, ip := range ipList {
  320. go func(ip string, index int) {
  321. semaphore <- struct{}{} // 获取信号量
  322. defer func() { <-semaphore }() // 释放信号量
  323. valid := net.ParseIP(ip) != nil
  324. resultChan <- ipResult{ip: ip, valid: valid, index: index}
  325. }(ip, i)
  326. }
  327. // 收集结果并保持原始顺序
  328. results := make([]ipResult, len(ipList))
  329. for i := 0; i < len(ipList); i++ {
  330. result := <-resultChan
  331. results[result.index] = result
  332. }
  333. close(resultChan)
  334. // 按原始顺序提取有效IP
  335. var validIps []string
  336. for _, result := range results {
  337. if result.valid {
  338. validIps = append(validIps, result.ip)
  339. }
  340. }
  341. return validIps
  342. }
  343. // filterValidIpsSequentially 顺序过滤有效IP地址(用于小数据集)
  344. func (s *aidedWebService) filterValidIpsSequentially(ipList []string) []string {
  345. var validIps []string
  346. for _, ip := range ipList {
  347. if net.ParseIP(ip) != nil {
  348. validIps = append(validIps, ip)
  349. }
  350. }
  351. return validIps
  352. }
  353. // EditLog 修改日志配置
  354. func (s *aidedWebService) EditLog(ctx context.Context, webId int64) error {
  355. webConfigId, err := s.webForwardingRepository.GetWebConfigId(ctx, webId)
  356. if err != nil {
  357. return err
  358. }
  359. if err := s.cdn.EditWebLog(ctx, webConfigId, v1.WebLog{
  360. IsPrior: false,
  361. IsOn: true,
  362. Fields: []int64{1, 2, 6, 7},
  363. Status1: true,
  364. Status2: true,
  365. Status3: true,
  366. Status4: true,
  367. Status5: true,
  368. FirewallOnly: false,
  369. EnableClientClosed: false,
  370. }); err != nil {
  371. return err
  372. }
  373. return nil
  374. }
  375. // BulidFormData 构建表单数据
  376. func (s *aidedWebService) BulidFormData(ctx context.Context, formData v1.Website) (v1.WebsiteSend, error) {
  377. httpJSON, err := json.Marshal(formData.HttpJSON)
  378. if err != nil {
  379. return v1.WebsiteSend{}, err
  380. }
  381. httpsJSON, err := json.Marshal(formData.HttpsJSON)
  382. if err != nil {
  383. return v1.WebsiteSend{}, err
  384. }
  385. formDataSend := v1.WebsiteSend{
  386. UserId: formData.UserId,
  387. AdminId: formData.AdminId,
  388. Type: formData.Type,
  389. Name: formData.Name,
  390. Description: formData.Description,
  391. ServerNamesJSON: formData.ServerNamesJSON,
  392. HttpJSON: httpJSON,
  393. HttpsJSON: httpsJSON,
  394. TcpJSON: formData.TcpJSON,
  395. TlsJSON: formData.TlsJSON,
  396. UdpJSON: formData.UdpJSON,
  397. WebId: formData.WebId,
  398. ReverseProxyJSON: formData.ReverseProxyJSON,
  399. ServerGroupIds: formData.ServerGroupIds,
  400. UserPlanId: formData.UserPlanId,
  401. NodeClusterId: formData.NodeClusterId,
  402. IncludeNodesJSON: formData.IncludeNodesJSON,
  403. ExcludeNodesJSON: formData.ExcludeNodesJSON,
  404. }
  405. return formDataSend, nil
  406. }
  407. // ProcessSSLCertificate 处理SSL证书
  408. func (s *aidedWebService) ProcessSSLCertificate(ctx context.Context, req *v1.WebForwardingRequest, cdnUid int) error {
  409. if !s.IsHttpsProtocol(req.WebForwardingData.IsHttps) {
  410. return nil // 非HTTPS协议不需要处理SSL证书
  411. }
  412. // 添加SSL证书
  413. sslCertId, err := s.sslCert.AddSSLCert(ctx, v1.SSL{
  414. Name: req.WebForwardingData.Domain,
  415. Domain: req.WebForwardingData.Domain,
  416. CertData: req.WebForwardingData.HttpsCert,
  417. KeyData: req.WebForwardingData.HttpsKey,
  418. CdnUserId: cdnUid,
  419. Description: req.WebForwardingData.Comment,
  420. })
  421. if err != nil {
  422. return fmt.Errorf("添加SSL证书失败: %w", err)
  423. }
  424. // 更新请求中的证书ID
  425. req.WebForwardingData.SslCertId = sslCertId
  426. // 编辑SSL策略
  427. if err := s.sslCert.EditSslPolicy(ctx, req.WebForwardingData.SslPolicyId, []int64{sslCertId}, "add"); err != nil {
  428. return fmt.Errorf("编辑SSL策略失败: %w", err)
  429. }
  430. return nil
  431. }
  432. // CreateOriginServers 创建源站服务器
  433. func (s *aidedWebService) CreateOriginServers(ctx context.Context, req *v1.WebForwardingRequest) (map[string]int64, error) {
  434. cdnOriginIds := make(map[string]int64)
  435. for _, backend := range req.WebForwardingData.BackendList {
  436. apiType := s.GetProtocolType(backend.IsHttps)
  437. id, err := s.wafformatter.AddOrigin(ctx, v1.WebJson{
  438. ApiType: apiType,
  439. BackendList: backend.Addr,
  440. Host: backend.CustomHost,
  441. Comment: req.WebForwardingData.Comment,
  442. })
  443. if err != nil {
  444. return nil, fmt.Errorf("添加源站 %s 失败: %w", backend.Addr, err)
  445. }
  446. cdnOriginIds[backend.Addr] = id
  447. }
  448. return cdnOriginIds, nil
  449. }
  450. // GetProtocolType 获取协议类型字符串
  451. func (s *aidedWebService) GetProtocolType(isHttps int) string {
  452. if s.IsHttpsProtocol(isHttps) {
  453. return protocolHttps
  454. }
  455. return protocolHttp
  456. }
  457. // IsHttpsProtocol 判断是否为HTTPS协议
  458. func (s *aidedWebService) IsHttpsProtocol(httpsFlag int) bool {
  459. return httpsFlag == isHttps
  460. }
  461. // ValidateAddRequest 验证添加请求
  462. func (s *aidedWebService) ValidateAddRequest(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse) error {
  463. if err := s.wafformatter.ValidateWafDomainCount(ctx, v1.GlobalRequire{
  464. HostId: req.HostId,
  465. Domain: req.WebForwardingData.Domain,
  466. Comment: req.WebForwardingData.Comment,
  467. Uid: req.Uid,
  468. }); err != nil {
  469. return fmt.Errorf("域名数量验证失败: %w", err)
  470. }
  471. if err := s.wafformatter.ValidateWafPortCount(ctx, require.HostId); err != nil {
  472. return fmt.Errorf("端口数量验证失败: %w", err)
  473. }
  474. protocol := s.GetProtocolType(req.WebForwardingData.IsHttps)
  475. if err := s.wafformatter.VerifyPort(ctx, protocol, int64(req.WebForwardingData.Id), req.WebForwardingData.Port, int64(require.HostId), req.WebForwardingData.Domain); err != nil {
  476. return fmt.Errorf("端口 %d 验证失败: %w", req.WebForwardingData.Port, err)
  477. }
  478. return nil
  479. }
  480. // ValidateEditRequest 验证编辑请求
  481. func (s *aidedWebService) ValidateEditRequest(ctx context.Context, req *v1.WebForwardingRequest) error {
  482. if err := s.wafformatter.ValidateWafDomainCount(ctx, v1.GlobalRequire{
  483. HostId: req.HostId,
  484. Domain: req.WebForwardingData.Domain,
  485. Comment: req.WebForwardingData.Comment,
  486. Uid: req.Uid,
  487. }); err != nil {
  488. return fmt.Errorf("域名数量验证失败: %w", err)
  489. }
  490. protocol := s.GetProtocolType(req.WebForwardingData.IsHttps)
  491. if err := s.wafformatter.VerifyPort(ctx, protocol, int64(req.WebForwardingData.Id), req.WebForwardingData.Port, int64(req.HostId), req.WebForwardingData.Domain); err != nil {
  492. return fmt.Errorf("端口 %d 验证失败: %w", req.WebForwardingData.Port, err)
  493. }
  494. return nil
  495. }
  496. // ValidateDeletePermission 验证删除权限
  497. func (s *aidedWebService) ValidateDeletePermission(oldHostId int, hostId int) error {
  498. if oldHostId != hostId {
  499. return fmt.Errorf("用户权限不足")
  500. }
  501. return nil
  502. }
  503. // CreateCdnWebsite 创建CDN网站
  504. func (s *aidedWebService) CreateCdnWebsite(ctx context.Context, formData v1.Website) (int64, error) {
  505. formDataSend, err := s.BulidFormData(ctx, formData)
  506. if err != nil {
  507. return 0, fmt.Errorf("构建表单数据失败: %w", err)
  508. }
  509. webId, err := s.cdn.CreateWebsite(ctx, formDataSend)
  510. if err != nil {
  511. return 0, fmt.Errorf("创建CDN网站失败: %w", err)
  512. }
  513. return webId, nil
  514. }
  515. // UpdateCdnConfiguration 更新CDN配置
  516. func (s *aidedWebService) UpdateCdnConfiguration(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, tag string, formData v1.Website) error {
  517. // 修改网站端口、协议或证书
  518. if oldData.Port != req.WebForwardingData.Port || oldData.IsHttps != req.WebForwardingData.IsHttps ||
  519. oldData.HttpsCert != req.WebForwardingData.HttpsCert || oldData.HttpsKey != req.WebForwardingData.HttpsKey {
  520. if err := s.updateWebsiteProtocolAndCert(ctx, req.WebForwardingData.IsHttps, int64(oldData.CdnWebId), formData); err != nil {
  521. return err
  522. }
  523. }
  524. // 修改网站域名
  525. if oldData.Domain != req.WebForwardingData.Domain {
  526. if err := s.updateWebsiteDomain(ctx, req.WebForwardingData.Domain, int64(oldData.CdnWebId)); err != nil {
  527. return err
  528. }
  529. }
  530. // 修改网站名字
  531. if oldData.Comment != req.WebForwardingData.Comment {
  532. if err := s.updateWebsiteBasicInfo(ctx, int64(oldData.CdnWebId), tag); err != nil {
  533. return err
  534. }
  535. }
  536. return nil
  537. }
  538. // DeleteCdnServer 删除CDN服务器
  539. func (s *aidedWebService) DeleteCdnServer(ctx context.Context, cdnWebId int) error {
  540. if err := s.cdn.DelServer(ctx, int64(cdnWebId)); err != nil {
  541. return fmt.Errorf("删除CDN服务器失败: %w", err)
  542. }
  543. return nil
  544. }
  545. // updateWebsiteProtocolAndCert 更新网站协议和证书
  546. func (s *aidedWebService) updateWebsiteProtocolAndCert(ctx context.Context, isHttps int, cdnWebId int64, formData v1.Website) error {
  547. // 切换协议
  548. var typeConfig, closeConfig v1.TypeJSON
  549. var apiType, closeType string
  550. if s.IsHttpsProtocol(isHttps) {
  551. typeConfig = formData.HttpsJSON
  552. closeConfig = formData.HttpJSON
  553. apiType = s.GetProtocolType(isHttps)
  554. closeType = s.GetProtocolType(0) // HTTP
  555. } else {
  556. typeConfig = formData.HttpJSON
  557. closeConfig = formData.HttpsJSON
  558. apiType = s.GetProtocolType(isHttps)
  559. closeType = s.GetProtocolType(1) // HTTPS
  560. }
  561. typeJson, err := json.Marshal(typeConfig)
  562. if err != nil {
  563. return fmt.Errorf("序列化协议配置失败: %w", err)
  564. }
  565. closeJson, err := json.Marshal(closeConfig)
  566. if err != nil {
  567. return fmt.Errorf("序列化关闭协议配置失败: %w", err)
  568. }
  569. // 切换协议
  570. if err := s.cdn.EditServerType(ctx, v1.EditWebsite{
  571. Id: cdnWebId,
  572. TypeJSON: typeJson,
  573. }, apiType); err != nil {
  574. return fmt.Errorf("切换到%s协议失败: %w", apiType, err)
  575. }
  576. if err := s.cdn.EditServerType(ctx, v1.EditWebsite{
  577. Id: cdnWebId,
  578. TypeJSON: closeJson,
  579. }, closeType); err != nil {
  580. return fmt.Errorf("关闭%s协议失败: %w", closeType, err)
  581. }
  582. return nil
  583. }
  584. // updateWebsiteDomain 更新网站域名
  585. func (s *aidedWebService) updateWebsiteDomain(ctx context.Context, domain string, cdnWebId int64) error {
  586. type serverName struct {
  587. Name string `json:"name" form:"name"`
  588. Type string `json:"type" form:"type"`
  589. }
  590. var serverData []serverName
  591. serverData = append(serverData, serverName{
  592. Name: domain,
  593. Type: "full",
  594. })
  595. serverJson, err := json.Marshal(serverData)
  596. if err != nil {
  597. return fmt.Errorf("序列化服务器名称失败: %w", err)
  598. }
  599. if err := s.cdn.EditServerName(ctx, v1.EditServerNames{
  600. ServerId: cdnWebId,
  601. ServerNamesJSON: serverJson,
  602. }); err != nil {
  603. return fmt.Errorf("更新服务器名称失败: %w", err)
  604. }
  605. return nil
  606. }
  607. // updateWebsiteBasicInfo 更新网站基本信息
  608. func (s *aidedWebService) updateWebsiteBasicInfo(ctx context.Context, cdnWebId int64, tag string) error {
  609. // 通过globalLimitRep获取节点ID,这是项目中现有的方法
  610. nodeId, err := s.globalLimitRep.GetNodeId(ctx, int(cdnWebId))
  611. if err != nil {
  612. return fmt.Errorf("获取节点ID失败: %w", err)
  613. }
  614. if err := s.cdn.EditServerBasic(ctx, cdnWebId, tag, nodeId); err != nil {
  615. return fmt.Errorf("更新服务器基本信息失败: %w", err)
  616. }
  617. return nil
  618. }
  619. // AddOriginsToWebsite 添加源站到网站
  620. func (s *aidedWebService) AddOriginsToWebsite(ctx context.Context, req *v1.WebForwardingRequest, webId int64) (map[string]int64, error) {
  621. cdnOriginIds, err := s.CreateOriginServers(ctx, req)
  622. if err != nil {
  623. return nil, fmt.Errorf("创建源站服务器失败: %w", err)
  624. }
  625. // 添加源站到网站
  626. for _, originId := range cdnOriginIds {
  627. if err := s.cdn.AddServerOrigin(ctx, webId, originId); err != nil {
  628. return nil, fmt.Errorf("添加源站到网站失败: %w", err)
  629. }
  630. }
  631. return cdnOriginIds, nil
  632. }
  633. // UpdateOriginServers 更新源站服务器
  634. func (s *aidedWebService) UpdateOriginServers(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, ipData *model.WebForwardingRule) error {
  635. addOrigins, delOrigins := s.FindDifferenceList(ipData.BackendList, req.WebForwardingData.BackendList)
  636. addedIds := make(map[string]int64)
  637. // 添加新源站
  638. for _, v := range addOrigins {
  639. apiType := s.GetProtocolType(v.IsHttps)
  640. id, err := s.wafformatter.AddOrigin(ctx, v1.WebJson{
  641. ApiType: apiType,
  642. BackendList: v.Addr,
  643. Host: v.CustomHost,
  644. Comment: req.WebForwardingData.Comment,
  645. })
  646. if err != nil {
  647. return fmt.Errorf("添加源站 %s 失败: %w", v.Addr, err)
  648. }
  649. addedIds[v.Addr] = id
  650. }
  651. // 将新源站添加到网站
  652. for _, v := range addedIds {
  653. if err := s.cdn.AddServerOrigin(ctx, int64(oldData.CdnWebId), v); err != nil {
  654. return fmt.Errorf("添加源站到网站失败: %w", err)
  655. }
  656. }
  657. // 删除旧源站
  658. for k, v := range ipData.CdnOriginIds {
  659. for _, ip := range delOrigins {
  660. if k == ip.Addr {
  661. if err := s.cdn.DelServerOrigin(ctx, int64(oldData.CdnWebId), v); err != nil {
  662. return fmt.Errorf("删除源站失败: %w", err)
  663. }
  664. delete(ipData.CdnOriginIds, k)
  665. }
  666. }
  667. }
  668. // 合并新的源站ID
  669. for k, v := range addedIds {
  670. ipData.CdnOriginIds[k] = v
  671. }
  672. return nil
  673. }
  674. // ConfigureWebsocket 配置WebSocket
  675. func (s *aidedWebService) ConfigureWebsocket(ctx context.Context, webId int64) error {
  676. websocketId, err := s.websocket.AddWebsocket(ctx)
  677. if err != nil {
  678. return fmt.Errorf("添加WebSocket失败: %w", err)
  679. }
  680. if err := s.websocket.EnableOrDisable(ctx, webId, websocketId, true, false); err != nil {
  681. return fmt.Errorf("启用WebSocket失败: %w", err)
  682. }
  683. return nil
  684. }
  685. // ConfigureProxyProtocol 配置代理协议
  686. func (s *aidedWebService) ConfigureProxyProtocol(ctx context.Context, proxy bool, cdnWebId int64) error {
  687. if err := s.proxy.EditProxy(ctx, cdnWebId, v1.ProxyProtocolJSON{
  688. IsOn: proxy,
  689. Version: proxyProtocolVersion,
  690. }); err != nil {
  691. return fmt.Errorf("启用代理协议失败: %w", err)
  692. }
  693. return nil
  694. }
  695. // ConfigureCCProtection 配置CC防护
  696. func (s *aidedWebService) ConfigureCCProtection(ctx context.Context, ccConfig v1.CcConfigRequest, webId int64) error {
  697. if err := s.cc.EditCcConfig(ctx, webId, ccConfig); err != nil {
  698. return fmt.Errorf("配置CC防护失败: %w", err)
  699. }
  700. return nil
  701. }
  702. // ConfigureWafFirewall 配置WAF防火墙
  703. func (s *aidedWebService) ConfigureWafFirewall(ctx context.Context, webId int64, groupId int) error {
  704. if err := s.ccIpList.AddCcIpListPolicy(ctx, webId, int64(groupId)); err != nil {
  705. return fmt.Errorf("配置WAF防火墙失败: %w", err)
  706. }
  707. return nil
  708. }
  709. // ProcessAsyncTasks 处理异步任务
  710. func (s *aidedWebService) ProcessAsyncTasks(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse) {
  711. // 域名白名单处理
  712. if req.WebForwardingData.Domain != "" {
  713. go func() {
  714. doMain, err := s.wafformatter.ConvertToWildcardDomain(ctx, req.WebForwardingData.Domain)
  715. if err != nil {
  716. return
  717. }
  718. if len(require.GatewayIps) == 0 {
  719. return
  720. }
  721. firstIp, err := s.gatewayIp.GetGatewayipByHostIdFirst(ctx, int64(require.HostId), int64(require.Uid))
  722. if err != nil {
  723. return
  724. }
  725. s.wafformatter.PublishDomainWhitelistTask(doMain, firstIp, "add")
  726. }()
  727. }
  728. // 源站IP白名单处理
  729. if req.WebForwardingData.BackendList != nil {
  730. go func() {
  731. var ips []string
  732. for _, v := range req.WebForwardingData.BackendList {
  733. ip, _, err := net.SplitHostPort(v.Addr)
  734. if err != nil {
  735. continue
  736. }
  737. ips = append(ips, ip)
  738. }
  739. if len(ips) > 0 {
  740. s.wafformatter.PublishIpWhitelistTask(ips, "add", "", "white")
  741. }
  742. }()
  743. }
  744. }
  745. // ProcessIpWhitelistChanges 处理IP白名单变更
  746. func (s *aidedWebService) ProcessIpWhitelistChanges(ctx context.Context, req *v1.WebForwardingRequest, ipData *model.WebForwardingRule) error {
  747. var oldIps, newIps []string
  748. // 提取旧IP列表
  749. for _, v := range ipData.BackendList {
  750. ip, _, err := net.SplitHostPort(v.Addr)
  751. if err != nil {
  752. return fmt.Errorf("解析旧IP地址失败: %w", err)
  753. }
  754. oldIps = append(oldIps, ip)
  755. }
  756. // 提取新IP列表
  757. for _, v := range req.WebForwardingData.BackendList {
  758. ip, _, err := net.SplitHostPort(v.Addr)
  759. if err != nil {
  760. return fmt.Errorf("解析新IP地址失败: %w", err)
  761. }
  762. newIps = append(newIps, ip)
  763. }
  764. // 查找IP差异
  765. addedIps, removedIps := s.wafformatter.FindIpDifferences(oldIps, newIps)
  766. // 异步处理添加的IP
  767. if len(addedIps) > 0 {
  768. go s.wafformatter.PublishIpWhitelistTask(addedIps, "add", "", "white")
  769. }
  770. // 异步处理删除的IP
  771. if len(removedIps) > 0 {
  772. go func() {
  773. ipsToDelist, err := s.wafformatter.WashDelIps(ctx, removedIps)
  774. if err != nil {
  775. return
  776. }
  777. if len(ipsToDelist) > 0 {
  778. s.wafformatter.PublishIpWhitelistTask(ipsToDelist, "del", "0", "white")
  779. }
  780. }()
  781. }
  782. return nil
  783. }
  784. // ProcessDeleteIpWhitelist 处理删除IP白名单
  785. func (s *aidedWebService) ProcessDeleteIpWhitelist(ctx context.Context, id int) error {
  786. ipData, err := s.webForwardingRepository.GetWebForwardingIpsByID(ctx, id)
  787. if err != nil {
  788. return fmt.Errorf("获取IP数据失败: %w", err)
  789. }
  790. if ipData != nil && len(ipData.BackendList) > 0 {
  791. var ips []string
  792. for _, v := range ipData.BackendList {
  793. ip, _, err := net.SplitHostPort(v.Addr)
  794. if err != nil {
  795. continue
  796. }
  797. ips = append(ips, ip)
  798. }
  799. if len(ips) > 0 {
  800. go func() {
  801. ipsToDelist, err := s.wafformatter.WashDelIps(ctx, ips)
  802. if err != nil {
  803. return
  804. }
  805. if len(ipsToDelist) > 0 {
  806. s.wafformatter.PublishIpWhitelistTask(ipsToDelist, "del", "0", "white")
  807. }
  808. }()
  809. }
  810. }
  811. return nil
  812. }
  813. // ProcessDomainWhitelistChanges 处理域名白名单变更
  814. func (s *aidedWebService) ProcessDomainWhitelistChanges(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, require common.RequireResponse) error {
  815. if oldData.Domain != req.WebForwardingData.Domain {
  816. firstIp, err := s.gatewayIp.GetGatewayipByHostIdFirst(ctx, int64(req.HostId), int64(req.Uid))
  817. if err != nil {
  818. return fmt.Errorf("获取网关IP失败: %w", err)
  819. }
  820. newDomain, err := s.wafformatter.ConvertToWildcardDomain(ctx, req.WebForwardingData.Domain)
  821. if err != nil {
  822. return fmt.Errorf("转换新域名失败: %w", err)
  823. }
  824. oldDomain, err := s.wafformatter.ConvertToWildcardDomain(ctx, oldData.Domain)
  825. if err != nil {
  826. return fmt.Errorf("转换旧域名失败: %w", err)
  827. }
  828. if len(require.GatewayIps) == 0 {
  829. return fmt.Errorf("网关组不存在")
  830. }
  831. // 检查旧域名使用数量
  832. count, err := s.webForwardingRepository.GetDomainCount(ctx, req.HostId, oldData.Domain)
  833. if err != nil {
  834. return fmt.Errorf("获取域名使用数量失败: %w", err)
  835. }
  836. // 异步处理域名白名单变更
  837. go func() {
  838. if count < 2 {
  839. s.wafformatter.PublishDomainWhitelistTask(oldDomain, firstIp, "del")
  840. }
  841. s.wafformatter.PublishDomainWhitelistTask(newDomain, firstIp, "add")
  842. }()
  843. }
  844. return nil
  845. }
  846. // ProcessDeleteDomainWhitelist 处理删除域名白名单
  847. func (s *aidedWebService) ProcessDeleteDomainWhitelist(ctx context.Context, oldData *model.WebForwarding, uid int) error {
  848. if oldData.Domain != "" {
  849. firstIp, err := s.gatewayIp.GetGatewayipByHostIdFirst(ctx, int64(oldData.HostId), int64(uid))
  850. if err != nil {
  851. return fmt.Errorf("获取网关IP失败: %w", err)
  852. }
  853. doMain, err := s.wafformatter.ConvertToWildcardDomain(ctx, oldData.Domain)
  854. if err != nil {
  855. return fmt.Errorf("转换域名失败: %w", err)
  856. }
  857. go s.wafformatter.PublishDomainWhitelistTask(doMain, firstIp, "del")
  858. }
  859. return nil
  860. }
  861. // SaveToDatabase 保存到数据库
  862. func (s *aidedWebService) SaveToDatabase(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse, webId int64, cdnOriginIds map[string]int64) (int, error) {
  863. webModel := s.BuildWebForwardingModel(&req.WebForwardingData, int(webId), require)
  864. id, err := s.webForwardingRepository.AddWebForwarding(ctx, webModel)
  865. if err != nil {
  866. return 0, fmt.Errorf("添加Web转发记录失败: %w", err)
  867. }
  868. webRuleModel := s.BuildWebRuleModel(&req.WebForwardingData, require, id, cdnOriginIds)
  869. if _, err = s.webForwardingRepository.AddWebForwardingIps(ctx, *webRuleModel); err != nil {
  870. return 0, fmt.Errorf("添加Web转发规则失败: %w", err)
  871. }
  872. return id, nil
  873. }
  874. // UpdateDatabaseRecords 更新数据库记录
  875. func (s *aidedWebService) UpdateDatabaseRecords(ctx context.Context, req *v1.WebForwardingRequest, require common.RequireResponse, ipData *model.WebForwardingRule) error {
  876. webModel := s.BuildWebForwardingModel(&req.WebForwardingData, req.WebForwardingData.CdnWebId, require)
  877. webModel.Id = req.WebForwardingData.Id
  878. if err := s.webForwardingRepository.EditWebForwarding(ctx, webModel); err != nil {
  879. return fmt.Errorf("更新Web转发记录失败: %w", err)
  880. }
  881. webRuleModel := s.BuildWebRuleModel(&req.WebForwardingData, require, req.WebForwardingData.Id, ipData.CdnOriginIds)
  882. if err := s.webForwardingRepository.EditWebForwardingIps(ctx, *webRuleModel); err != nil {
  883. return fmt.Errorf("更新Web转发规则失败: %w", err)
  884. }
  885. return nil
  886. }
  887. // CleanupDatabaseRecords 清理数据库记录
  888. func (s *aidedWebService) CleanupDatabaseRecords(ctx context.Context, id int) error {
  889. if err := s.webForwardingRepository.DeleteWebForwarding(ctx, int64(id)); err != nil {
  890. return fmt.Errorf("删除Web转发记录失败: %w", err)
  891. }
  892. if err := s.webForwardingRepository.DeleteWebForwardingIpsById(ctx, id); err != nil {
  893. return fmt.Errorf("删除Web转发规则失败: %w", err)
  894. }
  895. return nil
  896. }
  897. // ProcessSSLCertificateUpdate 处理SSL证书更新
  898. func (s *aidedWebService) ProcessSSLCertificateUpdate(ctx context.Context, req *v1.WebForwardingRequest, oldData *model.WebForwarding, cdnUid int) error {
  899. if !s.IsHttpsProtocol(req.WebForwardingData.IsHttps) {
  900. return nil // 非HTTPS协议不需要处理SSL证书
  901. }
  902. // 如果证书ID为0
  903. if oldData.SslCertId == 0 {
  904. err := s.ProcessSSLCertificate(ctx, req, cdnUid)
  905. if err != nil {
  906. return fmt.Errorf("处理SSL证书失败: %w", err)
  907. }
  908. return nil
  909. }
  910. // 如果证书内容有变化
  911. if oldData.HttpsCert != req.WebForwardingData.HttpsCert || oldData.HttpsKey != req.WebForwardingData.HttpsKey {
  912. if err := s.sslCert.EditSSLCert(ctx, v1.SSL{
  913. Name: req.WebForwardingData.Domain,
  914. CertId: oldData.SslCertId,
  915. CertData: req.WebForwardingData.HttpsCert,
  916. KeyData: req.WebForwardingData.HttpsKey,
  917. CdnUserId: cdnUid,
  918. Domain: req.WebForwardingData.Domain,
  919. Description: req.WebForwardingData.Comment,
  920. }); err != nil {
  921. return fmt.Errorf("更新SSL证书失败: %w", err)
  922. }
  923. }
  924. return nil
  925. }
  926. // CleanupSSLCertificate 清理SSL证书
  927. func (s *aidedWebService) CleanupSSLCertificate(ctx context.Context, oldData *model.WebForwarding) error {
  928. if oldData.SslCertId != 0 {
  929. if err := s.cdn.DelSSLCert(ctx, int64(oldData.SslCertId)); err != nil {
  930. return fmt.Errorf("删除SSL证书失败: %w", err)
  931. }
  932. if err := s.sslCert.EditSslPolicy(ctx, int64(oldData.SslPolicyId), []int64{int64(oldData.SslCertId)}, "del"); err != nil {
  933. return fmt.Errorf("删除SSL策略失败: %w", err)
  934. }
  935. }
  936. return nil
  937. }