2026-04-28 14:42:06 +08:00

211 lines
4.6 KiB
Go

package cav2
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hospital-admin-api/config"
"hospital-admin-api/global"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/sirupsen/logrus"
)
type Client struct {
BaseURL string
AppID string
AppSecret string
HTTPClient *http.Client
}
func NewClient(baseURL, appID, appSecret string) *Client {
return &Client{
BaseURL: strings.TrimRight(baseURL, "/"),
AppID: appID,
AppSecret: appSecret,
HTTPClient: &http.Client{
Timeout: 15 * time.Second,
},
}
}
func NewClientFromConfig() *Client {
baseURL := config.C.CaOnlineV2.CaOnlineAPIURL
appID := config.C.CaOnlineV2.CaOnlineAppID
appSecret := config.C.CaOnlineV2.CaOnlineAppSecret
if baseURL == "" {
baseURL = config.C.CaOnline.CaOnlineApiUrl
}
if appID == "" {
appID = config.C.CaOnline.CaOnlineAppId
}
if appSecret == "" {
appSecret = config.C.CaOnline.CaOnlineAppSecret
}
return NewClient(
baseURL,
appID,
appSecret,
)
}
func (c *Client) postForm(path string, form url.Values, out interface{}) error {
if c == nil {
return errors.New("ca v2 client is nil")
}
if c.BaseURL == "" || c.AppID == "" || c.AppSecret == "" {
return errors.New("ca v2 client config is incomplete")
}
if c.HTTPClient == nil {
c.HTTPClient = &http.Client{Timeout: 15 * time.Second}
}
payload := strings.NewReader(form.Encode())
req, err := http.NewRequest(http.MethodPost, c.BaseURL+path, payload)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("app_id", c.AppID)
req.Header.Set("signature", c.signature(form))
resp, err := c.HTTPClient.Do(req)
if err != nil {
c.logUpstreamError(path, form, "request_error", err.Error())
return err
}
defer func() {
_ = resp.Body.Close()
}()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
c.logUpstreamError(path, form, "read_body_error", err.Error())
return err
}
c.logUpstreamResponse(path, form, resp.StatusCode, string(respBody))
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ca v2 request failed: http %d", resp.StatusCode)
}
var envelope responseEnvelope
if err := json.Unmarshal(respBody, &envelope); err != nil {
c.logUpstreamError(path, form, "unmarshal_error", err.Error())
return err
}
if envelope.ResultCode != 0 {
if envelope.ResultMsg != "" {
c.logUpstreamError(path, form, "upstream_business_error", envelope.ResultMsg)
return errors.New(envelope.ResultMsg)
}
return fmt.Errorf("ca v2 request failed with code %d", envelope.ResultCode)
}
if out == nil {
return nil
}
body := strings.TrimSpace(string(envelope.Body))
if body == "" || body == "null" {
return nil
}
return json.Unmarshal(envelope.Body, out)
}
func (c *Client) postFormForString(path string, form url.Values) (string, error) {
var raw json.RawMessage
if err := c.postForm(path, form, &raw); err != nil {
return "", err
}
body := strings.TrimSpace(string(raw))
if body == "" || body == "null" {
return "", nil
}
var text string
if err := json.Unmarshal(raw, &text); err == nil {
return text, nil
}
var data map[string]string
if err := json.Unmarshal(raw, &data); err != nil {
return "", err
}
for _, key := range []string{"url", "authUrl", "faceUrl", "h5Url"} {
if value := strings.TrimSpace(data[key]); value != "" {
return value, nil
}
}
return "", errors.New("ca v2 string body not found")
}
func (c *Client) signature(form url.Values) string {
keys := make([]string, 0, len(form))
for key := range form {
keys = append(keys, key)
}
sort.Strings(keys)
values := make([]string, 0, len(keys))
for _, key := range keys {
for _, value := range form[key] {
values = append(values, value)
}
}
h := hmac.New(sha1.New, []byte(c.AppSecret))
h.Write([]byte(strings.Join(values, "&")))
return hex.EncodeToString(h.Sum(nil))
}
func setValue(form url.Values, key, value string) {
if strings.TrimSpace(value) == "" {
return
}
form.Set(key, value)
}
func (c *Client) logUpstreamResponse(path string, form url.Values, statusCode int, body string) {
if global.Logger == nil {
return
}
global.Logger.WithFields(logrus.Fields{
"module": "ca_v2",
"upstream_path": path,
"upstream_status": statusCode,
"request_form": form.Encode(),
"response_body": body,
}).Info("ca v2 upstream response")
}
func (c *Client) logUpstreamError(path string, form url.Values, stage string, message string) {
if global.Logger == nil {
return
}
global.Logger.WithFields(logrus.Fields{
"module": "ca_v2",
"stage": stage,
"upstream_path": path,
"request_form": form.Encode(),
}).Error(message)
}