package imadmin import ( "bytes" "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "io" "net/http" "strconv" "strings" "time" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" ) func (c *Client) InternalGet(ctx context.Context, path string, out any) error { return c.internalJSON(ctx, http.MethodGet, path, nil, out) } func (c *Client) InternalPost(ctx context.Context, path string, in any, out any) error { body, err := json.Marshal(in) if err != nil { return gerror.Wrap(err, "序列化IM内部请求失败") } return c.internalJSON(ctx, http.MethodPost, path, body, out) } func (c *Client) internalJSON(ctx context.Context, method, path string, body []byte, out any) error { req, err := c.newInternalRequest(ctx, method, path, body) if err != nil { return err } resp, err := c.http.Do(req) if err != nil { g.Log().Warningf(ctx, "IM internal query request failed url:%s err:%+v", req.URL.String(), err) return gerror.New("IM内部查询服务暂不可用") } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return c.parseError(resp) } if out == nil { return nil } responseBody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBody)) if err != nil { return gerror.Wrap(err, "读取IM内部查询响应失败") } var envelope struct { Code int `json:"code"` Message string `json:"message"` Reason string `json:"reason"` Data json.RawMessage `json:"data"` } if err = json.Unmarshal(responseBody, &envelope); err == nil && len(envelope.Data) > 0 { if envelope.Code != 0 { return stableError(envelope.Message, envelope.Reason) } return json.Unmarshal(envelope.Data, out) } return json.Unmarshal(responseBody, out) } func (c *Client) newInternalRequest(ctx context.Context, method string, path string, bodyBytes []byte) (*http.Request, error) { baseUrl := strings.TrimRight(c.conf.InternalBaseUrl, "/") if baseUrl == "" { baseUrl = strings.TrimRight(c.conf.BaseUrl, "/") } if baseUrl == "" { return nil, gerror.New("IM内部查询API未配置") } serviceName := strings.TrimSpace(c.conf.InternalServiceName) if serviceName == "" { serviceName = strings.TrimSpace(c.conf.ServiceId) } if serviceName == "" { return nil, gerror.New("IM内部查询服务名未配置") } secret := c.conf.InternalServiceSecret if secret == "" { secret = c.conf.ServiceSecret } if secret == "" { return nil, gerror.New("IM内部查询API密钥未配置") } req, err := http.NewRequestWithContext(ctx, method, baseUrl+path, bytes.NewReader(bodyBytes)) if err != nil { return nil, gerror.Wrap(err, "创建IM内部查询请求失败") } timestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10) nonce, err := randomNonce() if err != nil { return nil, err } bodyHash := sha256.Sum256(bodyBytes) signingText := strings.Join([]string{ method, requestTarget(req), hex.EncodeToString(bodyHash[:]), timestamp, nonce, }, "\n") mac := hmac.New(sha256.New, []byte(secret)) _, _ = mac.Write([]byte(signingText)) req.Header.Set("Accept", "application/json") req.Header.Set("X-Service-Name", serviceName) req.Header.Set("X-Timestamp", timestamp) req.Header.Set("X-Nonce", nonce) req.Header.Set("X-Signature", hex.EncodeToString(mac.Sum(nil))) return req, nil }