cdn.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. package service
  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/repository"
  8. "github.com/spf13/viper"
  9. )
  10. type CdnService interface {
  11. GetToken(ctx context.Context) (string, error)
  12. AddUser(ctx context.Context, req v1.User) (int64, error)
  13. CreateGroup(ctx context.Context, req v1.Group) (int64, error)
  14. BindPlan(ctx context.Context, req v1.Plan) (int64, error)
  15. RenewPlan(ctx context.Context, req v1.RenewalPlan) error
  16. CreateWebsite(ctx context.Context, req v1.Website) (int64, error)
  17. EditProtocol(ctx context.Context, req v1.ProxyJson, action string) error
  18. CreateOrigin(ctx context.Context, req v1.Origin) (int64, error)
  19. EditOrigin(ctx context.Context, req v1.Origin) error
  20. }
  21. func NewCdnService(
  22. service *Service,
  23. conf *viper.Viper,
  24. request RequestService,
  25. cdnRepository repository.CdnRepository,
  26. ) CdnService {
  27. return &cdnService{
  28. Service: service,
  29. Url: conf.GetString("flexCdn.Url"),
  30. AccessKeyID: conf.GetString("flexCdn.AccessKeyID"),
  31. AccessKeySecret: conf.GetString("flexCdn.AccessKeySecret"),
  32. request: request,
  33. cdnRepository: cdnRepository,
  34. maxRetryCount: 3, // 可以配置最大重试次数
  35. retryDelaySeconds: 2, // 可以配置重试间隔
  36. }
  37. }
  38. type cdnService struct {
  39. *Service
  40. Url string
  41. AccessKeyID string
  42. AccessKeySecret string
  43. request RequestService
  44. cdnRepository repository.CdnRepository
  45. maxRetryCount int
  46. retryDelaySeconds int
  47. }
  48. // SendData 是一个通用的请求发送方法,它封装了 token 过期重试的逻辑
  49. func (s *cdnService) sendDataWithTokenRetry(ctx context.Context, formData map[string]interface{}, apiUrl string) ([]byte, error) {
  50. var resBody []byte
  51. for i := 0; i < s.maxRetryCount; i++ {
  52. token, err := s.Token(ctx) // 确保使用最新的 token
  53. if err != nil {
  54. return nil, fmt.Errorf("获取或刷新 token 失败: %w", err)
  55. }
  56. resBody, err = s.request.Request(ctx, formData, apiUrl, "X-Cloud-Access-Token", token)
  57. if err != nil {
  58. // 检查错误是否是由于 token 无效引起的
  59. if s.isTokenInvalidError(resBody, err) { // 判断是否是 token 无效错误
  60. _, getTokenErr := s.GetToken(ctx)
  61. if getTokenErr != nil {
  62. return nil, fmt.Errorf("刷新 token 失败: %w", getTokenErr)
  63. }
  64. continue // 继续下一次循环,使用新的 token
  65. }
  66. return nil, fmt.Errorf("请求失败: %w", err)
  67. }
  68. // 成功获取到响应,处理响应体
  69. var generalResponse v1.GeneralResponse[any]
  70. if err := json.Unmarshal(resBody, &generalResponse); err != nil {
  71. return nil, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  72. }
  73. // 检查 API 返回的 code 和 message
  74. if generalResponse.Code == 400 && generalResponse.Message == "invalid access token" {
  75. fmt.Printf("尝试 %d/%d:API 返回无效 token 错误,准备刷新并重试...\n", i+1, s.maxRetryCount)
  76. _, getTokenErr := s.GetToken(ctx)
  77. if getTokenErr != nil {
  78. return nil, fmt.Errorf("刷新 token 失败: %w", getTokenErr)
  79. }
  80. continue // 继续下一次循环,使用新的 token
  81. }
  82. // 成功处理,返回结果
  83. return resBody, nil
  84. }
  85. // 如果循环结束仍未成功,则返回最终错误
  86. return nil, fmt.Errorf("达到最大重试次数后请求仍然失败")
  87. }
  88. // isTokenInvalidError 是一个辅助函数,用于判断错误是否是由于 token 无效引起的。
  89. // 你需要根据你的 request.Request 实现来具体实现这个函数。
  90. // 例如,你可以检查 resBody 是否包含特定的错误信息。
  91. func (s *cdnService) isTokenInvalidError(resBody []byte, err error) bool {
  92. // 示例:如果请求本身就返回了非 200 的错误,并且响应体中有特定信息
  93. if err != nil {
  94. // 尝试从 resBody 中解析出错误信息,判断是否是 token 无效
  95. var generalResponse v1.GeneralResponse[any]
  96. if parseErr := json.Unmarshal(resBody, &generalResponse); parseErr == nil {
  97. if generalResponse.Code == 400 && generalResponse.Message == "invalid access token" {
  98. return true
  99. }
  100. }
  101. // 或者检查 err 本身是否有相关的错误信息
  102. // if strings.Contains(err.Error(), "invalid access token") {
  103. // return true
  104. // }
  105. }
  106. return false
  107. }
  108. func (s *cdnService) GetToken(ctx context.Context) (string, error) {
  109. formData := map[string]interface{}{
  110. "type": "admin",
  111. "accessKeyId": s.AccessKeyID,
  112. "accessKey": s.AccessKeySecret,
  113. }
  114. apiUrl := s.Url + "APIAccessTokenService/getAPIAccessToken"
  115. resBody, err := s.request.Request(ctx, formData, apiUrl, "X-Cloud-Access-Token", "")
  116. if err != nil {
  117. return "", err
  118. }
  119. var res v1.GeneralResponse[v1.FlexCdnTokenResponse]
  120. if err := json.Unmarshal(resBody, &res); err != nil {
  121. return "", fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  122. }
  123. if res.Code != 200 {
  124. return "", fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  125. }
  126. err = s.cdnRepository.PutToken(ctx, res.Data.Token)
  127. if err != nil {
  128. return "", err
  129. }
  130. return res.Data.Token, nil
  131. }
  132. func (s *cdnService) Token(ctx context.Context) (string, error) {
  133. token, err := s.cdnRepository.GetToken(ctx)
  134. if err != nil {
  135. return "", err
  136. }
  137. if token == "" {
  138. token, err = s.GetToken(ctx)
  139. if err != nil {
  140. return "", err
  141. }
  142. }
  143. return token, nil
  144. }
  145. //注册用户
  146. func (s *cdnService) AddUser(ctx context.Context, req v1.User) (int64, error) {
  147. formData := map[string]interface{}{
  148. "id": req.ID,
  149. "username": req.Username,
  150. "password": "a7fKiKujgAzzsJ6", // 这个密码应该被妥善管理,而不是硬编码
  151. "fullname": req.Fullname,
  152. "mobile": req.Mobile,
  153. "tel": req.Tel,
  154. "email": req.Email,
  155. "remark": req.Remark,
  156. "source": req.Source,
  157. "nodeClusterId": 1,
  158. }
  159. apiUrl := s.Url + "UserService/createUser"
  160. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl)
  161. if err != nil {
  162. return 0, err
  163. }
  164. type DataStr struct {
  165. UserId int64 `json:"userId" form:"userId"`
  166. }
  167. var res v1.GeneralResponse[DataStr]
  168. if err := json.Unmarshal(resBody, &res); err != nil {
  169. return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  170. }
  171. if res.Code != 200 {
  172. return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  173. }
  174. if res.Data.UserId == 0 {
  175. return 0, fmt.Errorf("添加用户失败")
  176. }
  177. return res.Data.UserId, nil
  178. }
  179. //创建规则分组
  180. func (s *cdnService) CreateGroup(ctx context.Context, req v1.Group) (int64, error) {
  181. formData := map[string]interface{}{
  182. "name": req.Name,
  183. }
  184. apiUrl := s.Url + "ServerGroupService/createServerGroup"
  185. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  186. if err != nil {
  187. return 0, err
  188. }
  189. type DataStr struct {
  190. ServerGroupId int64 `json:"serverGroupId" form:"serverGroupId"`
  191. }
  192. var res v1.GeneralResponse[DataStr]
  193. if err := json.Unmarshal(resBody, &res); err != nil {
  194. return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  195. }
  196. if res.Code != 200 {
  197. return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  198. }
  199. if res.Data.ServerGroupId == 0 {
  200. return 0, fmt.Errorf("创建规则分组失败")
  201. }
  202. return res.Data.ServerGroupId, nil
  203. }
  204. //分配套餐
  205. func (s *cdnService) BindPlan(ctx context.Context, req v1.Plan) (int64, error) {
  206. formData := map[string]interface{}{
  207. "userId": req.UserId,
  208. "planId": req.PlanId,
  209. "dayTo": req.DayTo,
  210. "period": req.Period,
  211. "countPeriod": req.CountPeriod,
  212. "name": req.Name,
  213. "isFree": req.IsFree,
  214. "periodDayTo": req.PeriodDayTo,
  215. }
  216. apiUrl := s.Url + "UserPlanService/buyUserPlan"
  217. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  218. if err != nil {
  219. return 0, err
  220. }
  221. type DataStr struct {
  222. UserPlanId int64 `json:"userPlanId" form:"userPlanId"`
  223. }
  224. var res v1.GeneralResponse[DataStr]
  225. if err := json.Unmarshal(resBody, &res); err != nil {
  226. return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  227. }
  228. if res.Code != 200 {
  229. return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  230. }
  231. if res.Data.UserPlanId == 0 {
  232. return 0, fmt.Errorf("分配套餐失败")
  233. }
  234. return res.Data.UserPlanId, nil
  235. }
  236. //续费套餐
  237. func (s *cdnService) RenewPlan(ctx context.Context, req v1.RenewalPlan) error {
  238. formData := map[string]interface{}{
  239. "userPlanId": req.UserPlanId,
  240. "dayTo": req.DayTo,
  241. "period": req.Period,
  242. "countPeriod": req.CountPeriod,
  243. "isFree": req.IsFree,
  244. "periodDayTo": req.PeriodDayTo,
  245. }
  246. apiUrl := s.Url + "UserPlanService/renewUserPlan"
  247. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  248. if err != nil {
  249. return err
  250. }
  251. var res v1.GeneralResponse[any]
  252. if err := json.Unmarshal(resBody, &res); err != nil {
  253. return fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  254. }
  255. if res.Code != 200 {
  256. return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  257. }
  258. return nil
  259. }
  260. //创建网站
  261. func (s *cdnService) CreateWebsite(ctx context.Context, req v1.Website) (int64, error) {
  262. formData := map[string]interface{}{
  263. "userId": req.UserId,
  264. "type": req.Type,
  265. "name": req.Name,
  266. "description": req.Description,
  267. "serverNamesJSON": req.ServerNamesJSON,
  268. "httpJSON": req.HttpJSON,
  269. "httpsJSON": req.HttpsJSON,
  270. "tcpJSON": req.TcpJSON,
  271. "tlsJSON": req.TlsJSON,
  272. "udpJSON": req.UdpJSON,
  273. "webId": req.WebId,
  274. "reverseProxyJSON": req.ReverseProxyJSON,
  275. "serverGroupIds": req.ServerGroupIds,
  276. "userPlanId": req.UserPlanId,
  277. "nodeClusterId": req.NodeClusterId,
  278. }
  279. apiUrl := s.Url + "ServerService/createServer"
  280. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  281. if err != nil {
  282. return 0, err
  283. }
  284. type DataStr struct {
  285. WebsiteId int64 `json:"websiteId" form:"websiteId"`
  286. }
  287. var res v1.GeneralResponse[DataStr]
  288. if err := json.Unmarshal(resBody, &res); err != nil {
  289. return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  290. }
  291. if res.Code != 200 {
  292. return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  293. }
  294. if res.Data.WebsiteId == 0 {
  295. return 0, fmt.Errorf("创建网站失败")
  296. }
  297. return res.Data.WebsiteId, nil
  298. }
  299. func (s *cdnService) EditProtocol(ctx context.Context, req v1.ProxyJson, action string) error {
  300. formData := map[string]interface{}{
  301. "serverId": req.ServerId,
  302. }
  303. var apiUrl string
  304. switch action {
  305. case "tcp":
  306. formData["tcpJSON"] = req.JSON
  307. apiUrl = s.Url + "ServerService/updateServerTCP"
  308. case "tls":
  309. formData["tlsJSON"] = req.JSON
  310. apiUrl = s.Url + "ServerService/updateServerTLS"
  311. case "udp":
  312. formData["udpJSON"] = req.JSON
  313. apiUrl = s.Url + "ServerService/updateServerUDP"
  314. case "http":
  315. formData["httpJSON"] = req.JSON
  316. apiUrl = s.Url + "ServerService/updateServerHTTP"
  317. case "https":
  318. formData["httpsJSON"] = req.JSON
  319. apiUrl = s.Url + "ServerService/updateServerHTTPS"
  320. default:
  321. return fmt.Errorf("不支持的协议类型")
  322. }
  323. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  324. if err != nil {
  325. return err
  326. }
  327. var res v1.GeneralResponse[any]
  328. if err := json.Unmarshal(resBody, &res); err != nil {
  329. return fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  330. }
  331. if res.Code != 200 {
  332. return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  333. }
  334. return nil
  335. }
  336. func (s *cdnService) CreateOrigin(ctx context.Context, req v1.Origin) (int64, error) {
  337. formData := map[string]interface{}{
  338. "name": req.Name,
  339. "addr": req.Addr,
  340. "ossJSON": req.OssJSON,
  341. "description": req.Description,
  342. "weight": req.Weight,
  343. "isOn": req.IsOn,
  344. "domains": req.Domains,
  345. "certRefJSON": req.CertRefJSON,
  346. "host": req.Host,
  347. "followPort": req.FollowPort,
  348. "http2Enabled": req.Http2Enabled,
  349. "tlsSecurityVerifyMode": req.TlsSecurityVerifyMode,
  350. }
  351. apiUrl := s.Url + "OriginService/createOrigin"
  352. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  353. if err != nil {
  354. return 0, err
  355. }
  356. type DataStr struct {
  357. OriginId int64 `json:"originId" form:"originId"`
  358. }
  359. var res v1.GeneralResponse[DataStr]
  360. if err := json.Unmarshal(resBody, &res); err != nil {
  361. return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  362. }
  363. if res.Code != 200 {
  364. return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  365. }
  366. if res.Data.OriginId == 0 {
  367. return 0, fmt.Errorf("创建源站失败")
  368. }
  369. return res.Data.OriginId, nil
  370. }
  371. func (s *cdnService) EditOrigin(ctx context.Context, req v1.Origin) error {
  372. formData := map[string]interface{}{
  373. "originId": req.OriginId,
  374. "name": req.Name,
  375. "addr": req.Addr,
  376. "ossJSON": req.OssJSON,
  377. "description": req.Description,
  378. "weight": req.Weight,
  379. "isOn": req.IsOn,
  380. "domains": req.Domains,
  381. "certRefJSON": req.CertRefJSON,
  382. "host": req.Host,
  383. "followPort": req.FollowPort,
  384. "http2Enabled": req.Http2Enabled,
  385. "tlsSecurityVerifyMode": req.TlsSecurityVerifyMode,
  386. }
  387. apiUrl := s.Url + "OriginService/updateOrigin"
  388. resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
  389. if err != nil {
  390. return err
  391. }
  392. var res v1.GeneralResponse[any]
  393. if err := json.Unmarshal(resBody, &res); err != nil {
  394. return fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
  395. }
  396. if res.Code != 200 {
  397. return fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
  398. }
  399. return nil
  400. }