package tianyancha import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strings" "time" ) var ( ErrDatasource = errors.New("数据源异常") ErrNotFound = errors.New("查询为空") ErrSystem = errors.New("系统异常") ErrInvalidParam = errors.New("参数错误") ) // APIEndpoints 天眼查 API 端点映射 var APIEndpoints = map[string]string{ "VerifyThreeElements": "/open/ic/verify/2.0", // 企业三要素验证 "InvestHistory": "/open/hi/invest/2.0", // 对外投资历史 "FinancingHistory": "/open/cd/findHistoryRongzi/2.0", // 融资历史 "PunishmentInfo": "/open/mr/punishmentInfo/3.0", // 行政处罚 "AbnormalInfo": "/open/mr/abnormal/2.0", // 经营异常 "OwnTax": "/open/mr/ownTax", // 欠税公告 "TaxContravention": "/open/mr/taxContravention", // 税收违法 } // TianYanChaConfig 天眼查配置 type TianYanChaConfig struct { BaseURL string Token string Timeout time.Duration } // TianYanChaService 天眼查服务 type TianYanChaService struct { config TianYanChaConfig } // APIResponse 标准API响应结构 type APIResponse struct { Success bool `json:"success"` Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } // TianYanChaResponse 天眼查原始响应结构 type TianYanChaResponse struct { ErrorCode int `json:"error_code"` Reason string `json:"reason"` Result interface{} `json:"result"` } // NewTianYanChaService 创建天眼查服务实例 func NewTianYanChaService(baseURL, token string, timeout time.Duration) *TianYanChaService { if timeout == 0 { timeout = 30 * time.Second } return &TianYanChaService{ config: TianYanChaConfig{ BaseURL: baseURL, Token: token, Timeout: timeout, }, } } // CallAPI 调用天眼查API - 通用方法,由外部处理器传入具体参数 func (t *TianYanChaService) CallAPI(ctx context.Context, apiCode string, params map[string]string) (*APIResponse, error) { // 从映射中获取 API 端点 endpoint, exists := APIEndpoints[apiCode] if !exists { return nil, errors.Join(ErrInvalidParam, fmt.Errorf("未找到 API 代码对应的端点: %s", apiCode)) } // 构建完整 URL fullURL := strings.TrimRight(t.config.BaseURL, "/") + "/" + strings.TrimLeft(endpoint, "/") // 检查 Token 是否配置 if t.config.Token == "" { return nil, errors.Join(ErrSystem, fmt.Errorf("天眼查 API Token 未配置")) } // 构建查询参数 queryParams := url.Values{} for key, value := range params { queryParams.Set(key, value) } // 构建完整URL requestURL := fullURL if len(queryParams) > 0 { requestURL += "?" + queryParams.Encode() } // 创建请求 req, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil) if err != nil { return nil, errors.Join(ErrSystem, fmt.Errorf("创建请求失败: %v", err)) } // 设置请求头 req.Header.Set("Authorization", t.config.Token) // 发送请求 client := &http.Client{Timeout: t.config.Timeout} resp, err := client.Do(req) if err != nil { return nil, errors.Join(ErrDatasource, fmt.Errorf("API 请求异常: %v", err)) } defer resp.Body.Close() // 检查 HTTP 状态码 if resp.StatusCode != http.StatusOK { return nil, errors.Join(ErrDatasource, fmt.Errorf("API 请求失败,状态码: %d", resp.StatusCode)) } // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Join(ErrSystem, fmt.Errorf("读取响应体失败: %v", err)) } // 解析 JSON 响应 var tianYanChaResp TianYanChaResponse if err := json.Unmarshal(body, &tianYanChaResp); err != nil { return nil, errors.Join(ErrSystem, fmt.Errorf("解析响应 JSON 失败: %v", err)) } // 检查天眼查业务状态码 if tianYanChaResp.ErrorCode != 0 { // 特殊处理:ErrorCode 300000 表示查询为空,返回ErrNotFound if tianYanChaResp.ErrorCode == 300000 { return nil, errors.Join(ErrNotFound, fmt.Errorf("天眼查查询为空: %s", tianYanChaResp.Reason)) } return &APIResponse{ Success: false, Code: tianYanChaResp.ErrorCode, Message: tianYanChaResp.Reason, Data: tianYanChaResp.Result, }, nil } // 成功情况 return &APIResponse{ Success: true, Code: 0, Message: tianYanChaResp.Reason, Data: tianYanChaResp.Result, }, nil }