client.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package qiyuesuosdk
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "encoding/hex"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "mime/multipart"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "time"
  14. "git.sxidc.com/student-physical-examination/contract_lock_sdk/utils"
  15. "github.com/google/uuid"
  16. )
  17. // Client 契约锁开放平台客户端;仅封装 HTTP/签名与契约锁 API,不含业务状态机。
  18. type Client struct {
  19. cfg Config
  20. signDefaults SignDefaults
  21. sdk *utils.SdkClient
  22. http *http.Client
  23. }
  24. // New 创建客户端。signDefaults 可为零值,签章时可按请求单独传入 ProcessID/Launcher。
  25. func New(cfg Config, signDefaults SignDefaults) *Client {
  26. address := strings.TrimRight(cfg.Address, "/")
  27. return &Client{
  28. cfg: cfg,
  29. signDefaults: signDefaults,
  30. sdk: utils.NewSdkClient(address, cfg.AppToken, cfg.AppSecret),
  31. http: &http.Client{Timeout: 60 * time.Second},
  32. }
  33. }
  34. func (c *Client) baseURL() string {
  35. return strings.TrimRight(c.cfg.Address, "/")
  36. }
  37. // PortalURL 契约锁用户前台地址(制章、验证码登录)。
  38. func (c *Client) PortalURL() string {
  39. return UserPortalURL(c.baseURL())
  40. }
  41. func (c *Client) authHeaders() map[string]string {
  42. nonce := uuid.NewString()
  43. timestamp := fmt.Sprintf("%d", time.Now().UnixMilli())
  44. raw := c.cfg.AppToken + c.cfg.AppSecret + timestamp + nonce
  45. sum := md5.Sum([]byte(raw))
  46. return map[string]string{
  47. "x-qys-accesstoken": c.cfg.AppToken,
  48. "x-qys-timestamp": timestamp,
  49. "x-qys-nonce": nonce,
  50. "x-qys-signature": hex.EncodeToString(sum[:]),
  51. }
  52. }
  53. func (c *Client) postJSON(path string, body any, out any) error {
  54. payload, err := json.Marshal(body)
  55. if err != nil {
  56. return err
  57. }
  58. req, err := http.NewRequest(http.MethodPost, c.baseURL()+path, bytes.NewReader(payload))
  59. if err != nil {
  60. return err
  61. }
  62. req.Header.Set("Content-Type", "application/json;charset=UTF-8")
  63. for k, v := range c.authHeaders() {
  64. req.Header.Set(k, v)
  65. }
  66. return c.doJSON(req, out)
  67. }
  68. func (c *Client) getQuery(path string, query url.Values, out any) error {
  69. u := c.baseURL() + path
  70. if len(query) > 0 {
  71. u += "?" + query.Encode()
  72. }
  73. req, err := http.NewRequest(http.MethodGet, u, nil)
  74. if err != nil {
  75. return err
  76. }
  77. for k, v := range c.authHeaders() {
  78. req.Header.Set(k, v)
  79. }
  80. return c.doJSON(req, out)
  81. }
  82. func (c *Client) doJSON(req *http.Request, out any) error {
  83. resp, err := c.http.Do(req)
  84. if err != nil {
  85. return err
  86. }
  87. defer resp.Body.Close()
  88. data, err := io.ReadAll(resp.Body)
  89. if err != nil {
  90. return err
  91. }
  92. if out == nil {
  93. return nil
  94. }
  95. return json.Unmarshal(data, out)
  96. }
  97. func (c *Client) postMultipart(path string, fields map[string]string, fileField string, fileBytes []byte, out any) error {
  98. var buf bytes.Buffer
  99. w := multipart.NewWriter(&buf)
  100. for k, v := range fields {
  101. if err := w.WriteField(k, v); err != nil {
  102. return err
  103. }
  104. }
  105. if len(fileBytes) > 0 && fileField != "" {
  106. part, err := w.CreateFormFile(fileField, "document.pdf")
  107. if err != nil {
  108. return err
  109. }
  110. if _, err = part.Write(fileBytes); err != nil {
  111. return err
  112. }
  113. }
  114. if err := w.Close(); err != nil {
  115. return err
  116. }
  117. req, err := http.NewRequest(http.MethodPost, c.baseURL()+path, &buf)
  118. if err != nil {
  119. return err
  120. }
  121. req.Header.Set("Content-Type", w.FormDataContentType())
  122. for k, v := range c.authHeaders() {
  123. req.Header.Set(k, v)
  124. }
  125. return c.doJSON(req, out)
  126. }
  127. func (c *Client) download(path string, query url.Values) ([]byte, error) {
  128. u := c.baseURL() + path
  129. if len(query) > 0 {
  130. u += "?" + query.Encode()
  131. }
  132. req, err := http.NewRequest(http.MethodGet, u, nil)
  133. if err != nil {
  134. return nil, err
  135. }
  136. for k, v := range c.authHeaders() {
  137. req.Header.Set(k, v)
  138. }
  139. resp, err := c.http.Do(req)
  140. if err != nil {
  141. return nil, err
  142. }
  143. defer resp.Body.Close()
  144. return io.ReadAll(resp.Body)
  145. }
  146. func (c *Client) serviceOK(resp *utils.SdkResponse) error {
  147. if resp == nil {
  148. return fmt.Errorf("qiyuesuo: empty sdk response")
  149. }
  150. if resp.Message == "SUCCESS" {
  151. return nil
  152. }
  153. return fmt.Errorf("qiyuesuo: %s", resp.Message)
  154. }
  155. func (c *Client) serviceString(resp *utils.SdkResponse) (string, error) {
  156. if err := c.serviceOK(resp); err != nil {
  157. return "", err
  158. }
  159. if resp.Result == nil {
  160. return "", nil
  161. }
  162. if s, ok := resp.Result.(string); ok {
  163. return s, nil
  164. }
  165. return "", fmt.Errorf("qiyuesuo: unexpected result type %T", resp.Result)
  166. }
  167. func (c *Client) resolveProcessID(id string) string {
  168. if id != "" {
  169. return id
  170. }
  171. return c.signDefaults.ProcessID
  172. }
  173. func (c *Client) resolveLauncher(name string) string {
  174. if name != "" {
  175. return name
  176. }
  177. return c.signDefaults.TenantName
  178. }