aidedweb.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. package web
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  7. "github.com/go-nunu/nunu-layout-advanced/internal/model"
  8. "github.com/go-nunu/nunu-layout-advanced/internal/repository/api/waf"
  9. "github.com/go-nunu/nunu-layout-advanced/internal/service"
  10. "github.com/go-nunu/nunu-layout-advanced/internal/service/api/flexCdn"
  11. waf2 "github.com/go-nunu/nunu-layout-advanced/internal/service/api/waf"
  12. "github.com/go-nunu/nunu-layout-advanced/internal/service/api/waf/common"
  13. )
  14. // AidedWebService Web转发辅助服务接口
  15. type AidedWebServiceInterface interface {
  16. // 数据准备辅助函数
  17. PrepareWafData(ctx context.Context, req *v1.WebForwardingRequest) (common.RequireResponse, v1.Website, error)
  18. BuildProxyConfig(ctx context.Context, req *v1.WebForwardingRequest, gatewayIps []string) (v1.TypeJSON, error)
  19. BulidFormData(ctx context.Context, formData v1.Website) (v1.WebsiteSend, error)
  20. // 协议判断辅助函数
  21. GetProtocolType(isHttps int) string
  22. IsHttpsProtocol(isHttps int) bool
  23. // 模型构建辅助函数
  24. BuildWebForwardingModel(req *v1.WebForwardingDataRequest, ruleId int, require common.RequireResponse) *model.WebForwarding
  25. BuildWebRuleModel(reqData *v1.WebForwardingDataRequest, require common.RequireResponse, localDbId int, cdnOriginIds map[string]int64) *model.WebForwardingRule
  26. }
  27. func NewAidedWebService(
  28. service *service.Service,
  29. webForwardingRepository waf.WebForwardingRepository,
  30. wafformatter common.WafFormatterService,
  31. sslCert flexCdn.SslCertService,
  32. cdn flexCdn.CdnService,
  33. proxy flexCdn.ProxyService,
  34. websocket flexCdn.WebsocketService,
  35. cc waf2.CcService,
  36. ccIpList waf2.CcIpListService,
  37. gatewayIp common.GatewayipService,
  38. globalLimitRep waf.GlobalLimitRepository,
  39. ) *AidedWebService {
  40. return &AidedWebService{
  41. Service: service,
  42. webForwardingRepository: webForwardingRepository,
  43. wafformatter: wafformatter,
  44. sslCert: sslCert,
  45. cdn: cdn,
  46. proxy: proxy,
  47. websocket: websocket,
  48. cc: cc,
  49. ccIpList: ccIpList,
  50. gatewayIp: gatewayIp,
  51. globalLimitRep: globalLimitRep,
  52. }
  53. }
  54. type AidedWebService struct {
  55. *service.Service
  56. webForwardingRepository waf.WebForwardingRepository
  57. wafformatter common.WafFormatterService
  58. sslCert flexCdn.SslCertService
  59. cdn flexCdn.CdnService
  60. proxy flexCdn.ProxyService
  61. websocket flexCdn.WebsocketService
  62. cc waf2.CcService
  63. ccIpList waf2.CcIpListService
  64. gatewayIp common.GatewayipService
  65. globalLimitRep waf.GlobalLimitRepository
  66. }
  67. const (
  68. // 协议类型常量
  69. isHttps = 1
  70. isHttp = 0
  71. protocolHttps = "https"
  72. protocolHttp = "http"
  73. // 默认配置常量
  74. defaultNodeClusterId = 2
  75. proxyProtocolVersion = 1
  76. )
  77. // BuildWebForwardingModel 辅助函数,用于构建通用的 WebForwarding 模型
  78. // ruleId 是从 WAF 系统获取的 ID
  79. func (s *AidedWebService) BuildWebForwardingModel(req *v1.WebForwardingDataRequest, ruleId int, require common.RequireResponse) *model.WebForwarding {
  80. return &model.WebForwarding{
  81. HostId: require.HostId,
  82. CdnWebId: ruleId,
  83. Port: req.Port,
  84. Domain: req.Domain,
  85. IsHttps: req.IsHttps,
  86. Comment: req.Comment,
  87. HttpsCert: req.HttpsCert,
  88. HttpsKey: req.HttpsKey,
  89. SslCertId: int(req.SslCertId),
  90. SslPolicyId: int(req.SslPolicyId),
  91. Cc: req.CcConfig.IsOn,
  92. ThresholdMethod: req.CcConfig.ThresholdMethod,
  93. Level: req.CcConfig.Level,
  94. Limit5s: req.CcConfig.Limit5s,
  95. Limit60s: req.CcConfig.Limit60s,
  96. Limit300s: req.CcConfig.Limit300s,
  97. Proxy: req.Proxy,
  98. }
  99. }
  100. // BuildWebRuleModel 构建WebForwardingRule模型
  101. func (s *AidedWebService) BuildWebRuleModel(reqData *v1.WebForwardingDataRequest, require common.RequireResponse, localDbId int, cdnOriginIds map[string]int64) *model.WebForwardingRule {
  102. return &model.WebForwardingRule{
  103. Uid: require.Uid,
  104. HostId: require.HostId,
  105. WebId: localDbId,
  106. CdnOriginIds: cdnOriginIds,
  107. BackendList: reqData.BackendList,
  108. }
  109. }
  110. // getRequire 获取前置配置
  111. func (s *AidedWebService) getRequire (ctx context.Context, req *v1.WebForwardingRequest) (common.RequireResponse, error) {
  112. // 1. 获取基础配置
  113. require, err := s.wafformatter.Require(ctx, v1.GlobalRequire{
  114. HostId: req.HostId,
  115. Uid: req.Uid,
  116. Comment: req.WebForwardingData.Comment,
  117. })
  118. if err != nil {
  119. return common.RequireResponse{}, fmt.Errorf("获取WAF前置配置失败: %w", err)
  120. }
  121. if require.Uid == 0 {
  122. return common.RequireResponse{}, fmt.Errorf("请先配置实例")
  123. }
  124. return require, nil
  125. }
  126. // PrepareWafData 准备WAF数据
  127. // 职责:协调整个流程,负责获取前置配置和组装最终的 formData。
  128. func (s *AidedWebService) PrepareWafData(ctx context.Context, req *v1.WebForwardingRequest) (common.RequireResponse, v1.Website, error) {
  129. // 1. 获取前置配置
  130. require, err := s.getRequire(ctx, req)
  131. if err != nil {
  132. return common.RequireResponse{}, v1.Website{}, err
  133. }
  134. // 2. 调用辅助函数,构建核心的代理配置 (将复杂逻辑封装起来)
  135. byteData, err := s.BuildProxyConfig(ctx, req, require.GatewayIps)
  136. if err != nil {
  137. return common.RequireResponse{}, v1.Website{}, err // 错误信息在辅助函数中已经包装好了
  138. }
  139. type serverNames struct {
  140. ServerNames string `json:"name" form:"name"`
  141. Type string `json:"type" form:"type"`
  142. }
  143. var serverName []serverNames
  144. var serverJson []byte
  145. if req.WebForwardingData.Domain != "" {
  146. serverName = append(serverName, serverNames{
  147. ServerNames: req.WebForwardingData.Domain,
  148. Type: "full",
  149. })
  150. serverJson, err = json.Marshal(serverName)
  151. if err != nil {
  152. return common.RequireResponse{}, v1.Website{}, err
  153. }
  154. }
  155. // 3. 组装最终的 WAF 表单数据
  156. formData := v1.Website{
  157. UserId: int64(require.CdnUid),
  158. Type: "httpProxy",
  159. Name: require.Tag,
  160. ServerNamesJSON: serverJson,
  161. Description: req.WebForwardingData.Comment,
  162. ServerGroupIds: []int64{int64(require.GroupId)},
  163. NodeClusterId: defaultNodeClusterId,
  164. }
  165. // 4. 根据协议类型,填充 HttpJSON 和 HttpsJSON 字段
  166. if req.WebForwardingData.IsHttps == isHttps {
  167. formData.HttpJSON = v1.TypeJSON{IsOn: false}
  168. formData.HttpsJSON = byteData
  169. } else {
  170. formData.HttpJSON = byteData
  171. formData.HttpsJSON = v1.TypeJSON{IsOn: false}
  172. }
  173. return require, formData, nil
  174. }
  175. func (s *AidedWebService) buildSslPolicy(ctx context.Context, data *v1.WebForwardingDataRequest) (v1.SslPolicyRef, error) {
  176. // 如果不是 HTTPS,直接返回关闭状态的 SSL 策略
  177. if data.IsHttps != isHttps {
  178. return v1.SslPolicyRef{
  179. IsOn: false,
  180. SslPolicyId: data.SslPolicyId,
  181. }, nil
  182. }
  183. // --- 以下是 HTTPS 的逻辑 ---
  184. sslPolicyID := data.SslPolicyId
  185. // 如果请求中没有提供 SSL 策略 ID,则为其创建一个新的
  186. if sslPolicyID == 0 {
  187. var err error
  188. sslPolicyID, err = s.sslCert.AddSslPolicy(ctx, nil)
  189. if err != nil {
  190. // 如果创建失败,返回零值和错误
  191. return v1.SslPolicyRef{}, err
  192. }
  193. }
  194. // 返回开启状态的 HTTPS 策略
  195. return v1.SslPolicyRef{
  196. IsOn: true,
  197. SslPolicyId: sslPolicyID,
  198. }, nil
  199. }
  200. // BuildProxyConfig 构建代理配置
  201. // 职责:专门负责处理 HTTP/HTTPS 的差异,并生成对应的 JSON 配置。
  202. func (s *AidedWebService) BuildProxyConfig(ctx context.Context, req *v1.WebForwardingRequest, gatewayIps []string) (v1.TypeJSON, error) {
  203. // 第一步:构建 SSL 策略。所有复杂的 if/else 都被封装在辅助函数中
  204. sslPolicy, err := s.buildSslPolicy(ctx, &req.WebForwardingData)
  205. if err != nil {
  206. return v1.TypeJSON{}, err
  207. }
  208. // 更新请求中的 SSL 策略
  209. req.WebForwardingData.SslPolicyId = sslPolicy.SslPolicyId
  210. // 第二步:根据协议类型确定 apiType
  211. apiType := protocolHttp
  212. if req.WebForwardingData.IsHttps == isHttps {
  213. apiType = protocolHttps
  214. }
  215. // 第三步:构建通用的 Listen 配置
  216. listenConfigs := make([]v1.Listen, 0, len(gatewayIps))
  217. for _, ip := range gatewayIps {
  218. listenConfigs = append(listenConfigs, v1.Listen{
  219. Protocol: apiType,
  220. Host: ip,
  221. Port: req.WebForwardingData.Port,
  222. })
  223. }
  224. // 第四步:组装并返回最终结果
  225. jsonData := v1.TypeJSON{
  226. IsOn: true,
  227. SslPolicyRef: sslPolicy,
  228. Listen: listenConfigs,
  229. }
  230. return jsonData, nil
  231. }
  232. // BulidFormData 构建表单数据
  233. func (s *AidedWebService) BulidFormData(ctx context.Context, formData v1.Website) (v1.WebsiteSend, error) {
  234. httpJSON, err := json.Marshal(formData.HttpJSON)
  235. if err != nil {
  236. return v1.WebsiteSend{}, err
  237. }
  238. httpsJSON, err := json.Marshal(formData.HttpsJSON)
  239. if err != nil {
  240. return v1.WebsiteSend{}, err
  241. }
  242. formDataSend := v1.WebsiteSend{
  243. UserId: formData.UserId,
  244. AdminId: formData.AdminId,
  245. Type: formData.Type,
  246. Name: formData.Name,
  247. Description: formData.Description,
  248. ServerNamesJSON: formData.ServerNamesJSON,
  249. HttpJSON: httpJSON,
  250. HttpsJSON: httpsJSON,
  251. TcpJSON: formData.TcpJSON,
  252. TlsJSON: formData.TlsJSON,
  253. UdpJSON: formData.UdpJSON,
  254. WebId: formData.WebId,
  255. ReverseProxyJSON: formData.ReverseProxyJSON,
  256. ServerGroupIds: formData.ServerGroupIds,
  257. UserPlanId: formData.UserPlanId,
  258. NodeClusterId: formData.NodeClusterId,
  259. IncludeNodesJSON: formData.IncludeNodesJSON,
  260. ExcludeNodesJSON: formData.ExcludeNodesJSON,
  261. }
  262. return formDataSend, nil
  263. }
  264. // GetProtocolType 获取协议类型字符串
  265. func (s *AidedWebService) GetProtocolType(isHttps int) string {
  266. if s.IsHttpsProtocol(isHttps) {
  267. return protocolHttps
  268. }
  269. return protocolHttp
  270. }
  271. // IsHttpsProtocol 判断是否为HTTPS协议
  272. func (s *AidedWebService) IsHttpsProtocol(httpsFlag int) bool {
  273. return httpsFlag == isHttps
  274. }
  275. // updateWebsiteProtocolAndCert 更新网站协议和证书
  276. func (s *AidedWebService) updateWebsiteProtocolAndCert(ctx context.Context, isHttps int, cdnWebId int64, formData v1.Website) error {
  277. // 切换协议
  278. var typeConfig, closeConfig v1.TypeJSON
  279. var apiType, closeType string
  280. if s.IsHttpsProtocol(isHttps) {
  281. typeConfig = formData.HttpsJSON
  282. closeConfig = formData.HttpJSON
  283. apiType = s.GetProtocolType(isHttps)
  284. closeType = s.GetProtocolType(0) // HTTP
  285. } else {
  286. typeConfig = formData.HttpJSON
  287. closeConfig = formData.HttpsJSON
  288. apiType = s.GetProtocolType(isHttps)
  289. closeType = s.GetProtocolType(1) // HTTPS
  290. }
  291. typeJson, err := json.Marshal(typeConfig)
  292. if err != nil {
  293. return fmt.Errorf("序列化协议配置失败: %w", err)
  294. }
  295. closeJson, err := json.Marshal(closeConfig)
  296. if err != nil {
  297. return fmt.Errorf("序列化关闭协议配置失败: %w", err)
  298. }
  299. // 切换协议
  300. if err := s.cdn.EditServerType(ctx, v1.EditWebsite{
  301. Id: cdnWebId,
  302. TypeJSON: typeJson,
  303. }, apiType); err != nil {
  304. return fmt.Errorf("切换到%s协议失败: %w", apiType, err)
  305. }
  306. if err := s.cdn.EditServerType(ctx, v1.EditWebsite{
  307. Id: cdnWebId,
  308. TypeJSON: closeJson,
  309. }, closeType); err != nil {
  310. return fmt.Errorf("关闭%s协议失败: %w", closeType, err)
  311. }
  312. return nil
  313. }
  314. // updateWebsiteDomain 更新网站域名
  315. func (s *AidedWebService) updateWebsiteDomain(ctx context.Context, domain string, cdnWebId int64) error {
  316. type serverName struct {
  317. Name string `json:"name" form:"name"`
  318. Type string `json:"type" form:"type"`
  319. }
  320. var serverData []serverName
  321. serverData = append(serverData, serverName{
  322. Name: domain,
  323. Type: "full",
  324. })
  325. serverJson, err := json.Marshal(serverData)
  326. if err != nil {
  327. return fmt.Errorf("序列化服务器名称失败: %w", err)
  328. }
  329. if err := s.cdn.EditServerName(ctx, v1.EditServerNames{
  330. ServerId: cdnWebId,
  331. ServerNamesJSON: serverJson,
  332. }); err != nil {
  333. return fmt.Errorf("更新服务器名称失败: %w", err)
  334. }
  335. return nil
  336. }
  337. // updateWebsiteBasicInfo 更新网站基本信息
  338. func (s *AidedWebService) updateWebsiteBasicInfo(ctx context.Context, cdnWebId int64, tag string) error {
  339. // 通过globalLimitRep获取节点ID,这是项目中现有的方法
  340. nodeId, err := s.globalLimitRep.GetNodeId(ctx, int(cdnWebId))
  341. if err != nil {
  342. return fmt.Errorf("获取节点ID失败: %w", err)
  343. }
  344. if err := s.cdn.EditServerBasic(ctx, cdnWebId, tag, nodeId); err != nil {
  345. return fmt.Errorf("更新服务器基本信息失败: %w", err)
  346. }
  347. return nil
  348. }