Files
tyapi-server/internal/infrastructure/external/tianyancha/tianyancha_service.go
2025-09-30 12:03:51 +08:00

159 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}