planeHttpClient.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package plane_v1
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "fwh-toolbox-server/config"
  7. "io"
  8. "net/http"
  9. "strings"
  10. "time"
  11. "go.uber.org/zap"
  12. )
  13. // 官方API手册
  14. // https://developers.plane.so/api-reference/introduction
  15. const (
  16. timeout time.Duration = 60 * time.Second // Plane请求超时60s
  17. )
  18. var (
  19. planeHost string
  20. planeApiKey string
  21. planeWorkspaceSlug string
  22. planeProjectId string
  23. planeLabelBugId string
  24. planeLabelFeatureId string
  25. )
  26. // PlaneConfig Plane配置结构
  27. type PlaneConfig struct {
  28. Host string
  29. ApiKey string
  30. WorkspaceSlug string
  31. ProjectId string
  32. LabelBugId string
  33. LabelFeatureId string
  34. }
  35. // PlaneHttpInit 初始化Plane HTTP客户端配置
  36. func PlaneHttpInit(cfg *config.PlaneApiConfig) {
  37. planeHost = strings.TrimRight(cfg.Host, "/")
  38. planeApiKey = cfg.ApiKey
  39. planeWorkspaceSlug = cfg.WorkspaceSlug
  40. planeProjectId = cfg.ProjectId
  41. planeLabelBugId = cfg.LabelBugId
  42. planeLabelFeatureId = cfg.LabelFeatureId
  43. }
  44. // GetLabelBugId 获取Bug标签ID
  45. func GetLabelBugId() string {
  46. return planeLabelBugId
  47. }
  48. // GetLabelFeatureId 获取Feature标签ID
  49. func GetLabelFeatureId() string {
  50. return planeLabelFeatureId
  51. }
  52. // planeResponse 封装Plane HTTP响应
  53. type planeResponse struct {
  54. StatusCode int
  55. Status string
  56. Body []byte
  57. }
  58. // planeHttpGet 发送GET请求到Plane API
  59. func planeHttpGet(relativeURL string) (*planeResponse, error) {
  60. url := planeHost + relativeURL
  61. zap.L().Debug("[Plane] HttpGet", zap.String("url", url))
  62. req, err := http.NewRequest(http.MethodGet, url, nil)
  63. if err != nil {
  64. return nil, fmt.Errorf("创建请求失败: %w", err)
  65. }
  66. req.Header.Set("X-API-Key", planeApiKey)
  67. req.Header.Set("Accept", "application/json")
  68. client := &http.Client{Timeout: timeout}
  69. resp, err := client.Do(req)
  70. if err != nil {
  71. return nil, fmt.Errorf("请求失败: %w", err)
  72. }
  73. defer resp.Body.Close()
  74. body, err := io.ReadAll(resp.Body)
  75. if err != nil {
  76. return nil, fmt.Errorf("读取响应失败: %w", err)
  77. }
  78. return &planeResponse{
  79. StatusCode: resp.StatusCode,
  80. Status: resp.Status,
  81. Body: body,
  82. }, nil
  83. }
  84. // planeHttpPost 发送POST请求到Plane API
  85. func planeHttpPost(relativeURL string, reqBody any) (*planeResponse, error) {
  86. url := planeHost + relativeURL
  87. zap.L().Debug("[Plane] HttpPost", zap.String("url", url))
  88. jsonData, err := json.Marshal(reqBody)
  89. if err != nil {
  90. return nil, fmt.Errorf("序列化请求体失败: %w", err)
  91. }
  92. req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(jsonData))
  93. if err != nil {
  94. return nil, fmt.Errorf("创建请求失败: %w", err)
  95. }
  96. req.Header.Set("X-API-Key", planeApiKey)
  97. req.Header.Set("Content-Type", "application/json")
  98. req.Header.Set("Accept", "application/json")
  99. client := &http.Client{Timeout: timeout}
  100. resp, err := client.Do(req)
  101. if err != nil {
  102. return nil, fmt.Errorf("请求失败: %w", err)
  103. }
  104. defer resp.Body.Close()
  105. body, err := io.ReadAll(resp.Body)
  106. if err != nil {
  107. return nil, fmt.Errorf("读取响应失败: %w", err)
  108. }
  109. return &planeResponse{
  110. StatusCode: resp.StatusCode,
  111. Status: resp.Status,
  112. Body: body,
  113. }, nil
  114. }
  115. // planeHttpPatch 发送PATCH请求到Plane API
  116. func planeHttpPatch(relativeURL string, reqBody any) (*planeResponse, error) {
  117. url := planeHost + relativeURL
  118. zap.L().Debug("[Plane] HttpPatch", zap.String("url", url))
  119. jsonData, err := json.Marshal(reqBody)
  120. if err != nil {
  121. return nil, fmt.Errorf("序列化请求体失败: %w", err)
  122. }
  123. req, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(jsonData))
  124. if err != nil {
  125. return nil, fmt.Errorf("创建请求失败: %w", err)
  126. }
  127. req.Header.Set("X-API-Key", planeApiKey)
  128. req.Header.Set("Content-Type", "application/json")
  129. req.Header.Set("Accept", "application/json")
  130. client := &http.Client{Timeout: timeout}
  131. resp, err := client.Do(req)
  132. if err != nil {
  133. return nil, fmt.Errorf("请求失败: %w", err)
  134. }
  135. defer resp.Body.Close()
  136. body, err := io.ReadAll(resp.Body)
  137. if err != nil {
  138. return nil, fmt.Errorf("读取响应失败: %w", err)
  139. }
  140. return &planeResponse{
  141. StatusCode: resp.StatusCode,
  142. Status: resp.Status,
  143. Body: body,
  144. }, nil
  145. }
  146. // planeHttpPut 发送PUT请求(用于上传文件到presigned URL,不带API Key)
  147. func planeHttpPut(url string, data []byte, contentType string) (*planeResponse, error) {
  148. zap.L().Debug("[Plane] HttpPut", zap.String("url", url))
  149. req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
  150. if err != nil {
  151. return nil, fmt.Errorf("创建请求失败: %w", err)
  152. }
  153. req.Header.Set("Content-Type", contentType)
  154. client := &http.Client{Timeout: timeout}
  155. resp, err := client.Do(req)
  156. if err != nil {
  157. return nil, fmt.Errorf("请求失败: %w", err)
  158. }
  159. defer resp.Body.Close()
  160. body, err := io.ReadAll(resp.Body)
  161. if err != nil {
  162. return nil, fmt.Errorf("读取响应失败: %w", err)
  163. }
  164. return &planeResponse{
  165. StatusCode: resp.StatusCode,
  166. Status: resp.Status,
  167. Body: body,
  168. }, nil
  169. }
  170. // isSuccess 判断HTTP状态码是否表示成功(2xx)
  171. func isSuccess(statusCode int) bool {
  172. return statusCode >= 200 && statusCode < 300
  173. }
  174. // planeHttpDelete 发送DELETE请求到Plane API
  175. func planeHttpDelete(relativeURL string) (*planeResponse, error) {
  176. url := planeHost + relativeURL
  177. zap.L().Debug("[Plane] HttpDelete", zap.String("url", url))
  178. req, err := http.NewRequest(http.MethodDelete, url, nil)
  179. if err != nil {
  180. return nil, fmt.Errorf("创建请求失败: %w", err)
  181. }
  182. req.Header.Set("X-API-Key", planeApiKey)
  183. req.Header.Set("Accept", "application/json")
  184. client := &http.Client{Timeout: timeout}
  185. resp, err := client.Do(req)
  186. if err != nil {
  187. return nil, fmt.Errorf("请求失败: %w", err)
  188. }
  189. defer resp.Body.Close()
  190. body, err := io.ReadAll(resp.Body)
  191. if err != nil {
  192. return nil, fmt.Errorf("读取响应失败: %w", err)
  193. }
  194. return &planeResponse{
  195. StatusCode: resp.StatusCode,
  196. Status: resp.Status,
  197. Body: body,
  198. }, nil
  199. }