Files
shop-crm-agent/internal/client/client.go
T

167 lines
7.6 KiB
Go
Raw Normal View History

package client
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
api "code.zhecent.com/open/shop-crm-agent/internal/api"
"google.golang.org/protobuf/proto"
)
const (
apiPath = "/api/shop/crm-agent/v1"
maxResponseSize = 16 << 20
)
type Client struct {
baseURL string
token string
httpClient *http.Client
}
func New(baseURL, token string, httpClient *http.Client) (*Client, error) {
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
parsed, err := url.Parse(baseURL)
if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
return nil, errors.New("CRM Agent API Base URL 必须是有效的 http/https URL")
}
if strings.TrimSpace(token) == "" {
return nil, errors.New("CRM Agent Token 不能为空")
}
if httpClient == nil {
httpClient = &http.Client{Timeout: 30 * time.Second}
}
return &Client{baseURL: baseURL, token: strings.TrimSpace(token), httpClient: httpClient}, nil
}
type responseWithHeader interface {
proto.Message
GetHeader() *api.ResponseHeader
}
func doRequest[Req proto.Message, Resp responseWithHeader](ctx context.Context, client *Client, path string, request Req, response Resp) (Resp, error) {
data, err := proto.Marshal(request)
if err != nil {
return response, fmt.Errorf("编码 Protobuf 请求失败: %w", err)
}
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, client.baseURL+apiPath+path, bytes.NewReader(data))
if err != nil {
return response, err
}
httpRequest.Header.Set("Content-Type", "application/x-protobuf")
httpRequest.Header.Set("Accept", "application/x-protobuf")
httpRequest.Header.Set("Authorization", "Bearer "+client.token)
httpResponse, err := client.httpClient.Do(httpRequest)
if err != nil {
return response, err
}
defer httpResponse.Body.Close()
body, err := io.ReadAll(io.LimitReader(httpResponse.Body, maxResponseSize+1))
if err != nil {
return response, err
}
if len(body) > maxResponseSize {
return response, errors.New("CRM Agent API 响应超过16 MiB限制")
}
if err := proto.Unmarshal(body, response); err != nil {
return response, fmt.Errorf("解析 Protobuf 响应失败(HTTP %d: %w", httpResponse.StatusCode, err)
}
header := response.GetHeader()
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
if header != nil && header.Message != "" {
return response, fmt.Errorf("CRM Agent API HTTP %d: %s", httpResponse.StatusCode, header.Message)
}
return response, fmt.Errorf("CRM Agent API HTTP %d", httpResponse.StatusCode)
}
if header == nil {
return response, errors.New("CRM Agent API 响应缺少 ResponseHeader")
}
if header.Code != 200 {
return response, fmt.Errorf("CRM Agent API 错误 %d: %s", header.Code, header.Message)
}
return response, nil
}
func (c *Client) CustomerList(ctx context.Context, req *api.CustomerListRequest) (*api.CustomerListResponse, error) {
return doRequest(ctx, c, "/customers/list", req, &api.CustomerListResponse{})
}
func (c *Client) ProjectList(ctx context.Context, req *api.ProjectListRequest) (*api.ProjectListResponse, error) {
return doRequest(ctx, c, "/projects/list", req, &api.ProjectListResponse{})
}
func (c *Client) ProjectGet(ctx context.Context, req *api.ProjectGetRequest) (*api.ProjectResponse, error) {
return doRequest(ctx, c, "/projects/get", req, &api.ProjectResponse{})
}
func (c *Client) ProjectCreate(ctx context.Context, req *api.ProjectCreateRequest) (*api.ProjectResponse, error) {
return doRequest(ctx, c, "/projects/create", req, &api.ProjectResponse{})
}
func (c *Client) ProjectUpdate(ctx context.Context, req *api.ProjectUpdateRequest) (*api.ProjectResponse, error) {
return doRequest(ctx, c, "/projects/update", req, &api.ProjectResponse{})
}
func (c *Client) ProjectStatus(ctx context.Context, req *api.ProjectStatusRequest) (*api.ProjectResponse, error) {
return doRequest(ctx, c, "/projects/status", req, &api.ProjectResponse{})
}
func (c *Client) ProjectDeletePreview(ctx context.Context, req *api.ProjectDeletePreviewRequest) (*api.DeletePreviewResponse, error) {
return doRequest(ctx, c, "/projects/delete/preview", req, &api.DeletePreviewResponse{})
}
func (c *Client) ProjectDelete(ctx context.Context, req *api.ProjectDeleteRequest) (*api.DeletePreviewResponse, error) {
return doRequest(ctx, c, "/projects/delete", req, &api.DeletePreviewResponse{})
}
func (c *Client) QuotationList(ctx context.Context, req *api.QuotationListRequest) (*api.QuotationListResponse, error) {
return doRequest(ctx, c, "/quotations/list", req, &api.QuotationListResponse{})
}
func (c *Client) QuotationGet(ctx context.Context, req *api.QuotationGetRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/get", req, &api.QuotationResponse{})
}
func (c *Client) QuotationCreate(ctx context.Context, req *api.QuotationCreateRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/create", req, &api.QuotationResponse{})
}
func (c *Client) QuotationCopy(ctx context.Context, req *api.QuotationCopyRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/copy", req, &api.QuotationResponse{})
}
func (c *Client) QuotationTitle(ctx context.Context, req *api.QuotationTitleRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/title", req, &api.QuotationResponse{})
}
func (c *Client) QuotationStatus(ctx context.Context, req *api.QuotationStatusRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/status", req, &api.QuotationResponse{})
}
func (c *Client) QuotationLock(ctx context.Context, req *api.QuotationLockRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/lock", req, &api.QuotationResponse{})
}
func (c *Client) QuotationUnlock(ctx context.Context, req *api.QuotationLockRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotations/unlock", req, &api.QuotationResponse{})
}
func (c *Client) QuotationDeletePreview(ctx context.Context, req *api.QuotationDeletePreviewRequest) (*api.DeletePreviewResponse, error) {
return doRequest(ctx, c, "/quotations/delete/preview", req, &api.DeletePreviewResponse{})
}
func (c *Client) QuotationDelete(ctx context.Context, req *api.QuotationDeleteRequest) (*api.DeletePreviewResponse, error) {
return doRequest(ctx, c, "/quotations/delete", req, &api.DeletePreviewResponse{})
}
func (c *Client) CatalogSearch(ctx context.Context, req *api.CatalogSearchRequest) (*api.CatalogSearchResponse, error) {
return doRequest(ctx, c, "/catalog/search", req, &api.CatalogSearchResponse{})
}
func (c *Client) EditBegin(ctx context.Context, req *api.QuotationEditBeginRequest) (*api.EditSessionResponse, error) {
return doRequest(ctx, c, "/quotation/edit/begin", req, &api.EditSessionResponse{})
}
func (c *Client) EditCommand(ctx context.Context, req *api.QuotationEditCommandRequest) (*api.QuotationEditCommandResponse, error) {
return doRequest(ctx, c, "/quotation/edit/command", req, &api.QuotationEditCommandResponse{})
}
func (c *Client) EditPreview(ctx context.Context, req *api.QuotationEditPreviewRequest) (*api.QuotationEditPreviewResponse, error) {
return doRequest(ctx, c, "/quotation/edit/preview", req, &api.QuotationEditPreviewResponse{})
}
func (c *Client) EditCommit(ctx context.Context, req *api.QuotationEditCommitRequest) (*api.QuotationResponse, error) {
return doRequest(ctx, c, "/quotation/edit/commit", req, &api.QuotationResponse{})
}
func (c *Client) EditDiscard(ctx context.Context, req *api.QuotationEditDiscardRequest) (*api.EditSessionResponse, error) {
return doRequest(ctx, c, "/quotation/edit/discard", req, &api.EditSessionResponse{})
}