package qiyuesuosdk import ( "bytes" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/url" "strings" "time" "git.sxidc.com/student-physical-examination/contract_lock_sdk/utils" "github.com/google/uuid" ) // Client 契约锁开放平台客户端;仅封装 HTTP/签名与契约锁 API,不含业务状态机。 type Client struct { cfg Config signDefaults SignDefaults sdk *utils.SdkClient http *http.Client } // New 创建客户端。signDefaults 可为零值,签章时可按请求单独传入 ProcessID/Launcher。 func New(cfg Config, signDefaults SignDefaults) *Client { address := strings.TrimRight(cfg.Address, "/") return &Client{ cfg: cfg, signDefaults: signDefaults, sdk: utils.NewSdkClient(address, cfg.AppToken, cfg.AppSecret), http: &http.Client{Timeout: 60 * time.Second}, } } func (c *Client) baseURL() string { return strings.TrimRight(c.cfg.Address, "/") } func (c *Client) authHeaders() map[string]string { nonce := uuid.NewString() timestamp := fmt.Sprintf("%d", time.Now().UnixMilli()) raw := c.cfg.AppToken + c.cfg.AppSecret + timestamp + nonce sum := md5.Sum([]byte(raw)) return map[string]string{ "x-qys-accesstoken": c.cfg.AppToken, "x-qys-timestamp": timestamp, "x-qys-nonce": nonce, "x-qys-signature": hex.EncodeToString(sum[:]), } } func (c *Client) postJSON(path string, body any, out any) error { payload, err := json.Marshal(body) if err != nil { return err } req, err := http.NewRequest(http.MethodPost, c.baseURL()+path, bytes.NewReader(payload)) if err != nil { return err } req.Header.Set("Content-Type", "application/json;charset=UTF-8") for k, v := range c.authHeaders() { req.Header.Set(k, v) } return c.doJSON(req, out) } func (c *Client) getQuery(path string, query url.Values, out any) error { u := c.baseURL() + path if len(query) > 0 { u += "?" + query.Encode() } req, err := http.NewRequest(http.MethodGet, u, nil) if err != nil { return err } for k, v := range c.authHeaders() { req.Header.Set(k, v) } return c.doJSON(req, out) } func (c *Client) doJSON(req *http.Request, out any) error { resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return err } if out == nil { return nil } return json.Unmarshal(data, out) } func (c *Client) postMultipart(path string, fields map[string]string, fileField string, fileBytes []byte, out any) error { var buf bytes.Buffer w := multipart.NewWriter(&buf) for k, v := range fields { if err := w.WriteField(k, v); err != nil { return err } } if len(fileBytes) > 0 && fileField != "" { part, err := w.CreateFormFile(fileField, "document.pdf") if err != nil { return err } if _, err = part.Write(fileBytes); err != nil { return err } } if err := w.Close(); err != nil { return err } req, err := http.NewRequest(http.MethodPost, c.baseURL()+path, &buf) if err != nil { return err } req.Header.Set("Content-Type", w.FormDataContentType()) for k, v := range c.authHeaders() { req.Header.Set(k, v) } return c.doJSON(req, out) } func (c *Client) download(path string, query url.Values) ([]byte, error) { u := c.baseURL() + path if len(query) > 0 { u += "?" + query.Encode() } req, err := http.NewRequest(http.MethodGet, u, nil) if err != nil { return nil, err } for k, v := range c.authHeaders() { req.Header.Set(k, v) } resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) } func (c *Client) serviceOK(resp *utils.SdkResponse) error { if resp == nil { return fmt.Errorf("qiyuesuo: empty sdk response") } if resp.Message == "SUCCESS" { return nil } return fmt.Errorf("qiyuesuo: %s", resp.Message) } func (c *Client) serviceString(resp *utils.SdkResponse) (string, error) { if err := c.serviceOK(resp); err != nil { return "", err } if resp.Result == nil { return "", nil } if s, ok := resp.Result.(string); ok { return s, nil } return "", fmt.Errorf("qiyuesuo: unexpected result type %T", resp.Result) } func (c *Client) resolveProcessID(id string) string { if id != "" { return id } return c.signDefaults.ProcessID } func (c *Client) resolveLauncher(name string) string { if name != "" { return name } return c.signDefaults.TenantName }