package osi import ( "context" "encoding/json" "fmt" "net/http" "strconv" "strings" "time" "chis_osi/contract" ) type ClientConfig struct { BaseURL string OrgCode string DeviceSN string UserName string Ask string OperateUser string OperateUnit string Transport *Transport NowMillis func() string } type Client struct { config ClientConfig transport *Transport } type Result struct { HTTPStatus int Code string Message string Raw []byte Success bool Retryable bool } type responseEnvelope struct { Code string `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data"` } func NewClient(config ClientConfig) *Client { return &Client{config: config, transport: config.Transport} } func (c *Client) Call(ctx context.Context, serviceID string, baseInfo any, out any) (Result, error) { if c.transport == nil { transport, err := NewTransport(TransportConfig{}) if err != nil { return Result{}, err } c.transport = transport } path, err := PathOf(serviceID) if err != nil { return Result{}, err } payload := contract.Envelope{ ServiceID: serviceID, UploadInfo: contract.UploadInfo{ BaseInfo: baseInfo, ManageInfo: contract.ManageInfo{ DSFMC: c.config.UserName, OperateUser: c.config.OperateUser, OperateUnit: c.operateUnit(), }, }, } targetURL := c.urlFor(path) status, raw, err := c.transport.PostJSON(ctx, targetURL, payload, c.headers()) result := Result{HTTPStatus: status, Raw: raw, Retryable: isRetryableHTTPStatus(status)} if err != nil { result.Retryable = true return result, err } var resp responseEnvelope if err := json.Unmarshal(raw, &resp); err != nil { return result, fmt.Errorf("decode osi response: %w", err) } result.Code = resp.Code result.Message = resp.Message result.Success = IsSuccessCode(resp.Code) result.Retryable = result.Retryable || IsRetryableCode(resp.Code) if !result.Success { return result, fmt.Errorf("osi call failed code=%s message=%s", resp.Code, resp.Message) } if out != nil && len(resp.Data) > 0 && string(resp.Data) != "null" { if err := json.Unmarshal(resp.Data, out); err != nil { return result, fmt.Errorf("decode osi data: %w", err) } } return result, nil } func (c *Client) headers() map[string]string { ts := strconv.FormatInt(time.Now().UnixMilli(), 10) if c.config.NowMillis != nil { ts = c.config.NowMillis() } return BuildHeaders(HeaderInput{ OrgCode: c.config.OrgCode, DeviceSN: c.config.DeviceSN, UserName: c.config.UserName, Ask: c.config.Ask, TS: ts, }) } func (c *Client) operateUnit() string { if c.config.OperateUnit != "" { return c.config.OperateUnit } return c.config.OrgCode } func (c *Client) urlFor(path string) string { baseURL := strings.TrimRight(c.config.BaseURL, "/") if strings.HasSuffix(baseURL, "/osi/api") { return baseURL + path } return baseURL + "/osi/api" + path } func isRetryableHTTPStatus(status int) bool { return status == http.StatusTooManyRequests || status >= 500 }