diff --git a/.gitignore b/.gitignore index 832cc89..5cd2360 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ data/* /app/api /app/main/api/main /app/main/api/_* +__debug_bin.exe +**/.../ # 文档目录 diff --git a/app/main/api/desc/front/toolbox.api b/app/main/api/desc/front/toolbox.api new file mode 100644 index 0000000..a373d6d --- /dev/null +++ b/app/main/api/desc/front/toolbox.api @@ -0,0 +1,29 @@ +syntax = "v1" + +info ( + title: "工具箱服务" + desc: "免费工具箱(天行聚合等)" + version: "v1" +) + +//============================> toolbox v1 <============================ +@server ( + prefix: api/v1 + group: toolbox +) +service main { + @doc "工具箱统一查询" + @handler toolboxQuery + post /toolbox/query (ToolboxQueryReq) returns (ToolboxQueryResp) +} + +type ( + ToolboxQueryReq { + ToolKey string `json:"tool_key"` + Params map[string]interface{} `json:"params,optional"` + } + ToolboxQueryResp { + ToolKey string `json:"tool_key"` + Result map[string]interface{} `json:"result"` + } +) diff --git a/app/main/api/desc/front/upload.api b/app/main/api/desc/front/upload.api new file mode 100644 index 0000000..6271063 --- /dev/null +++ b/app/main/api/desc/front/upload.api @@ -0,0 +1,44 @@ +syntax = "v1" + +info ( + title: "上传服务" + desc: "图片 Base64 上传与文件访问" + version: "v1" +) + +//============================> upload v1 <============================ +// 访问已上传图片(直接输出二进制,无 JSON 包装) +@server ( + prefix: api/v1 + group: upload +) +service main { + @doc "访问已上传图片" + @handler serveUpload + get /upload/file/:name (ServeUploadFileReq) +} + +type ServeUploadFileReq { + Name string `path:"name"` +} + +@server ( + prefix: api/v1 + group: upload + jwt: JwtAuth + middleware: AuthInterceptor +) +service main { + @doc "图片 Base64 上传" + @handler uploadImage + post /upload/image (UploadImageReq) returns (UploadImageResp) +} + +type ( + UploadImageReq { + ImageBase64 string `json:"image_base64"` + } + UploadImageResp { + Url string `json:"url"` + } +) diff --git a/app/main/api/desc/main.api b/app/main/api/desc/main.api index 2773fb0..3d72214 100644 --- a/app/main/api/desc/main.api +++ b/app/main/api/desc/main.api @@ -14,6 +14,8 @@ import "./front/product.api" import "./front/agent.api" import "./front/app.api" import "./front/authorization.api" +import "./front/toolbox.api" +import "./front/upload.api" // 后台 import "./admin/auth.api" import "./admin/menu.api" @@ -29,3 +31,6 @@ import "./admin/admin_query.api" import "./admin/admin_agent.api" import "./admin/admin_api.api" import "./admin/admin_role_api.api" + + + diff --git a/app/main/api/etc/main.dev.yaml b/app/main/api/etc/main.dev.yaml index b338fba..144f12d 100644 --- a/app/main/api/etc/main.dev.yaml +++ b/app/main/api/etc/main.dev.yaml @@ -89,6 +89,10 @@ Tianyuanapi: Key: "04c6b4c559be6d5ba5351c04c8713a64" BaseURL: "https://api.tianyuanapi.com" Timeout: 60 +tianxingjuhe: + url: "https://apis.tianapi.com" + key: "4ceffb1ffb95b83230b9a9c9df2467e1" + timeout: 30 Authorization: FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL Promotion: diff --git a/app/main/api/etc/main.yaml b/app/main/api/etc/main.yaml index aea47de..f7620e1 100644 --- a/app/main/api/etc/main.yaml +++ b/app/main/api/etc/main.yaml @@ -78,6 +78,10 @@ Tianyuanapi: Key: "04c6b4c559be6d5ba5351c04c8713a64" BaseURL: "https://api.tianyuanapi.com" Timeout: 60 +tianxingjuhe: + url: "https://apis.tianapi.com" + key: "4ceffb1ffb95b83230b9a9c9df2467e1" + timeout: 30 Authorization: FileBaseURL: "https://www.quannengcha.com/api/v1/auth-docs" # 授权书文件访问基础URL Promotion: diff --git a/app/main/api/internal/config/config.go b/app/main/api/internal/config/config.go index c57704a..8b685aa 100644 --- a/app/main/api/internal/config/config.go +++ b/app/main/api/internal/config/config.go @@ -24,7 +24,8 @@ type Config struct { Query QueryConfig AdminConfig AdminConfig TaxConfig TaxConfig - Promotion PromotionConfig // 推广链接配置 + Promotion PromotionConfig // 推广链接配置 + Tianxingjuhe TianxingjuheConfig // 天行聚合API配置 } // JwtAuth 用于 JWT 鉴权配置 @@ -126,3 +127,10 @@ type PromotionConfig struct { PromotionDomain string // 推广域名(用于生成短链) OfficialDomain string // 正式站点域名(短链重定向的目标域名) } + +// TianxingjuheConfig 天行聚合API配置 +type TianxingjuheConfig struct { + URL string // API基础URL + Key string // API密钥 + Timeout int // 超时时间(秒),默认30秒 +} diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go index 4f309a7..b84b72f 100644 --- a/app/main/api/internal/handler/routes.go +++ b/app/main/api/internal/handler/routes.go @@ -27,6 +27,8 @@ import ( pay "qnc-server/app/main/api/internal/handler/pay" product "qnc-server/app/main/api/internal/handler/product" query "qnc-server/app/main/api/internal/handler/query" + toolbox "qnc-server/app/main/api/internal/handler/toolbox" + upload "qnc-server/app/main/api/internal/handler/upload" user "qnc-server/app/main/api/internal/handler/user" "qnc-server/app/main/api/internal/svc" @@ -1091,6 +1093,46 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { rest.WithPrefix("/api/v1"), ) + server.AddRoutes( + []rest.Route{ + { + // 工具箱统一查询 + Method: http.MethodPost, + Path: "/toolbox/query", + Handler: toolbox.ToolboxQueryHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + []rest.Route{ + { + // 访问已上传图片 + Method: http.MethodGet, + Path: "/upload/file/:name", + Handler: upload.ServeUploadHandler(serverCtx), + }, + }, + rest.WithPrefix("/api/v1"), + ) + + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.AuthInterceptor}, + []rest.Route{ + { + // 图片 Base64 上传 + Method: http.MethodPost, + Path: "/upload/image", + Handler: upload.UploadImageHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api/v1"), + ) + server.AddRoutes( []rest.Route{ { diff --git a/app/main/api/internal/handler/toolbox/toolboxqueryhandler.go b/app/main/api/internal/handler/toolbox/toolboxqueryhandler.go new file mode 100644 index 0000000..f14783c --- /dev/null +++ b/app/main/api/internal/handler/toolbox/toolboxqueryhandler.go @@ -0,0 +1,25 @@ +package toolbox + +import ( + "net/http" + + "qnc-server/app/main/api/internal/logic/toolbox" + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/result" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func ToolboxQueryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ToolboxQueryReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + l := toolbox.NewToolboxQueryLogic(r.Context(), svcCtx) + resp, err := l.ToolboxQuery(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/handler/upload/serveuploadhandler.go b/app/main/api/internal/handler/upload/serveuploadhandler.go new file mode 100644 index 0000000..a928f8f --- /dev/null +++ b/app/main/api/internal/handler/upload/serveuploadhandler.go @@ -0,0 +1,26 @@ +package upload + +import ( + "net/http" + + uploadlogic "qnc-server/app/main/api/internal/logic/upload" + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/result" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func ServeUploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ServeUploadFileReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + l := uploadlogic.NewServeUploadLogic(r.Context(), svcCtx) + if err := l.ServeUpload(req.Name, w); err != nil { + result.HttpResult(r, w, nil, err) + } + } +} diff --git a/app/main/api/internal/handler/upload/uploadimagehandler.go b/app/main/api/internal/handler/upload/uploadimagehandler.go new file mode 100644 index 0000000..6bbbd23 --- /dev/null +++ b/app/main/api/internal/handler/upload/uploadimagehandler.go @@ -0,0 +1,25 @@ +package upload + +import ( + "net/http" + + uploadlogic "qnc-server/app/main/api/internal/logic/upload" + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/result" + + "github.com/zeromicro/go-zero/rest/httpx" +) + +func UploadImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UploadImageReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + l := uploadlogic.NewUploadImageLogic(r.Context(), svcCtx) + resp, err := l.UploadImage(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/logic/toolbox/toolboxquerylogic.go b/app/main/api/internal/logic/toolbox/toolboxquerylogic.go new file mode 100644 index 0000000..b04b845 --- /dev/null +++ b/app/main/api/internal/logic/toolbox/toolboxquerylogic.go @@ -0,0 +1,47 @@ +package toolbox + +import ( + "context" + + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type ToolboxQueryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewToolboxQueryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToolboxQueryLogic { + return &ToolboxQueryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ToolboxQueryLogic) ToolboxQuery(req *types.ToolboxQueryReq) (*types.ToolboxQueryResp, error) { + if req.ToolKey == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("tool_key 不能为空"), "") + } + if l.svcCtx.ToolboxService == nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "工具箱服务未初始化") + } + params := req.Params + if params == nil { + params = map[string]interface{}{} + } + result, err := l.svcCtx.ToolboxService.Query(l.ctx, req.ToolKey, params) + if err != nil { + return nil, err + } + return &types.ToolboxQueryResp{ + ToolKey: req.ToolKey, + Result: result, + }, nil +} diff --git a/app/main/api/internal/logic/upload/serveuploadlogic.go b/app/main/api/internal/logic/upload/serveuploadlogic.go new file mode 100644 index 0000000..1f35448 --- /dev/null +++ b/app/main/api/internal/logic/upload/serveuploadlogic.go @@ -0,0 +1,67 @@ +package upload + +import ( + "context" + "net/http" + "os" + "path/filepath" + "strings" + + "qnc-server/app/main/api/internal/svc" + "qnc-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type ServeUploadLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewServeUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServeUploadLogic { + return &ServeUploadLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ServeUploadLogic) ServeUpload(name string, w http.ResponseWriter) error { + name = filepath.Base(strings.TrimSpace(name)) + if name == "" || name == "." || name == ".." || !isSafeUploadFileName(name) { + return errors.Wrapf(xerr.NewErrMsg("无效的文件名"), "") + } + fullPath := filepath.Join(uploadImageDir, name) + data, err := os.ReadFile(fullPath) + if err != nil { + if os.IsNotExist(err) { + return errors.Wrapf(xerr.NewErrMsg("文件不存在"), "") + } + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取文件失败: %v", err) + } + w.Header().Set("Cache-Control", "public, max-age=86400") + switch filepath.Ext(name) { + case ".png": + w.Header().Set("Content-Type", "image/png") + case ".gif": + w.Header().Set("Content-Type", "image/gif") + case ".webp": + w.Header().Set("Content-Type", "image/webp") + default: + w.Header().Set("Content-Type", "image/jpeg") + } + _, err = w.Write(data) + return err +} + +func isSafeUploadFileName(name string) bool { + for _, c := range name { + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' { + continue + } + return false + } + return true +} diff --git a/app/main/api/internal/logic/upload/uploadimagelogic.go b/app/main/api/internal/logic/upload/uploadimagelogic.go new file mode 100644 index 0000000..0059f46 --- /dev/null +++ b/app/main/api/internal/logic/upload/uploadimagelogic.go @@ -0,0 +1,93 @@ +package upload + +import ( + "context" + "encoding/base64" + "fmt" + "os" + "path/filepath" + "strings" + + "qnc-server/app/main/api/internal/svc" + "qnc-server/app/main/api/internal/types" + "qnc-server/common/xerr" + + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +const uploadImageDir = "uploads/images" + +type UploadImageLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic { + return &UploadImageLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (*types.UploadImageResp, error) { + raw := strings.TrimSpace(req.ImageBase64) + if raw == "" { + return nil, errors.Wrapf(xerr.NewErrMsg("image_base64 不能为空"), "") + } + if idx := strings.Index(raw, ","); idx >= 0 { + raw = raw[idx+1:] + } + data, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("图片 Base64 解析失败"), "") + } + if len(data) == 0 { + return nil, errors.Wrapf(xerr.NewErrMsg("图片内容为空"), "") + } + if len(data) > 5*1024*1024 { + return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 5MB"), "") + } + + ext := detectImageExt(data) + fileName := fmt.Sprintf("%s%s", uuid.NewString(), ext) + dir := uploadImageDir + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err) + } + fullPath := filepath.Join(dir, fileName) + if err := os.WriteFile(fullPath, data, 0o644); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err) + } + + base := publicAPIBase(l.svcCtx.Config.Promotion.OfficialDomain) + url := fmt.Sprintf("%s/api/v1/upload/file/%s", base, fileName) + return &types.UploadImageResp{Url: url}, nil +} + +func detectImageExt(data []byte) string { + if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF { + return ".jpg" + } + if len(data) >= 8 && string(data[0:8]) == "\x89PNG\r\n\x1a\n" { + return ".png" + } + if len(data) >= 6 && string(data[0:6]) == "GIF87a" || string(data[0:6]) == "GIF89a" { + return ".gif" + } + if len(data) >= 12 && string(data[8:12]) == "WEBP" { + return ".webp" + } + return ".jpg" +} + +func publicAPIBase(officialDomain string) string { + base := strings.TrimRight(officialDomain, "/") + if base != "" { + return base + } + return "http://127.0.0.1:8888" +} diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index 38781c2..5f1f508 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -159,7 +159,7 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq. encryptData = encryptedEmptyData } else { // 正常模式:调用API请求服务 - combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id) + combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id, order.OrderNo) if err != nil { return l.handleError(ctx, err, order, query) } diff --git a/app/main/api/internal/service/apirequestService.go b/app/main/api/internal/service/apirequestService.go index 5cad92c..f497fbe 100644 --- a/app/main/api/internal/service/apirequestService.go +++ b/app/main/api/internal/service/apirequestService.go @@ -2,12 +2,14 @@ package service import ( "context" + "encoding/hex" "encoding/json" "errors" "fmt" "qnc-server/app/main/api/internal/config" tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk" "qnc-server/app/main/model" + "qnc-server/pkg/lzkit/crypto" "sort" "strings" "sync" @@ -75,7 +77,8 @@ type APIResponseData struct { } // ProcessRequests 处理请求 -func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([]byte, error) { +// orderNo: 当前查询订单号,供异步车辆类接口生成 return_url 回调地址 +func (a *ApiRequestService) ProcessRequests(params []byte, productID string, orderNo string) ([]byte, error) { var ctx, cancel = context.WithCancel(context.Background()) defer cancel() @@ -112,6 +115,25 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([] if len(featureList) == 0 { return nil, errors.New("处理请求错误,产品无对应接口功能") } + + // 在原始 params 上附加 order_no,供异步车辆类接口自动生成回调地址 + var baseParams map[string]interface{} + if err := json.Unmarshal(params, &baseParams); err != nil { + logx.Errorf("解析查询参数失败, Params: %s, Error: %v", string(params), err) + return nil, fmt.Errorf("解析查询参数失败: %w", err) + } + if mobile, exists := baseParams["mobile"]; exists { + baseParams["mobile_no"] = mobile + } + if orderNo != "" { + baseParams["order_no"] = orderNo + } + paramsWithOrder, err := json.Marshal(baseParams) + if err != nil { + logx.Errorf("序列化查询参数失败, Params: %s, Error: %v", string(params), err) + return nil, fmt.Errorf("序列化查询参数失败: %w", err) + } + var ( wg sync.WaitGroup resultsCh = make(chan APIResponseData, len(featureList)) @@ -152,7 +174,7 @@ func (a *ApiRequestService) ProcessRequests(params []byte, productID string) ([] tryCount := 0 for { tryCount++ - resp, preprocessErr = a.PreprocessRequestApi(params, feature.ApiId) + resp, preprocessErr = a.PreprocessRequestApi(paramsWithOrder, feature.ApiId) if preprocessErr == nil { break } @@ -224,7 +246,21 @@ var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, err "QYGL6F2D": (*ApiRequestService).ProcessQYGL6F2DRequest, "JRZQ8203": (*ApiRequestService).ProcessJRZQ8203Request, "JRZQ4AA8": (*ApiRequestService).ProcessJRZQ4AA8Request, + "QCXGGB2Q": (*ApiRequestService).ProcessQCXGGB2QRequest, + "QCXGYTS2": (*ApiRequestService).ProcessQCXGYTS2Request, + "QCXG5F3A": (*ApiRequestService).ProcessQCXG5F3ARequest, "QCXG7A2B": (*ApiRequestService).ProcessQCXG7A2BRequest, + "QCXG9P1C": (*ApiRequestService).ProcessQCXG9P1CFRequest, + "QCXG4D2E": (*ApiRequestService).ProcessQCXG4D2ERequest, + "QCXG5U0Z": (*ApiRequestService).ProcessQCXG5U0ZRequest, + "QCXG1U4U": (*ApiRequestService).ProcessQCXG1U4URequest, + "QCXGY7F2": (*ApiRequestService).ProcessQCXGY7F2Request, + "QCXG1H7Y": (*ApiRequestService).ProcessQCXG1H7YRequest, + "QCXG4I1Z": (*ApiRequestService).ProcessQCXG4I1ZRequest, + "QCXG3Y6B": (*ApiRequestService).ProcessQCXG3Y6BRequest, + "QCXG3Z3L": (*ApiRequestService).ProcessQCXG3Z3LRequest, + "QCXGP00W": (*ApiRequestService).ProcessQCXGP00WRequest, + "QCXG6B4E": (*ApiRequestService).ProcessQCXG6B4ERequest, "DWBG8B4D": (*ApiRequestService).ProcessDWBG8B4DRequest, "DWBG6A2C": (*ApiRequestService).ProcessDWBG6A2CRequest, "JRZQ4B6C": (*ApiRequestService).ProcessJRZQ4B6CRequest, @@ -1126,24 +1162,290 @@ func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error return nil, fmt.Errorf("响应code错误%s", code.String()) } +// ProcessQCXGGB2QRequest 人车核验简版 +func (a *ApiRequestService) ProcessQCXGGB2QRequest(params []byte) ([]byte, error) { + plateNo := gjson.GetBytes(params, "plate_no") + carplateType := gjson.GetBytes(params, "carplate_type") + name := gjson.GetBytes(params, "name") + if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() { + return nil, errors.New("api请求, QCXGGB2Q, 获取相关参数失败") + } + resp, err := a.tianyuanapi.CallInterface("QCXGGB2Q", map[string]interface{}{ + "plate_no": plateNo.String(), + "carplate_type": carplateType.String(), + "name": name.String(), + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// ProcessQCXGYTS2Request 人车核验详版 +func (a *ApiRequestService) ProcessQCXGYTS2Request(params []byte) ([]byte, error) { + plateNo := gjson.GetBytes(params, "plate_no") + carplateType := gjson.GetBytes(params, "carplate_type") + name := gjson.GetBytes(params, "name") + if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() { + return nil, errors.New("api请求, QCXGYTS2, 获取相关参数失败") + } + resp, err := a.tianyuanapi.CallInterface("QCXGYTS2", map[string]interface{}{ + "plate_no": plateNo.String(), + "carplate_type": carplateType.String(), + "name": name.String(), + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// ProcessQCXG5F3ARequest 名下车辆(车牌) +func (a *ApiRequestService) ProcessQCXG5F3ARequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, QCXG5F3A, 获取相关参数失败") + } + resp, err := a.tianyuanapi.CallInterface("QCXG5F3A", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + // ProcessQCXG7A2BRequest 名下车辆 func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) { idCard := gjson.GetBytes(params, "id_card") if !idCard.Exists() { return nil, errors.New("api请求, QCXG7A2B, 获取相关参数失败") } - resp, err := a.tianyuanapi.CallInterface("QCXG7A2B", map[string]interface{}{ "id_card": idCard.String(), }) - if err != nil { return nil, err } - return convertTianyuanResponse(resp) } +// ProcessQCXG9P1CFRequest 名下车辆车牌查询 A +func (a *ApiRequestService) ProcessQCXG9P1CFRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + mobile := gjson.GetBytes(params, "mobile") + if !idCard.Exists() || !name.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, QCXG9P1C, 获取相关参数失败") + } + resp, err := a.tianyuanapi.CallInterface("QCXG9P1C", map[string]interface{}{ + "id_card": idCard.String(), + "authorized": "1", + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG4D2ERequest(params []byte) ([]byte, error) { + m := map[string]interface{}{} + if err := json.Unmarshal(params, &m); err != nil { + return nil, fmt.Errorf("api请求, QCXG4D2E, 解析参数失败: %w", err) + } + body := map[string]interface{}{} + if v, ok := m["user_type"].(string); ok && v != "" { + body["user_type"] = v + } else { + body["user_type"] = "1" + } + if v, ok := m["id_card"].(string); ok { + body["id_card"] = v + } else { + return nil, errors.New("api请求, QCXG4D2E, 缺少 id_card") + } + resp, err := a.tianyuanapi.CallInterface("QCXG4D2E", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG5U0ZRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + if !vin.Exists() || vin.String() == "" { + return nil, errors.New("api请求, QCXG5U0Z, 缺少 vin_code") + } + resp, err := a.tianyuanapi.CallInterface("QCXG5U0Z", map[string]interface{}{"vin_code": vin.String()}) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG1U4URequest(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code", "image_url"}, nil) + orderNo := gjson.GetBytes(params, "order_no").String() + if body["vin_code"] == nil || body["image_url"] == nil || orderNo == "" { + return nil, errors.New("api请求, QCXG1U4U, 缺少必填参数 vin_code/image_url/order_no") + } + logx.Infof("vehicle api request QCXG1U4U, order_no=%s, vin_code=%v, image_url=%v", orderNo, body["vin_code"], body["image_url"]) + body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG1U4U") + resp, err := a.tianyuanapi.CallInterface("QCXG1U4U", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXGY7F2Request(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code", "vehicle_location", "first_registrationdate"}, nil) + if body["vin_code"] == nil || body["vehicle_location"] == nil || body["first_registrationdate"] == nil { + return nil, errors.New("api请求, QCXGY7F2, 缺少必填参数 vin_code/vehicle_location/first_registrationdate") + } + logx.Infof("vehicle api request QCXGY7F2, vin_code=%v, vehicle_location=%v, first_registrationdate=%v", body["vin_code"], body["vehicle_location"], body["first_registrationdate"]) + resp, err := a.tianyuanapi.CallInterface("QCXGY7F2", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG1H7YRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + plate := gjson.GetBytes(params, "car_license") + if !vin.Exists() || vin.String() == "" || !plate.Exists() || plate.String() == "" { + return nil, errors.New("api请求, QCXG1H7Y, 缺少 vin_code 或 car_license") + } + body := map[string]interface{}{ + "vin_code": vin.String(), + "plate_no": plate.String(), + } + logx.Infof("vehicle api request QCXG1H7Y, vin_code=%s, plate_no=%s", vin.String(), plate.String()) + resp, err := a.tianyuanapi.CallInterface("QCXG1H7Y", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG4I1ZRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + if !vin.Exists() || vin.String() == "" { + return nil, errors.New("api请求, QCXG4I1Z, 缺少 vin_code") + } + resp, err := a.tianyuanapi.CallInterface("QCXG4I1Z", map[string]interface{}{"vin_code": vin.String()}) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code"}, nil) + orderNo := gjson.GetBytes(params, "order_no").String() + if body["vin_code"] == nil || orderNo == "" { + return nil, errors.New("api请求, QCXG3Y6B, 缺少必填参数 vin_code/order_no") + } + logx.Infof("vehicle api request QCXG3Y6B, order_no=%s, vin_code=%v", orderNo, body["vin_code"]) + body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Y6B") + resp, err := a.tianyuanapi.CallInterface("QCXG3Y6B", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code"}, nil) + orderNo := gjson.GetBytes(params, "order_no").String() + if body["vin_code"] == nil || orderNo == "" { + return nil, errors.New("api请求, QCXG3Z3L, 缺少必填参数 vin_code/order_no") + } + logx.Infof("vehicle api request QCXG3Z3L, order_no=%s, vin_code=%v", orderNo, body["vin_code"]) + body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Z3L") + resp, err := a.tianyuanapi.CallInterface("QCXG3Z3L", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + orderNo := gjson.GetBytes(params, "order_no").String() + vlphoto := gjson.GetBytes(params, "vlphoto_data") + if !vin.Exists() || vin.String() == "" || orderNo == "" || !vlphoto.Exists() || vlphoto.String() == "" { + return nil, errors.New("api请求, QCXGP00W, 缺少必填参数 vin_code/order_no/vlphoto_data") + } + logx.Infof("vehicle api request QCXGP00W, order_no=%s, vin_code=%s, vlphoto_data_len=%d", orderNo, vin.String(), len(vlphoto.String())) + key, err := hex.DecodeString(a.config.Encrypt.SecretKey) + if err != nil { + return nil, fmt.Errorf("api请求, QCXGP00W, 密钥解析失败: %w", err) + } + encData, err := crypto.AesEncrypt([]byte(vlphoto.String()), key) + if err != nil { + return nil, fmt.Errorf("api请求, QCXGP00W, 加密行驶证数据失败: %w", err) + } + resp, err := a.tianyuanapi.CallInterface("QCXGP00W", map[string]interface{}{ + "vin_code": vin.String(), + "return_url": a.buildVehicleCallbackURL(orderNo, "QCXGP00W"), + "data": encData, + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + if !vin.Exists() || vin.String() == "" { + return nil, errors.New("api请求, QCXG6B4E, 缺少 vin_code") + } + auth := gjson.GetBytes(params, "authorized").String() + if auth == "" { + auth = "1" + } + resp, err := a.tianyuanapi.CallInterface("QCXG6B4E", map[string]interface{}{ + "vin_code": vin.String(), + "authorized": auth, + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// buildVehicleBody 从 params 中取 required 与 optional 键,仅非空才写入 body +func buildVehicleBody(params []byte, required, optional []string) map[string]interface{} { + body := make(map[string]interface{}) + for _, k := range required { + v := gjson.GetBytes(params, k) + if v.Exists() { + body[k] = v.String() + } + } + for _, k := range optional { + v := gjson.GetBytes(params, k) + if v.Exists() && v.String() != "" { + body[k] = v.String() + } + } + return body +} + +// buildVehicleCallbackURL 生成车辆类接口的异步回调地址 +func (a *ApiRequestService) buildVehicleCallbackURL(orderNo, apiID string) string { + base := strings.TrimRight(a.config.Promotion.OfficialDomain, "/") + if base == "" { + return fmt.Sprintf("/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", orderNo, apiID) + } + return fmt.Sprintf("%s/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", base, orderNo, apiID) +} + // ProcessYYSY09CDRequest 三要素 func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error) { name := gjson.GetBytes(params, "name") diff --git a/app/main/api/internal/service/apirequestService2.md b/app/main/api/internal/service/apirequestService2.md new file mode 100644 index 0000000..50df6b3 --- /dev/null +++ b/app/main/api/internal/service/apirequestService2.md @@ -0,0 +1,1984 @@ +package service + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + "tyc-server/app/main/api/internal/config" + tianyuanapi "tyc-server/app/main/api/internal/service/tianyuanapi_sdk" + "tyc-server/app/main/model" + "tyc-server/pkg/lzkit/crypto" + + "github.com/Masterminds/squirrel" + "github.com/tidwall/gjson" + "github.com/zeromicro/go-zero/core/logx" +) + +// 辅助函数:将天远API响应转换为JSON字节数组 +func convertTianyuanResponse(resp *tianyuanapi.Response) ([]byte, error) { + return json.Marshal(resp.Data) +} + +// 生成认证时间范围:当前时间前后两天的YYYYMMDD-YYMMDD格式 +func generateAuthDateRange() string { + now := time.Now() + start := now.AddDate(0, 0, -2).Format("20060102") + end := now.AddDate(0, 0, 2).Format("20060102") + return fmt.Sprintf("%s-%s", start, end) +} + +type ApiRequestService struct { + config config.Config + featureModel model.FeatureModel + productFeatureModel model.ProductFeatureModel + tianyuanapi *tianyuanapi.Client + authService *AuthorizationService +} + +// NewApiRequestService 是一个构造函数,用于初始化 ApiRequestService +func NewApiRequestService(c config.Config, featureModel model.FeatureModel, productFeatureModel model.ProductFeatureModel, tianyuanapi *tianyuanapi.Client, authService *AuthorizationService) *ApiRequestService { + return &ApiRequestService{ + config: c, + featureModel: featureModel, + productFeatureModel: productFeatureModel, + tianyuanapi: tianyuanapi, + authService: authService, + } +} + +// keysOfMap 返回 map 的 key 列表,便于 debug 日志展示参数结构而不是完整明文 +func keysOfMap(m map[string]interface{}) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +type APIResponseData struct { + ApiID string `json:"apiID"` + Data json.RawMessage `json:"data"` // 这里用 RawMessage 来存储原始的 data + Success bool `json:"success"` + Timestamp string `json:"timestamp"` + Error string `json:"error,omitempty"` +} + +// ProcessRequests 处理请求 +// orderNo: 当前查询对应的订单号,用于为异步车辆类接口生成回调地址(return_url) +func (a *ApiRequestService) ProcessRequests(params []byte, productID int64, orderNo string) ([]APIResponseData, error) { + var ctx, cancel = context.WithCancel(context.Background()) + defer cancel() + build := a.productFeatureModel.SelectBuilder().Where(squirrel.Eq{ + "product_id": productID, + }) + productFeatureList, findProductFeatureErr := a.productFeatureModel.FindAll(ctx, build, "") + if findProductFeatureErr != nil { + return nil, findProductFeatureErr + } + var featureIDs []int64 + isImportantMap := make(map[int64]int64, len(productFeatureList)) + for _, pf := range productFeatureList { + featureIDs = append(featureIDs, pf.FeatureId) + isImportantMap[pf.FeatureId] = pf.IsImportant + } + if len(featureIDs) == 0 { + return nil, errors.New("featureIDs 是空的") + } + builder := a.featureModel.SelectBuilder().Where(squirrel.Eq{"id": featureIDs}) + featureList, findFeatureErr := a.featureModel.FindAll(ctx, builder, "") + if findFeatureErr != nil { + return nil, findFeatureErr + } + if len(featureList) == 0 { + return nil, errors.New("处理请求错误,产品无对应接口功能") + } + + // 在原始 params 上附加 order_no,供异步车辆类接口自动生成回调地址使用 + var baseParams map[string]interface{} + if err := json.Unmarshal(params, &baseParams); err != nil { + logx.Errorf("解析查询参数失败, Params: %s, Error: %v", string(params), err) + return nil, fmt.Errorf("解析查询参数失败: %w", err) + } + + // 参数转换:将 mobile 转换为 mobile_no + if mobile, exists := baseParams["mobile"]; exists { + baseParams["mobile_no"] = mobile + } + + if orderNo != "" { + baseParams["order_no"] = orderNo + } + paramsWithOrder, err := json.Marshal(baseParams) + if err != nil { + logx.Errorf("序列化查询参数失败, Params: %s, Error: %v", string(params), err) + return nil, fmt.Errorf("序列化查询参数失败: %w", err) + } + var ( + wg sync.WaitGroup + resultsCh = make(chan APIResponseData, len(featureList)) + errorsCh = make(chan error, len(featureList)) + errorCount int32 + errorLimit = len(featureList) + retryNum = 5 + ) + + for i, feature := range featureList { + wg.Add(1) + go func(i int, feature *model.Feature) { + defer wg.Done() + + select { + case <-ctx.Done(): + return + default: + } + result := APIResponseData{ + ApiID: feature.ApiId, + Success: false, + } + timestamp := time.Now().Format("2006-01-02 15:04:05") + var ( + resp json.RawMessage + preprocessErr error + ) + // 若 isImportantMap[feature.ID] == 1,则表示需要在出错时重试 + isImportant := isImportantMap[feature.Id] == 1 + tryCount := 0 + for { + tryCount++ + resp, preprocessErr = a.PreprocessRequestApi(paramsWithOrder, feature.ApiId) + if preprocessErr == nil { + break + } + if isImportant && tryCount < retryNum { + continue + } else { + break + } + } + if preprocessErr != nil { + // 在这里添加日志记录入参 + logx.Errorf("API请求处理器失败, ApiID: %s, Params: %s, Error: %v", feature.ApiId, string(paramsWithOrder), preprocessErr) + + result.Timestamp = timestamp + result.Error = preprocessErr.Error() + result.Data = resp + resultsCh <- result + errorsCh <- fmt.Errorf("请求失败: %v", preprocessErr) + atomic.AddInt32(&errorCount, 1) + if atomic.LoadInt32(&errorCount) >= int32(errorLimit) { + cancel() + } + return + } + + result.Data = resp + result.Success = true + result.Timestamp = timestamp + resultsCh <- result + }(i, feature) + } + + go func() { + wg.Wait() + close(resultsCh) + close(errorsCh) + }() + // 收集所有结果并合并z + var responseData []APIResponseData + for result := range resultsCh { + responseData = append(responseData, result) + } + if atomic.LoadInt32(&errorCount) >= int32(errorLimit) { + var allErrors []error + for err := range errorsCh { + allErrors = append(allErrors, err) + } + return nil, fmt.Errorf("请求失败次数超过 %d 次: %v", errorLimit, allErrors) + } + + return responseData, nil +} + +// ------------------------------------请求处理器-------------------------- +var requestProcessors = map[string]func(*ApiRequestService, []byte) ([]byte, error){ + "PersonEnterprisePro": (*ApiRequestService).ProcessPersonEnterpriseProRequest, + "BehaviorRiskScan": (*ApiRequestService).ProcessBehaviorRiskScanRequest, + "YYSYBE08": (*ApiRequestService).ProcessYYSYBE08Request, + "YYSY09CD": (*ApiRequestService).ProcessYYSY09CDRequest, + "QCXGGB2Q": (*ApiRequestService).ProcessQCXGGB2QRequest, + "QCXGYTS2": (*ApiRequestService).ProcessQCXGYTS2Request, + "QCXG5F3A": (*ApiRequestService).ProcessQCXG5F3ARequest, //内部替换名下 + "FLXG0687": (*ApiRequestService).ProcessFLXG0687Request, + "FLXG3D56": (*ApiRequestService).ProcessFLXG3D56Request, + "FLXG0V4B": (*ApiRequestService).ProcessFLXG0V4BRequest, + "QYGL8271": (*ApiRequestService).ProcessQYGL8271Request, + "IVYZ5733": (*ApiRequestService).ProcessIVYZ5733Request, + "IVYZ9A2B": (*ApiRequestService).ProcessIVYZ9A2BRequest, + "JRZQ0A03": (*ApiRequestService).ProcessJRZQ0A03Request, + "QYGL6F2D": (*ApiRequestService).ProcessQYGL6F2DRequest, + "JRZQ8203": (*ApiRequestService).ProcessJRZQ8203Request, + "JRZQ4AA8": (*ApiRequestService).ProcessJRZQ4AA8Request, + "QCXG7A2B": (*ApiRequestService).ProcessQCXG7A2BRequest, + "DWBG8B4D": (*ApiRequestService).ProcessDWBG8B4DRequest, + "DWBG6A2C": (*ApiRequestService).ProcessDWBG6A2CRequest, + "JRZQ4B6C": (*ApiRequestService).ProcessJRZQ4B6CRequest, + "JRZQ09J8": (*ApiRequestService).ProcessJRZQ09J8Request, + "JRZQ5E9F": (*ApiRequestService).ProcessJRZQ5E9FRequest, + "QYGL3F8E": (*ApiRequestService).ProcessQYGL3F8ERequest, + "IVYZ81NC": (*ApiRequestService).ProcessIVYZ81NCRequest, + "IVYZ7F3A": (*ApiRequestService).ProcessIVYZ7F3ARequest, + "IVYZ3P9M": (*ApiRequestService).ProcessIVYZ3P9MRequest, + "FLXG7E8F": (*ApiRequestService).ProcessFLXG7E8FRequest, + "QCXG9P1C": (*ApiRequestService).ProcessQCXG9P1CFRequest, + // 车辆类接口(传参后续配置,先透传缓存 params) + "QCXG4D2E": (*ApiRequestService).ProcessQCXG4D2ERequest, + "QCXG5U0Z": (*ApiRequestService).ProcessQCXG5U0ZRequest, + "QCXG1U4U": (*ApiRequestService).ProcessQCXG1U4URequest, + "QCXGY7F2": (*ApiRequestService).ProcessQCXGY7F2Request, + "QCXG1H7Y": (*ApiRequestService).ProcessQCXG1H7YRequest, + "QCXG4I1Z": (*ApiRequestService).ProcessQCXG4I1ZRequest, + "QCXG3Y6B": (*ApiRequestService).ProcessQCXG3Y6BRequest, + "QCXG3Z3L": (*ApiRequestService).ProcessQCXG3Z3LRequest, + "QCXGP00W": (*ApiRequestService).ProcessQCXGP00WRequest, + "QCXG6B4E": (*ApiRequestService).ProcessQCXG6B4ERequest, + // 核验工具(verify feature.md) + "IVYZ9K7F": (*ApiRequestService).ProcessIVYZ9K7FRequest, + "IVYZA1B3": (*ApiRequestService).ProcessIVYZA1B3Request, + "IVYZ6M8P": (*ApiRequestService).ProcessIVYZ6M8PRequest, + "JRZQ8B3C": (*ApiRequestService).ProcessJRZQ8B3CRequest, + "YYSY3M8S": (*ApiRequestService).ProcessYYSY3M8SRequest, + "YYSYK9R4": (*ApiRequestService).ProcessYYSYK9R4Request, + "YYSYF2T7": (*ApiRequestService).ProcessYYSYF2T7Request, + "YYSYK8R3": (*ApiRequestService).ProcessYYSYK8R3Request, + "YYSYS9W1": (*ApiRequestService).ProcessYYSYS9W1Request, + "YYSYE7V5": (*ApiRequestService).ProcessYYSYE7V5Request, + "YYSYP0T4": (*ApiRequestService).ProcessYYSYP0T4Request, + "YYSY6F2B": (*ApiRequestService).ProcessYYSY6F2BRequest, + "YYSY9E4A": (*ApiRequestService).ProcessYYSY9E4ARequest, + "QYGL5F6A": (*ApiRequestService).ProcessQYGL5F6ARequest, + "JRZQACAB": (*ApiRequestService).ProcessJRZQACABRequest, + "JRZQ0B6Y": (*ApiRequestService).ProcessJRZQ0B6YRequest, + // 新增司法涉诉类接口 + "QYGL66SL": (*ApiRequestService).ProcessQYGL66SLRequest, + "FLXG3A9B": (*ApiRequestService).ProcessFLXG3A9BRequest, + "QYGL2S0W": (*ApiRequestService).ProcessQYGL2S0WRequest, + "FLXGDEA9": (*ApiRequestService).ProcessFLXGDEA9Request, + "IVYZ4Y27": (*ApiRequestService).ProcessIVYZ4Y27Request, + "IVYZ0S0D": (*ApiRequestService).ProcessIVYZ0S0DRequest, +} + +// PreprocessRequestApi 调用指定的请求处理函数 +func (a *ApiRequestService) PreprocessRequestApi(params []byte, apiID string) ([]byte, error) { + if processor, exists := requestProcessors[apiID]; exists { + return processor(a, params) // 调用 ApiRequestService 方法 + } + + return nil, errors.New("api请求, 未找到相应的处理程序") +} + +// PersonEnterprisePro 人企业关系加强版 +func (a *ApiRequestService) ProcessPersonEnterpriseProRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + // 设置最大调用次数上限 + maxApiCalls := 20 // 允许最多查询20个企业 + + if !idCard.Exists() { + return nil, errors.New("api请求, PersonEnterprisePro, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGLB4C0", map[string]interface{}{ + "id_card": idCard.String(), + }) + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 处理股东人企关系的响应数据 + code := gjson.GetBytes(respBytes, "code") + if !code.Exists() { + return nil, fmt.Errorf("响应中缺少 code 字段") + } + + // 判断 code 是否等于 "0000" + if code.String() == "0000" { + // 获取 data 字段的值 + data := gjson.GetBytes(respBytes, "data") + if !data.Exists() { + return nil, fmt.Errorf("响应中缺少 data 字段") + } + + // 使用gjson获取企业列表 + datalistResult := gjson.Get(data.Raw, "datalist") + if !datalistResult.Exists() { + return nil, fmt.Errorf("datalist字段不存在") + } + + // 获取所有企业并进行排序 + companies := datalistResult.Array() + + // 创建企业对象切片,用于排序 + type CompanyWithPriority struct { + Index int + Data gjson.Result + RelationshipVal int // 关系权重值 + RelationCount int // 关系数量 + AdminPenalty int // 行政处罚数量 + Executed int // 被执行人数量 + Dishonest int // 失信被执行人数量 + } + + companiesWithPriority := make([]CompanyWithPriority, 0, len(companies)) + + // 遍历企业,计算优先级 + for i, companyJson := range companies { + // 统计行政处罚、被执行人、失信被执行人 + adminPenalty := 0 + executed := 0 + dishonest := 0 + + // 检查行政处罚字段是否存在并获取数组长度 + adminPenaltyResult := companyJson.Get("adminPenalty") + if adminPenaltyResult.Exists() && adminPenaltyResult.IsArray() { + adminPenalty = len(adminPenaltyResult.Array()) + } + + // 检查被执行人字段是否存在并获取数组长度 + executedPersonResult := companyJson.Get("executedPerson") + if executedPersonResult.Exists() && executedPersonResult.IsArray() { + executed = len(executedPersonResult.Array()) + } + + // 检查失信被执行人字段是否存在并获取数组长度 + dishonestExecutedPersonResult := companyJson.Get("dishonestExecutedPerson") + if dishonestExecutedPersonResult.Exists() && dishonestExecutedPersonResult.IsArray() { + dishonest = len(dishonestExecutedPersonResult.Array()) + } + + // 计算relationship权重 + relationshipVal := 0 + relationCount := 0 + + // 获取relationship数组 + relationshipResult := companyJson.Get("relationship") + if relationshipResult.Exists() && relationshipResult.IsArray() { + relationships := relationshipResult.Array() + // 统计各类关系的数量和权重 + for _, rel := range relationships { + relationCount++ + relStr := rel.String() + + // 根据关系类型设置权重,权重顺序: + // 股东(6) > 历史股东(5) > 法人(4) > 历史法人(3) > 高管(2) > 历史高管(1) + switch relStr { + case "sh": // 股东 + if relationshipVal < 6 { + relationshipVal = 6 + } + case "his_sh": // 历史股东 + if relationshipVal < 5 { + relationshipVal = 5 + } + case "lp": // 法人 + if relationshipVal < 4 { + relationshipVal = 4 + } + case "his_lp": // 历史法人 + if relationshipVal < 3 { + relationshipVal = 3 + } + case "tm": // 高管 + if relationshipVal < 2 { + relationshipVal = 2 + } + case "his_tm": // 历史高管 + if relationshipVal < 1 { + relationshipVal = 1 + } + } + } + } + + companiesWithPriority = append(companiesWithPriority, CompanyWithPriority{ + Index: i, + Data: companyJson, + RelationshipVal: relationshipVal, + RelationCount: relationCount, + AdminPenalty: adminPenalty, + Executed: executed, + Dishonest: dishonest, + }) + } + + // 按优先级排序 + sort.Slice(companiesWithPriority, func(i, j int) bool { + // 首先根据是否有失信被执行人排序 + if companiesWithPriority[i].Dishonest != companiesWithPriority[j].Dishonest { + return companiesWithPriority[i].Dishonest > companiesWithPriority[j].Dishonest + } + + // 然后根据是否有被执行人排序 + if companiesWithPriority[i].Executed != companiesWithPriority[j].Executed { + return companiesWithPriority[i].Executed > companiesWithPriority[j].Executed + } + + // 然后根据是否有行政处罚排序 + if companiesWithPriority[i].AdminPenalty != companiesWithPriority[j].AdminPenalty { + return companiesWithPriority[i].AdminPenalty > companiesWithPriority[j].AdminPenalty + } + + // 然后按relationship类型排序 + if companiesWithPriority[i].RelationshipVal != companiesWithPriority[j].RelationshipVal { + return companiesWithPriority[i].RelationshipVal > companiesWithPriority[j].RelationshipVal + } + + // 最后按relationship数量排序 + return companiesWithPriority[i].RelationCount > companiesWithPriority[j].RelationCount + }) + + // 限制处理的企业数量 + processCount := len(companiesWithPriority) + if processCount > maxApiCalls { + processCount = maxApiCalls + } + + // 只处理前N个优先级高的企业 + prioritizedCompanies := companiesWithPriority[:processCount] + + // 使用WaitGroup和chan处理并发 + var wg sync.WaitGroup + results := make(chan struct { + index int + data []byte + err error + }, processCount) + + // 对按优先级排序的前N个企业进行涉诉信息查询 + for _, company := range prioritizedCompanies { + wg.Add(1) + go func(origIndex int, companyInfo gjson.Result) { + defer wg.Done() + logx.Infof("开始处理企业[%d],企业名称: %s,统一社会信用代码: %s", origIndex, companyInfo.Get("basicInfo.name").String(), companyInfo.Get("basicInfo.creditCode").String()) + // 提取企业名称和统一社会信用代码 + orgName := companyInfo.Get("basicInfo.name") + creditCode := companyInfo.Get("basicInfo.creditCode") + + if !orgName.Exists() || !creditCode.Exists() { + results <- struct { + index int + data []byte + err error + }{origIndex, nil, fmt.Errorf("企业名称或统一社会信用代码不存在")} + return + } + + // 解析原始公司信息为map + var companyMap map[string]interface{} + if err := json.Unmarshal([]byte(companyInfo.Raw), &companyMap); err != nil { + results <- struct { + index int + data []byte + err error + }{origIndex, nil, fmt.Errorf("解析企业信息失败: %v", err)} + return + } + + // 调用QYGL8271接口获取企业涉诉信息 + lawsuitResp, err := a.tianyuanapi.CallInterface("QYGL8271", map[string]interface{}{ + "ent_name": orgName.String(), + "ent_code": creditCode.String(), + "auth_date": generateAuthDateRange(), + }) + // 无论是否有错误,都继续处理 + if err != nil { + // 可能是正常没有涉诉数据,设置为空对象 + logx.Infof("企业[%s]涉诉信息查询结果: %v", orgName.String(), err) + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else { + // 转换响应数据 + lawsuitRespBytes, err := convertTianyuanResponse(lawsuitResp) + if err != nil { + logx.Errorf("转换企业[%s]涉诉响应失败: %v", orgName.String(), err) + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else if len(lawsuitRespBytes) == 0 || string(lawsuitRespBytes) == "{}" || string(lawsuitRespBytes) == "null" { + // 无涉诉数据 + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else { + // 解析涉诉信息 + var lawsuitInfo interface{} + if err := json.Unmarshal(lawsuitRespBytes, &lawsuitInfo); err != nil { + logx.Errorf("解析企业[%s]涉诉信息失败: %v", orgName.String(), err) + companyMap["lawsuitInfo"] = map[string]interface{}{} + } else { + // 添加涉诉信息到企业信息中 + companyMap["lawsuitInfo"] = lawsuitInfo + } + } + } + + // 序列化更新后的企业信息 + companyData, err := json.Marshal(companyMap) + if err != nil { + results <- struct { + index int + data []byte + err error + }{origIndex, nil, fmt.Errorf("序列化企业信息失败: %v", err)} + return + } + + results <- struct { + index int + data []byte + err error + }{origIndex, companyData, nil} + }(company.Index, company.Data) + } + + // 关闭结果通道 + go func() { + wg.Wait() + close(results) + }() + + // 解析原始数据为map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(data.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析data字段失败: %v", err) + } + + // 获取原始企业列表 + originalDatalist, ok := dataMap["datalist"].([]interface{}) + if !ok { + return nil, fmt.Errorf("无法获取原始企业列表") + } + + // 创建结果映射,用于保存已处理的企业 + processedCompanies := make(map[int]interface{}) + + // 收集处理过的企业数据 + for result := range results { + if result.err != nil { + logx.Errorf("处理企业失败: %v", result.err) + continue + } + + if result.data != nil { + var companyMap interface{} + if err := json.Unmarshal(result.data, &companyMap); err == nil { + processedCompanies[result.index] = companyMap + } + } + } + + // 更新企业列表 + // 处理过的用新数据,未处理的保留原样 + updatedDatalist := make([]interface{}, len(originalDatalist)) + for i, company := range originalDatalist { + if processed, exists := processedCompanies[i]; exists { + // 已处理的企业,使用新数据 + updatedDatalist[i] = processed + } else { + // 未处理的企业,保留原始数据并添加空的涉诉信息 + companyMap, ok := company.(map[string]interface{}) + if ok { + // 为未处理的企业添加空的涉诉信息 + companyMap["lawsuitInfo"] = map[string]interface{}{} + updatedDatalist[i] = companyMap + } else { + updatedDatalist[i] = company + } + } + } + + // 更新原始数据中的企业列表 + dataMap["datalist"] = updatedDatalist + + // 序列化最终结果 + result, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("序列化最终结果失败: %v", err) + } + + return result, nil + } + + // code不等于"0000",返回错误 + return nil, fmt.Errorf("响应code错误: %s", code.String()) +} + +// ProcesFLXG0V4BRequest 个人司法涉诉(详版) +func (a *ApiRequestService) ProcesFLXG0V4BRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, FLXG0V4B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG0V4B", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "auth_date": generateAuthDateRange(), + }) + if err != nil { + return nil, err + } + respBytes, err := json.Marshal(resp.Data) + if err != nil { + return nil, err + } + return respBytes, nil +} + +// ProcessFLXG0687Request 反诈反赌核验 +func (a *ApiRequestService) ProcessFLXG0687Request(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, FLXG0687, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG0687", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + Value := gjson.GetBytes(respBytes, "value") + if !Value.Exists() { + return nil, fmt.Errorf("自然人反诈反赌核验查询失败") + } + + return []byte(Value.Raw), nil +} + +// ProcessFLXG3D56Request 违约失信 +func (a *ApiRequestService) ProcessFLXG3D56Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, FLXG3D56, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG3D56", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + codeResult := gjson.GetBytes(respBytes, "code") + if !codeResult.Exists() { + return nil, fmt.Errorf("code 字段不存在") + } + if codeResult.String() != "00" { + return nil, fmt.Errorf("未匹配到相关结果") + } + + // 获取 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("data 字段不存在") + } + + // 将 data 字段解析为 map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析 data 字段失败: %v", err) + } + + // 删除指定字段 + delete(dataMap, "swift_number") + delete(dataMap, "DataStrategy") + + // 重新编码为 JSON + modifiedData, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("编码修改后的 data 失败: %v", err) + } + return modifiedData, nil +} + +// ProcessIVYZ5733Request 婚姻状况 +func (a *ApiRequestService) ProcessIVYZ5733Request(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ5733, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ5733", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + result := gjson.GetBytes(respBytes, "data.data") + if !result.Exists() { + return nil, fmt.Errorf("婚姻状态查询失败") + } + + // 获取原始结果 + rawResult := result.String() + + // 根据结果转换状态码 + var statusCode string + switch { + case strings.HasPrefix(rawResult, "INR"): + statusCode = "0" // 匹配不成功 + case strings.HasPrefix(rawResult, "IA"): + statusCode = "1" // 结婚 + case strings.HasPrefix(rawResult, "IB"): + statusCode = "2" // 离婚 + default: + return nil, fmt.Errorf("婚姻状态查询失败,未知状态码: %s", statusCode) + } + + // 构建新的返回结果 + response := map[string]string{ + "status": statusCode, + } + // 序列化为JSON + jsonResponse, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("序列化结果失败: %v", err) + } + + return jsonResponse, nil +} + +// ProcessIVYZ9A2BRequest 学历查询 +func (a *ApiRequestService) ProcessIVYZ9A2BRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ9A2B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ9A2B", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 解析响应 + codeResult := gjson.GetBytes(respBytes, "data.education_background.code") + if !codeResult.Exists() { + return nil, fmt.Errorf("教育经历核验查询失败: 返回数据缺少code字段") + } + + code := codeResult.String() + var result map[string]interface{} + + switch code { + case "9100": + // 查询成功有结果 + eduResultArray := gjson.GetBytes(respBytes, "data.education_background.data").Array() + var processedEduData []interface{} + + // 提取每个元素中Raw字段的实际内容 + for _, item := range eduResultArray { + var eduInfo interface{} + if err := json.Unmarshal([]byte(item.Raw), &eduInfo); err != nil { + return nil, fmt.Errorf("解析教育信息失败: %v", err) + } + processedEduData = append(processedEduData, eduInfo) + } + + result = map[string]interface{}{ + "data": processedEduData, + "status": 1, + } + case "9000": + // 查询成功无结果 + result = map[string]interface{}{ + "data": []interface{}{}, + "status": 0, + } + default: + // 其他情况视为错误 + errMsg := gjson.GetBytes(respBytes, "data.education_background.msg").String() + return nil, fmt.Errorf("教育经历核验查询失败: %s (code: %s)", errMsg, code) + } + + // 将结果转为JSON字节 + jsonResult, err := json.Marshal(result) + if err != nil { + return nil, fmt.Errorf("处理教育经历查询结果失败: %v", err) + } + + return jsonResult, nil +} + +// ProcessYYSYBE08Request 二要素 +func (a *ApiRequestService) ProcessYYSYBE08Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, YYSYBE08, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("YYSYBE08", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 使用gjson获取resultCode + resultCode := gjson.GetBytes(respBytes, "ctidRequest.ctidAuth.resultCode") + if !resultCode.Exists() { + return nil, errors.New("获取resultCode失败") + } + + // 获取resultCode的第一个字符 + resultCodeStr := resultCode.String() + if len(resultCodeStr) == 0 { + return nil, errors.New("resultCode为空") + } + + firstChar := string(resultCodeStr[0]) + if firstChar != "0" && firstChar != "5" { + return nil, errors.New("resultCode的第一个字符既不是0也不是5") + } + return []byte(firstChar), nil +} + +// ProcessJRZQ0A03Request 借贷申请 +func (a *ApiRequestService) ProcessJRZQ0A03Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ0A03, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ0A03", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 获取 code 字段 + codeResult := gjson.GetBytes(respBytes, "code") + if !codeResult.Exists() { + return nil, fmt.Errorf("code 字段不存在") + } + if codeResult.String() != "00" { + return nil, fmt.Errorf("未匹配到相关结果") + } + + // 获取 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("data 字段不存在") + } + + // 将 data 字段解析为 map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析 data 字段失败: %v", err) + } + + // 删除指定字段 + delete(dataMap, "swift_number") + delete(dataMap, "DataStrategy") + + // 重新编码为 JSON + modifiedData, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("编码修改后的 data 失败: %v", err) + } + return modifiedData, nil +} + +// ProcessJRZQ8203Request 借贷行为 +func (a *ApiRequestService) ProcessJRZQ8203Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ8203, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ8203", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 获取 code 字段 + codeResult := gjson.GetBytes(respBytes, "code") + if !codeResult.Exists() { + return nil, fmt.Errorf("code 字段不存在") + } + if codeResult.String() != "00" { + return nil, fmt.Errorf("未匹配到相关结果") + } + + // 获取 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("data 字段不存在") + } + + // 将 data 字段解析为 map + var dataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil { + return nil, fmt.Errorf("解析 data 字段失败: %v", err) + } + + // 删除指定字段 + delete(dataMap, "swift_number") + delete(dataMap, "DataStrategy") + + // 重新编码为 JSON + modifiedData, err := json.Marshal(dataMap) + if err != nil { + return nil, fmt.Errorf("编码修改后的 data 失败: %v", err) + } + return modifiedData, nil +} + +// ProcessJRZQ4AA8Request 还款压力 +func (a *ApiRequestService) ProcessJRZQ4AA8Request(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + mobile := gjson.GetBytes(params, "mobile") + if !idCard.Exists() || !name.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ4AA8, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ4AA8", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 获取响应码和偿贷压力标志 + code := gjson.GetBytes(respBytes, "code").String() + flagDebtRepayStress := gjson.GetBytes(respBytes, "flag_debtrepaystress").String() + + // 判断是否成功 + if code != "00" || flagDebtRepayStress != "1" { + return nil, fmt.Errorf("偿贷压力查询失败: %+v", respBytes) + } + // 获取偿贷压力分数 + drsNoDebtScore := gjson.GetBytes(respBytes, "drs_nodebtscore").String() + + // 构建结果 + result := map[string]interface{}{ + "score": drsNoDebtScore, + } + + // 将结果转为JSON + jsonResult, err := json.Marshal(result) + if err != nil { + return nil, fmt.Errorf("处理偿贷压力查询结果失败: %v", err) + } + + return jsonResult, nil +} + +// ProcessQYGL8271Request 企业涉诉 +func (a *ApiRequestService) ProcessQYGL8271Request(params []byte) ([]byte, error) { + entName := gjson.GetBytes(params, "ent_name") + entCode := gjson.GetBytes(params, "ent_code") + + if !entName.Exists() || !entCode.Exists() { + return nil, errors.New("api请求, QYGL8271, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGL8271", map[string]interface{}{ + "ent_name": entName.String(), + "ent_code": entCode.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 第一步:提取外层的 data 字段 + dataResult := gjson.GetBytes(respBytes, "data") + if !dataResult.Exists() { + return nil, fmt.Errorf("外层 data 字段不存在") + } + + // 第二步:解析外层 data 的 JSON 字符串 + var outerDataMap map[string]interface{} + if err := json.Unmarshal([]byte(dataResult.String()), &outerDataMap); err != nil { + return nil, fmt.Errorf("解析外层 data 字段失败: %v", err) + } + + // 第三步:提取内层的 data 字段 + innerData, ok := outerDataMap["data"].(string) + if !ok { + return nil, fmt.Errorf("内层 data 字段不存在或类型错误") + } + + // 第四步:解析内层 data 的 JSON 字符串 + var finalDataMap map[string]interface{} + if err := json.Unmarshal([]byte(innerData), &finalDataMap); err != nil { + return nil, fmt.Errorf("解析内层 data 字段失败: %v", err) + } + + // 将最终的 JSON 对象编码为字节数组返回 + finalDataBytes, err := json.Marshal(finalDataMap) + if err != nil { + return nil, fmt.Errorf("编码最终的 JSON 对象失败: %v", err) + } + + statusResult := gjson.GetBytes(finalDataBytes, "status.status") + if statusResult.Exists() || statusResult.Int() == -1 { + return nil, fmt.Errorf("企业涉诉为空: %+v", finalDataBytes) + } + return finalDataBytes, nil +} + +// ProcessFLXG0V4BRequest 个人涉诉 +func (a *ApiRequestService) ProcessFLXG0V4BRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, FLXG0V4B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG0V4B", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "auth_date": generateAuthDateRange(), + }, &tianyuanapi.ApiCallOptions{ + Json: true, + }) + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessQYGL6F2DRequest 人企关联 +func (a *ApiRequestService) ProcessQYGL6F2DRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, QYGL6F2D, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGL6F2D", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 处理股东人企关系的响应数据 + code := gjson.GetBytes(respBytes, "code") + if !code.Exists() { + return nil, fmt.Errorf("响应中缺少 code 字段") + } + + // 判断 code 是否等于 "0000" + if code.String() == "0000" { + // 获取 data 字段的值 + data := gjson.GetBytes(respBytes, "data") + if !data.Exists() { + return nil, fmt.Errorf("响应中缺少 data 字段") + } + // 返回 data 字段的内容 + return []byte(data.Raw), nil + } + + // code 不等于 "0000",返回错误 + return nil, fmt.Errorf("响应code错误%s", code.String()) +} + +// ProcessQCXGGB2QRequest 人车核验简版 +func (a *ApiRequestService) ProcessQCXGGB2QRequest(params []byte) ([]byte, error) { + plateNo := gjson.GetBytes(params, "plate_no") + carplateType := gjson.GetBytes(params, "carplate_type") + name := gjson.GetBytes(params, "name") + + if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() { + return nil, errors.New("api请求, QCXGGB2Q, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QCXGGB2Q", map[string]interface{}{ + "plate_no": plateNo.String(), + "carplate_type": carplateType.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessQCXGYTS2Request 人车核验详版 +func (a *ApiRequestService) ProcessQCXGYTS2Request(params []byte) ([]byte, error) { + plateNo := gjson.GetBytes(params, "plate_no") + carplateType := gjson.GetBytes(params, "carplate_type") + name := gjson.GetBytes(params, "name") + + if !plateNo.Exists() || !carplateType.Exists() || !name.Exists() { + return nil, errors.New("api请求, QCXGYTS2, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QCXGYTS2", map[string]interface{}{ + "plate_no": plateNo.String(), + "carplate_type": carplateType.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessQCXG5F3ARequest 名下车辆(车牌) +func (a *ApiRequestService) ProcessQCXG5F3ARequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, QCXG5F3A, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QCXG5F3A", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// processVehicleApiPassThrough 车辆类接口通用透传:将缓存 params 原样传给天远(传参后续按接口文档再细化) +func (a *ApiRequestService) processVehicleApiPassThrough(params []byte, apiID string) ([]byte, error) { + var m map[string]interface{} + if err := json.Unmarshal(params, &m); err != nil { + return nil, fmt.Errorf("api请求, %s, 解析参数失败: %w", apiID, err) + } + logx.Infof("vehicle api passthrough, api_id=%s, params_keys=%v", apiID, keysOfMap(m)) + resp, err := a.tianyuanapi.CallInterface(apiID, m) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// 车辆 API 按 md 从缓存 params 提取字段并调用天远 + +func (a *ApiRequestService) ProcessQCXG4D2ERequest(params []byte) ([]byte, error) { + m := map[string]interface{}{} + if err := json.Unmarshal(params, &m); err != nil { + return nil, fmt.Errorf("api请求, QCXG4D2E, 解析参数失败: %w", err) + } + body := map[string]interface{}{} + if v, ok := m["user_type"].(string); ok && v != "" { + body["user_type"] = v + } else { + body["user_type"] = "1" + } + if v, ok := m["id_card"].(string); ok { + body["id_card"] = v + } else { + return nil, errors.New("api请求, QCXG4D2E, 缺少 id_card") + } + resp, err := a.tianyuanapi.CallInterface("QCXG4D2E", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG5U0ZRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + if !vin.Exists() || vin.String() == "" { + return nil, errors.New("api请求, QCXG5U0Z, 缺少 vin_code") + } + resp, err := a.tianyuanapi.CallInterface("QCXG5U0Z", map[string]interface{}{"vin_code": vin.String()}) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG1U4URequest(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code", "image_url"}, nil) + orderNo := gjson.GetBytes(params, "order_no").String() + if body["vin_code"] == nil || body["image_url"] == nil || orderNo == "" { + return nil, errors.New("api请求, QCXG1U4U, 缺少必填参数 vin_code/image_url/order_no") + } + logx.Infof("vehicle api request QCXG1U4U, order_no=%s, vin_code=%v, image_url=%v", orderNo, body["vin_code"], body["image_url"]) + body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG1U4U") + resp, err := a.tianyuanapi.CallInterface("QCXG1U4U", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXGY7F2Request(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code", "vehicle_location", "first_registrationdate"}, nil) + if body["vin_code"] == nil || body["vehicle_location"] == nil || body["first_registrationdate"] == nil { + return nil, errors.New("api请求, QCXGY7F2, 缺少必填参数 vin_code/vehicle_location/first_registrationdate") + } + logx.Infof("vehicle api request QCXGY7F2, vin_code=%v, vehicle_location=%v, first_registrationdate=%v", body["vin_code"], body["vehicle_location"], body["first_registrationdate"]) + resp, err := a.tianyuanapi.CallInterface("QCXGY7F2", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG1H7YRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + plate := gjson.GetBytes(params, "car_license") + if !vin.Exists() || vin.String() == "" || !plate.Exists() || plate.String() == "" { + return nil, errors.New("api请求, QCXG1H7Y, 缺少 vin_code 或 car_license") + } + // 天远侧字段为 vin_code + plate_no,这里将前端 car_license 映射为 plate_no + body := map[string]interface{}{ + "vin_code": vin.String(), + "plate_no": plate.String(), + } + logx.Infof("vehicle api request QCXG1H7Y, vin_code=%s, plate_no=%s", vin.String(), plate.String()) + resp, err := a.tianyuanapi.CallInterface("QCXG1H7Y", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG4I1ZRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + if !vin.Exists() || vin.String() == "" { + return nil, errors.New("api请求, QCXG4I1Z, 缺少 vin_code") + } + resp, err := a.tianyuanapi.CallInterface("QCXG4I1Z", map[string]interface{}{"vin_code": vin.String()}) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG3Y6BRequest(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code"}, nil) + orderNo := gjson.GetBytes(params, "order_no").String() + if body["vin_code"] == nil || orderNo == "" { + return nil, errors.New("api请求, QCXG3Y6B, 缺少必填参数 vin_code/order_no") + } + logx.Infof("vehicle api request QCXG3Y6B, order_no=%s, vin_code=%v", orderNo, body["vin_code"]) + body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Y6B") + resp, err := a.tianyuanapi.CallInterface("QCXG3Y6B", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG3Z3LRequest(params []byte) ([]byte, error) { + body := buildVehicleBody(params, []string{"vin_code"}, nil) + orderNo := gjson.GetBytes(params, "order_no").String() + if body["vin_code"] == nil || orderNo == "" { + return nil, errors.New("api请求, QCXG3Z3L, 缺少必填参数 vin_code/order_no") + } + logx.Infof("vehicle api request QCXG3Z3L, order_no=%s, vin_code=%v", orderNo, body["vin_code"]) + body["return_url"] = a.buildVehicleCallbackURL(orderNo, "QCXG3Z3L") + resp, err := a.tianyuanapi.CallInterface("QCXG3Z3L", body) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXGP00WRequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + orderNo := gjson.GetBytes(params, "order_no").String() + vlphoto := gjson.GetBytes(params, "vlphoto_data") + if !vin.Exists() || vin.String() == "" || orderNo == "" || !vlphoto.Exists() || vlphoto.String() == "" { + return nil, errors.New("api请求, QCXGP00W, 缺少必填参数 vin_code/order_no/vlphoto_data") + } + logx.Infof("vehicle api request QCXGP00W, order_no=%s, vin_code=%s, vlphoto_data_len=%d", orderNo, vin.String(), len(vlphoto.String())) + key, err := hex.DecodeString(a.config.Encrypt.SecretKey) + if err != nil { + return nil, fmt.Errorf("api请求, QCXGP00W, 密钥解析失败: %w", err) + } + encData, err := crypto.AesEncrypt([]byte(vlphoto.String()), key) + if err != nil { + return nil, fmt.Errorf("api请求, QCXGP00W, 加密行驶证数据失败: %w", err) + } + resp, err := a.tianyuanapi.CallInterface("QCXGP00W", map[string]interface{}{ + "vin_code": vin.String(), + "return_url": a.buildVehicleCallbackURL(orderNo, "QCXGP00W"), + "data": encData, + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessQCXG6B4ERequest(params []byte) ([]byte, error) { + vin := gjson.GetBytes(params, "vin_code") + if !vin.Exists() || vin.String() == "" { + return nil, errors.New("api请求, QCXG6B4E, 缺少 vin_code") + } + auth := gjson.GetBytes(params, "authorized").String() + if auth == "" { + auth = "1" + } + // 天远文档字段名为 VINCode、Authorized + resp, err := a.tianyuanapi.CallInterface("QCXG6B4E", map[string]interface{}{ + "vin_code": vin.String(), + "authorized": auth, + }) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// processVerifyPassThrough 核验类接口:缓存 params 已含 mobile_no/id_card/name 等,原样传天远 +func (a *ApiRequestService) processVerifyPassThrough(params []byte, apiID string) ([]byte, error) { + var m map[string]interface{} + if err := json.Unmarshal(params, &m); err != nil { + return nil, fmt.Errorf("api请求, %s, 解析参数失败: %w", apiID, err) + } + logx.Infof("verify api passthrough, api_id=%s, params_keys=%v", apiID, keysOfMap(m)) + resp, err := a.tianyuanapi.CallInterface(apiID, m) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +func (a *ApiRequestService) ProcessIVYZ9K7FRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "IVYZ9K7F") +} +func (a *ApiRequestService) ProcessIVYZA1B3Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "IVYZA1B3") +} +func (a *ApiRequestService) ProcessIVYZ6M8PRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "IVYZ6M8P") +} +func (a *ApiRequestService) ProcessJRZQ8B3CRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "JRZQ8B3C") +} +func (a *ApiRequestService) ProcessYYSY3M8SRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSY3M8S") +} +func (a *ApiRequestService) ProcessYYSYK9R4Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSYK9R4") +} +func (a *ApiRequestService) ProcessYYSYF2T7Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSYF2T7") +} +func (a *ApiRequestService) ProcessYYSYK8R3Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSYK8R3") +} +func (a *ApiRequestService) ProcessYYSYS9W1Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSYS9W1") +} +func (a *ApiRequestService) ProcessYYSYE7V5Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSYE7V5") +} +func (a *ApiRequestService) ProcessYYSYP0T4Request(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSYP0T4") +} +func (a *ApiRequestService) ProcessYYSY6F2BRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSY6F2B") +} +func (a *ApiRequestService) ProcessYYSY9E4ARequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "YYSY9E4A") +} +func (a *ApiRequestService) ProcessQYGL5F6ARequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "QYGL5F6A") +} +func (a *ApiRequestService) ProcessJRZQACABRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "JRZQACAB") +} +func (a *ApiRequestService) ProcessJRZQ0B6YRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "JRZQ0B6Y") +} + +// QYGL66SL 企业司法涉诉(简版) +func (a *ApiRequestService) ProcessQYGL66SLRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "QYGL66SL") +} + +// FLXG3A9B 限高被执行人 +func (a *ApiRequestService) ProcessFLXG3A9BRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "FLXG3A9B") +} + +// QYGL2S0W 失信被执行人 +func (a *ApiRequestService) ProcessQYGL2S0WRequest(params []byte) ([]byte, error) { + return a.processVerifyPassThrough(params, "QYGL2S0W") +} + +// FLXGDEA9 本人不良 +func (a *ApiRequestService) ProcessFLXGDEA9Request(params []byte) ([]byte, error) { + var m map[string]interface{} + if err := json.Unmarshal(params, &m); err != nil { + return nil, fmt.Errorf("api请求, FLXGDEA9, 解析参数失败: %w", err) + } + // 授权由后端默认传 1,前端与查询服务不再感知 authorized + if v, ok := m["authorized"]; !ok || v == "" { + m["authorized"] = "1" + } + resp, err := a.tianyuanapi.CallInterface("FLXGDEA9", m) + if err != nil { + return nil, err + } + return convertTianyuanResponse(resp) +} + +// buildVehicleBody 从 params 中取 required 与 optional 键,仅非空才写入 body +func buildVehicleBody(params []byte, required, optional []string) map[string]interface{} { + body := make(map[string]interface{}) + for _, k := range required { + v := gjson.GetBytes(params, k) + if v.Exists() { + body[k] = v.String() + } + } + for _, k := range optional { + v := gjson.GetBytes(params, k) + if v.Exists() && v.String() != "" { + body[k] = v.String() + } + } + return body +} + +// buildVehicleCallbackURL 生成车辆类接口的异步回调地址 +// 使用 PublicBaseURL 作为对外域名配置,路径固定为 /api/v1/tianyuan/vehicle/callback +// 并通过查询参数携带 order_no 与 api_id 以便后端识别具体查询与模块。 +func (a *ApiRequestService) buildVehicleCallbackURL(orderNo, apiID string) string { + base := strings.TrimRight(a.config.PublicBaseURL, "/") + if base == "" { + // 兜底:如果未配置 URLDomain,则使用相对路径,交给网关/部署层补全域名 + return fmt.Sprintf("/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", orderNo, apiID) + } + return fmt.Sprintf("%s/api/v1/tianyuan/vehicle/callback?order_no=%s&api_id=%s", base, orderNo, apiID) +} + +// ProcessQCXG7A2BRequest 名下车辆 +func (a *ApiRequestService) ProcessQCXG7A2BRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, QCXG7A2B, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QCXG7A2B", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessYYSY09CDRequest 三要素 +func (a *ApiRequestService) ProcessYYSY09CDRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, YYSY09CD, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("YYSY09CD", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + respBytes, err := convertTianyuanResponse(resp) + if err != nil { + return nil, err + } + + // 使用gjson获取resultCode + resultCode := gjson.GetBytes(respBytes, "ctidRequest.ctidAuth.resultCode") + if !resultCode.Exists() { + return nil, errors.New("获取resultCode失败") + } + + // 获取resultCode的第一个字符 + resultCodeStr := resultCode.String() + if len(resultCodeStr) == 0 { + return nil, errors.New("resultCode为空") + } + + firstChar := string(resultCodeStr[0]) + if firstChar != "0" && firstChar != "5" { + return nil, errors.New("resultCode的第一个字符既不是0也不是5") + } + return []byte(firstChar), nil +} + +// ProcessBehaviorRiskScanRequest 行为风险扫描 +func (a *ApiRequestService) ProcessBehaviorRiskScanRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, BehaviorRiskScan, 获取相关参数失败") + } + + var wg sync.WaitGroup + type apiResult struct { + name string + data []byte + err error + } + results := make(chan apiResult, 1) // 4个风险检测项 + + // 并行调用两个不同的风险检测API + wg.Add(1) + + // 反赌反诈 + go func() { + defer wg.Done() + respBytes, err := a.ProcessFLXG0687Request(params) + results <- apiResult{name: "anti_fraud_gaming", data: respBytes, err: err} + }() + + // 关闭结果通道 + go func() { + wg.Wait() + close(results) + }() + + // 收集所有结果 + resultMap := make(map[string]interface{}) + var errors []string + + for result := range results { + if result.err != nil { + // 记录错误但继续处理其他结果 + errors = append(errors, fmt.Sprintf("%s: %v", result.name, result.err)) + continue + } + + // 解析JSON结果并添加到结果映射 + var parsedData interface{} + if err := json.Unmarshal(result.data, &parsedData); err != nil { + errors = append(errors, fmt.Sprintf("解析%s数据失败: %v", result.name, err)) + } else { + resultMap[result.name] = parsedData + } + } + + // 添加错误信息到结果中(如果存在) + if len(errors) > 0 { + resultMap["errors"] = errors + } + + // 序列化最终结果 + finalResult, err := json.Marshal(resultMap) + if err != nil { + return nil, fmt.Errorf("序列化行为风险扫描结果失败: %v", err) + } + + return finalResult, nil +} + +// ProcessDWBG8B4DRequest 谛听多维报告 +func (a *ApiRequestService) ProcessDWBG8B4DRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + AuthorizationURL := gjson.GetBytes(params, "authorization_url") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() || !AuthorizationURL.Exists() { + return nil, errors.New("api请求, DWBG8B4D, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("DWBG8B4D", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorization_url": AuthorizationURL.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessDWBG6A2CRequest 司南报告服务 +func (a *ApiRequestService) ProcessDWBG6A2CRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + AuthorizationURL := gjson.GetBytes(params, "authorization_url") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() || !AuthorizationURL.Exists() { + return nil, errors.New("api请求, DWBG6A2C, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("DWBG6A2C", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorization_url": AuthorizationURL.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessJRZQ4B6CRequest 探针C风险评估 +func (a *ApiRequestService) ProcessJRZQ4B6CRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ4B6C, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ4B6C", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessJRZQ09J8Request 收入评估 +func (a *ApiRequestService) ProcessJRZQ09J8Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ09J8, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ09J8", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessJRZQ5E9FRequest 借选指数 +func (a *ApiRequestService) ProcessJRZQ5E9FRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, JRZQ5E9F, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("JRZQ5E9F", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile_no": mobile.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessQYGL3F8ERequest 人企关系加强版2 +func (a *ApiRequestService) ProcessQYGL3F8ERequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + if !idCard.Exists() { + return nil, errors.New("api请求, QYGL3F8E, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QYGL3F8E", map[string]interface{}{ + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ81NCRequest 婚姻,登记时间版 +func (a *ApiRequestService) ProcessIVYZ81NCRequest(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, IVYZ81NC, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ81NC", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ7F3ARequest 学历查询版B +func (a *ApiRequestService) ProcessIVYZ7F3ARequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ7F3A, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ7F3A", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + // 直接返回解密后的数据,而不是再次进行JSON编码 + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ3P9MRequest 学历实时查询 +func (a *ApiRequestService) ProcessIVYZ3P9MRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ3P9M, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ3P9M", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessFLXG7E8FRequest 个人涉诉 +func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + mobile := gjson.GetBytes(params, "mobile") + if !idCard.Exists() || !name.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, FLXG7E8F, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("FLXG7E8F", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + "mobile_no": mobile.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessQCXG9P1CFRequest 名下车辆 +func (a *ApiRequestService) ProcessQCXG9P1CFRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + mobile := gjson.GetBytes(params, "mobile") + if !idCard.Exists() || !name.Exists() || !mobile.Exists() { + return nil, errors.New("api请求, QCXG9P1C, 获取相关参数失败") + } + + resp, err := a.tianyuanapi.CallInterface("QCXG9P1C", map[string]interface{}{ + "id_card": idCard.String(), + "authorized": "1", + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ4Y27Request 学历信息查询(需生成专用授权书PDF并Base64编码传入) +func (a *ApiRequestService) ProcessIVYZ4Y27Request(params []byte) ([]byte, error) { + name := gjson.GetBytes(params, "name") + idCard := gjson.GetBytes(params, "id_card") + mobile := gjson.GetBytes(params, "mobile") + if !name.Exists() || !idCard.Exists() { + return nil, errors.New("api请求, IVYZ4Y27, 获取相关参数失败") + } + + // 生成专用授权书PDF并Base64编码 + userInfo := map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "mobile": mobile.String(), + } + authFileBase64, err := a.authService.GenerateIVYZ4Y27AuthorizationBase64(userInfo) + if err != nil { + return nil, fmt.Errorf("生成IVYZ4Y27授权书失败: %v", err) + } + + resp, err := a.tianyuanapi.CallInterface("IVYZ4Y27", map[string]interface{}{ + "name": name.String(), + "id_card": idCard.String(), + "auth_authorize_file_base64": authFileBase64, + }) + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} + +// ProcessIVYZ0S0DRequest 劳动仲裁信息查询(个人版) +func (a *ApiRequestService) ProcessIVYZ0S0DRequest(params []byte) ([]byte, error) { + idCard := gjson.GetBytes(params, "id_card") + name := gjson.GetBytes(params, "name") + if !idCard.Exists() || !name.Exists() { + return nil, errors.New("api请求, IVYZ0S0D, 获取相关参数失败") + } + resp, err := a.tianyuanapi.CallInterface("IVYZ0S0D", map[string]interface{}{ + "id_card": idCard.String(), + "name": name.String(), + }) + + if err != nil { + return nil, err + } + + return convertTianyuanResponse(resp) +} diff --git a/app/main/api/internal/service/tianxingjuhe_sdk/client.go b/app/main/api/internal/service/tianxingjuhe_sdk/client.go new file mode 100644 index 0000000..6a4f545 --- /dev/null +++ b/app/main/api/internal/service/tianxingjuhe_sdk/client.go @@ -0,0 +1,150 @@ +package tianxingjuhe + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" +) + +// Client 天行聚合API客户端 +type Client struct { + baseURL string + key string + client *http.Client +} + +// Config 客户端配置 +type Config struct { + BaseURL string // API基础URL + Key string // API密钥 + Timeout int // 超时时间(秒) +} + +// Response 通用API响应结构 +type Response struct { + Code int `json:"code"` // 状态码,200表示成功 + Msg string `json:"msg"` // 返回说明 + Result interface{} `json:"result"` // 返回结果集,具体内容根据接口而定 +} + +// NewClient 创建新的客户端实例 +func NewClient(config Config) (*Client, error) { + if config.BaseURL == "" { + return nil, fmt.Errorf("baseURL不能为空") + } + if config.Key == "" { + return nil, fmt.Errorf("key不能为空") + } + + return &Client{ + baseURL: config.BaseURL, + key: config.Key, + client: &http.Client{ + Timeout: time.Duration(config.Timeout) * time.Second, + }, + }, nil +} + +// Get 发送GET请求 +func (c *Client) Get(endpoint string, params map[string]interface{}) (*Response, error) { + // 构建完整URL + fullURL := fmt.Sprintf("%s/%s", c.baseURL, endpoint) + + // 添加请求参数 + queryParams := url.Values{} + for key, value := range params { + queryParams.Set(key, fmt.Sprintf("%v", value)) + } + + // 添加key参数 + queryParams.Set("key", c.key) + + // 拼接查询参数 + if len(queryParams) > 0 { + fullURL = fmt.Sprintf("%s?%s", fullURL, queryParams.Encode()) + } + + // 创建HTTP请求 + req, err := http.NewRequest("GET", fullURL, nil) + if err != nil { + return nil, fmt.Errorf("创建HTTP请求失败: %v", err) + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "Tianxingjuhe-Go-SDK/1.0.0") + + // 发送请求 + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("发送HTTP请求失败: %v", err) + } + defer resp.Body.Close() + + // 读取响应 + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + // 解析响应 + var apiResp Response + if err := json.Unmarshal(body, &apiResp); err != nil { + return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body)) + } + + return &apiResp, nil +} + +// Post 发送POST请求 +func (c *Client) Post(endpoint string, params map[string]interface{}) (*Response, error) { + // 构建完整URL + fullURL := fmt.Sprintf("%s/%s", c.baseURL, endpoint) + + // 添加key参数 + if params == nil { + params = make(map[string]interface{}) + } + params["key"] = c.key + + // 序列化请求体 + requestBody, err := json.Marshal(params) + if err != nil { + return nil, fmt.Errorf("序列化请求体失败: %v", err) + } + + // 创建HTTP请求 + req, err := http.NewRequest("POST", fullURL, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, fmt.Errorf("创建HTTP请求失败: %v", err) + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "Tianxingjuhe-Go-SDK/1.0.0") + + // 发送请求 + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("发送HTTP请求失败: %v", err) + } + defer resp.Body.Close() + + // 读取响应 + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("读取响应失败: %v", err) + } + + // 解析响应 + var apiResp Response + if err := json.Unmarshal(body, &apiResp); err != nil { + return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body)) + } + + return &apiResp, nil +} diff --git a/app/main/api/internal/service/tianyuanapiCallLogService.go b/app/main/api/internal/service/tianyuanapiCallLogService.go index 907531b..d710102 100644 --- a/app/main/api/internal/service/tianyuanapiCallLogService.go +++ b/app/main/api/internal/service/tianyuanapiCallLogService.go @@ -7,9 +7,10 @@ import ( "fmt" "time" + "qnc-server/app/main/model" + "github.com/Masterminds/squirrel" "github.com/zeromicro/go-zero/core/logx" - "qnc-server/app/main/model" ) // TianyuanapiCallLogService 天元API调用记录服务 diff --git a/app/main/api/internal/service/toolboxService.go b/app/main/api/internal/service/toolboxService.go new file mode 100644 index 0000000..37d8091 --- /dev/null +++ b/app/main/api/internal/service/toolboxService.go @@ -0,0 +1,8276 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + tianxingjuhe "qnc-server/app/main/api/internal/service/tianxingjuhe_sdk" + "qnc-server/common/xerr" + "regexp" + "strconv" + "strings" + "sync" + "time" + "unicode" + + "github.com/zeromicro/go-zero/core/logx" +) + +// ============================================================================ +// 工具箱服务 - 功能清单 +// ============================================================================ +// +// 【统计信息】 +// 总工具数:146 个 +// - 纯计算类工具(无外部依赖):10 个 +// - 天行聚合 SDK 工具:136 个 +// +// 【工具分类】 +// 1️⃣ 实用查询类 (26个) +// ip-location, idcard-info, phone-location, beijing-time, bank-card, +// plate-parse, domain, domainparse, zone, areadata, addressparse, +// telecode, obdcode, charconvert, checkipv6, worldtime, timezone, +// shares, fxrate, oilprice, holiday, zone, scenic, lunar, aqi +// +// 2️⃣ 文字处理类 (20个) +// money-to-chinese, password-strength, days-between-dates, file-size-format, +// text-stats, pinyin, hanzipinyin, duoyinzi, chaizi, xhzd, jf-words, +// lexicon, enwords, ensentence, fanyi, dailytel, charconvert, +// slogan, zimi +// +// 3️⃣ 文化知识类 (35个) +// history-today, ten-why, duilian, gjdm, bcgm, idiom-quiz, poem-fill, +// fairytales, yuanqu, poetry, poetries, shijing, songci, verse, +// tianqishiju, chengyu, caichengyu, chengyujielong, pitlishi, riddle, +// cityriddle, caizimi, zimi, qingshi, flmj, xiehou, proverb, dictum, +// mingyan, sentence, mnpara, mgjuzi, duishici, msdl +// +// 4️⃣ 娱乐趣味类 (25个) +// jixiong, blood, zodiac, xingzuo, star, constellation, dob, saylove, +// caihongpi, dujitang, joke, naowan, wenda, decide, caipu, dream, +// rkl, godreply, tiangou, robotlog, dialogue, story, zmsc, targa, +// hsjz +// +// 5️⃣ 生活资讯类 (25个) +// everyday, woman, pyqwenan, healthskill, healthtip, nutrient, +// surname, jtwfcode, pet, bmi, zhongyao, yaopin, qiaomen, +// areanews, wxnew, wxhottopic, weibohot, nethot, hotword, hotreview, +// bulletin, douyinhot, zaowanwords, wananwords, caihongpi +// +// 6️⃣ 搜索查询类 (15个) +// one, proverb, slogan, chengyu, lexicon, cnmoney, lzmy, baiketiku, +// gjmj, shares, hotlajifenlei, garbage, lajifenlei, anslajifenlei, +// hotreview, +// ============================================================================ + +// ToolboxService 工具箱服务 —— 所有工具业务方法统一在此文件 +type ToolboxService struct { + tianxingjuheClient *tianxingjuhe.Client + processors map[string]ToolProcessor + historyTodayCache sync.Map // key: MMDD -> value: historyTodayCacheItem +} + +// historyTodayCacheItem 历史今天缓存条目 +type historyTodayCacheItem struct { + Data map[string]interface{} + ExpiredAt time.Time +} + +// ToolProcessor 工具处理函数签名 +type ToolProcessor func(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) + +// NewToolboxService 创建工具箱服务实例 +func NewToolboxService(tianxingjuheClient *tianxingjuhe.Client) *ToolboxService { + s := &ToolboxService{ + tianxingjuheClient: tianxingjuheClient, + processors: make(map[string]ToolProcessor), + } + s.registerAllProcessors() + return s +} + +// registerAllProcessors 注册所有工具处理器 +func (s *ToolboxService) registerAllProcessors() { + // 纯计算类工具(无外部依赖) + s.processors["ip-location"] = processIPLocation + s.processors["idcard-info"] = processIDCardInfo + s.processors["beijing-time"] = processBeijingTime + s.processors["bank-card"] = processBankCard + s.processors["plate-parse"] = processPlateParse + s.processors["money-to-chinese"] = s.processMoneyToChinese + s.processors["password-strength"] = processPasswordStrength + s.processors["days-between-dates"] = processDaysBetweenDates + s.processors["file-size-format"] = processFileSizeFormat + s.processors["text-stats"] = processTextStats + // 天行聚合 SDK 工具 + + s.processors["phone-location"] = s.processPhoneLocation + s.processors["history-today"] = s.processHistoryToday + s.processors["ten-why"] = s.processTenWhy + s.processors["jf-words"] = s.processJFWords // 近义词反义词 + s.processors["duilian"] = s.processDuilian // 经典对联 + s.processors["gjdm"] = s.processGjDm // 古今地名 + s.processors["bcgm"] = s.processBcgm // 本草纲目 + s.processors["idiom-quiz"] = s.processIdiomQuiz // 成语填字 + s.processors["poem-fill"] = s.processPoemFill // 诗词填空 + s.processors["fairytales"] = s.processFairytales // 童话故事 + s.processors["caihongpi"] = s.processCaiHongPi // 彩虹屁 + s.processors["jixiong"] = s.processJixiong // 吉凶测试 + s.processors["surname"] = s.processSurname // 姓氏起源 + s.processors["jtwfcode"] = s.processJtwfcode // 违章代码 + s.processors["healthskill"] = s.processHealthskill // 健康小妙招 + s.processors["pyqwenan"] = s.processPyqwenan // 朋友圈文案 + s.processors["woman"] = s.processWomanNews // 女性新闻 + s.processors["decide"] = s.processDecide // 常识判断题 + s.processors["everyday"] = s.processEveryday // 每日英语 + s.processors["areanews"] = s.processAreaNews // 地区新闻 + s.processors["yuanqu"] = s.processYuanqu // 元曲三百首 + s.processors["bmi"] = s.processBMI // BMI标准体重 + s.processors["zhongyao"] = s.processZhongyao // 中药大全 + s.processors["yaopin"] = s.processYaopin // 药品说明书 + s.processors["slogan"] = s.processSlogan // 猜广告语 + s.processors["zimi"] = s.processZimi // 猜字谜 + s.processors["dujitang"] = s.processDujitang // 毒鸡汤 + s.processors["one"] = s.processOne // ONE一个 + s.processors["proverb"] = s.processProverb // 文化谚语 + s.processors["caichengyu"] = s.processCaichengyu // 猜成语 + s.processors["qiaomen"] = s.processQiaomen // 生活小窍门 + s.processors["riddle"] = s.processRiddle // 谜语大全 + s.processors["healthtip"] = s.processHealthTip // 健康小贴士 + s.processors["nutrient"] = s.processNutrient // 营养素查询 + s.processors["fxrate"] = s.processFxRate // 汇率查询 + s.processors["bulletin"] = s.processBulletin // 新闻简报 + s.processors["caizimi"] = s.processCaizimi // 彩字谜 + s.processors["hotlajifenlei"] = s.processHotLajifenlei // 热搜垃圾分类 + s.processors["oilprice"] = s.processOilPrice // 实时油价 + s.processors["chengyujielong"] = s.processChengyuJielong // 成语接龙 + s.processors["weibohot"] = s.processWeiboHot // 微博热搜 + s.processors["scenic"] = s.processScenic // 景点查询 + s.processors["mingyan"] = s.processMingyan // 名言警句 + s.processors["tianqishiju"] = s.processTianqiShiju // 天气诗句 + s.processors["jieqi"] = s.processJieQi // 节气查询 + s.processors["blood"] = s.processBloodPair // 血型配对 + s.processors["zodiac"] = s.processZodiacPair // 生肖配对 + s.processors["saylove"] = s.processSayLove // 情话大全 + s.processors["star"] = s.processStar // 星座查询 + s.processors["pinyin"] = s.processPinyin // 汉字转拼音 + s.processors["nethot"] = s.processNetHot // 网络热搜 + s.processors["ensentence"] = s.processEnSentence // 英文句子 + s.processors["verse"] = s.processVerse // 诗词名句 + s.processors["poetry"] = s.processPoetry // 古诗词 + s.processors["poetries"] = s.processPoetries // 古诗词集 + s.processors["pitlishi"] = s.processPiTongLiShi // 谐音历史 + s.processors["mnpara"] = s.processMnPara // 美文段落 + s.processors["skl"] = s.processSkl // 十苦乐 + s.processors["sentence"] = s.processSentence // 唯美句子 + s.processors["enwords"] = s.processEnWords // 英语单词 + s.processors["lexicon"] = s.processLexicon // 词典查询 + s.processors["lunar"] = s.processLunar // 农历查询 + s.processors["xingzuo"] = s.processXingZuo // 星座配对 + s.processors["godreply"] = s.processGodReply // 神回复 + s.processors["xiehou"] = s.processXiehou // 歇后语 + s.processors["rkl"] = s.processRKL // 绕口令 + s.processors["cname"] = s.processCName // 起名大全 + s.processors["shares"] = s.processShares // 股票查询 + s.processors["hotword"] = s.processHotWord // 热词 + s.processors["cityriddle"] = s.processCityRiddle // 城市谜语 + s.processors["wenda"] = s.processWenDa // 问答 + s.processors["chengyu"] = s.processChengYu // 成语词典 + s.processors["naowan"] = s.processNaoWan // 脑筋急转弯 + s.processors["dob"] = s.processDOB // 生日书 + s.processors["dictum"] = s.processDictum // 格言 + s.processors["joke"] = s.processJoke // 趣味笑话 + s.processors["dream"] = s.processDream // 周公解梦 + s.processors["caipu"] = s.processCaiPu // 菜谱大全 + s.processors["wxnew"] = s.processWxNew // 微信新闻 + // 新增工具(有天行SDK处理函数) + s.processors["constellation"] = s.processConstellation // 星座运势 + s.processors["fanyi"] = s.processFanyi // 在线翻译 + s.processors["holiday"] = s.processHoliday // 节假日查询 + s.processors["garbage"] = s.processGarbage // 垃圾分类查询 + s.processors["zone"] = s.processZone // 地区搜索查询 + + // ─── 第二批新增工具 ─── + s.processors["timezone"] = s.processTimeZone + s.processors["domain"] = s.processDomainParse // 域名解析 + s.processors["duoyinzi"] = s.processDuoYinZi //多音字 + s.processors["dailytel"] = s.processDailyTel + s.processors["obdcode"] = s.processObdCode + s.processors["chaizi"] = s.processChaiZi //拆字 + s.processors["huayu"] = s.processHuaYu + s.processors["moodpoetry"] = s.processMoodPoetry + s.processors["pcterm"] = s.processPcTerm + s.processors["targa"] = s.processTarga + s.processors["duishici"] = s.processDuiShiCi + s.processors["msdl"] = s.processMsdl + s.processors["shijing"] = s.processShiJing + s.processors["chepai"] = s.processChePai + s.processors["flmj"] = s.processFlmj + s.processors["xhzd"] = s.processXhZd + s.processors["telecode"] = s.processTeleCode + s.processors["charconvert"] = s.processCharConvert + s.processors["checkipv6"] = s.processCheckIPv6 + s.processors["worldtime"] = s.processWorldTime + s.processors["wxhottopic"] = s.processWxHotTopic + s.processors["zmsc"] = s.processZmsc + s.processors["hsjz"] = s.processHsjz + s.processors["gjmj"] = s.processGjmj + s.processors["cnmoney"] = s.processCnMoney + s.processors["lzmy"] = s.processLzmy + s.processors["baiketiku"] = s.processBaiKeTiKu + s.processors["dialogue"] = s.processDialogue + s.processors["tiangou"] = s.processTianGou + s.processors["robotlog"] = s.processRobotLog + s.processors["aqi"] = s.processAqi + s.processors["addressparse"] = s.processAddressParse + s.processors["pet"] = s.processPet + s.processors["hotreview"] = s.processHotReview + s.processors["anslajifenlei"] = s.processAnsLaJiFenLei + s.processors["douyinhot"] = s.processDouYinHot + s.processors["zaowanwords"] = s.processZaoAnWords + s.processors["wananwords"] = s.processWanAnWords + s.processors["areadata"] = s.processAreaData // 全国五级行政区划 + s.processors["mgjuzi"] = s.processMgJuZi + s.processors["songci"] = s.processSongCi + s.processors["story"] = s.processStory + s.processors["qingshi"] = s.processQingShi + s.processors["lajifenlei"] = s.processLaJiFenLei + s.processors["hanzipinyin"] = s.processHanZiPinYin + s.processors["shorthistory"] = s.processShortHistory +} + +var ( + ipv4Regex = regexp.MustCompile(`^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$`) + ipv6Regex = regexp.MustCompile(`^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$|^([0-9a-fA-F]{1,4}:)*::([0-9a-fA-F]{1,4}:)*$`) +) + +var httpClient = &http.Client{ + Timeout: 3 * time.Second, +} + +// Query 统一入口:根据 toolKey 分发到对应的处理函数 +func (s *ToolboxService) Query(ctx context.Context, toolKey string, params map[string]interface{}) (map[string]interface{}, error) { + processor, ok := s.processors[toolKey] + if !ok { + return nil, xerr.NewErrMsg("不支持的工具类型: " + toolKey) + } + return processor(ctx, params) +} + +// ==================== 具体工具方法 ==================== + +// processIPLocation IP属地查询 +func processIPLocation(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + ip, _ := params["ip"].(string) + if ip == "" { + return nil, xerr.NewErrMsg("IP地址不能为空") + } + + // 内网IP + if net.ParseIP(ip).IsPrivate() { + return map[string]interface{}{ + "ip": ip, + "country": "内网", + "province": "内网", + "city": "内网", + "isp": "内网", + "latitude": 0, + "longitude": 0, + }, nil + } + + // 格式校验 + if !ipv4Regex.MatchString(ip) && !ipv6Regex.MatchString(ip) { + return nil, xerr.NewErrMsg("IP地址格式不正确") + } + + logx.WithContext(ctx).Infof("IP属地查询: %s", ip) + + // 默认空值 + country, province, city, isp := "", "", "", "" + latitude, longitude := 0.0, 0.0 + client := http.Client{Timeout: 3 * time.Second} + + // ====================== + // 1. 主接口:ip-api + // ====================== + apiUrl := fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN", ip) + resp, err := client.Get(apiUrl) + if err == nil && resp.StatusCode == 200 { + defer resp.Body.Close() + + // 直接解析成 map,不定义结构体! + var res map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&res); err == nil { + if res["status"] == "success" { + country, _ = res["country"].(string) + province, _ = res["regionName"].(string) + city, _ = res["city"].(string) + isp, _ = res["isp"].(string) + latitude, _ = res["lat"].(float64) + longitude, _ = res["lon"].(float64) + + return map[string]interface{}{ + "ip": ip, + "country": country, + "province": province, + "city": city, + "isp": isp, + "latitude": latitude, + "longitude": longitude, + }, nil + } + } + } + + // ====================== + // 2. 降级:淘宝IP + // ====================== + logx.WithContext(ctx).Infof("ip-api 失败,降级淘宝IP: %s", ip) + taobaoUrl := fmt.Sprintf("https://ip.taobao.com/outGetIpInfo?ip=%s&accessKey=alibaba-inc", ip) + resp2, err2 := client.Get(taobaoUrl) + if err2 == nil && resp2.StatusCode == 200 { + defer resp2.Body.Close() + + var res map[string]interface{} + if err := json.NewDecoder(resp2.Body).Decode(&res); err == nil { + data, ok := res["data"].(map[string]interface{}) + if ok && res["code"].(float64) == 0 { + country, _ = data["country"].(string) + province, _ = data["region"].(string) + city, _ = data["city"].(string) + isp, _ = data["isp"].(string) + + return map[string]interface{}{ + "ip": ip, + "country": country, + "province": province, + "city": city, + "isp": isp, + "latitude": 0, + "longitude": 0, + }, nil + } + } + } + + // 全部失败 + return map[string]interface{}{ + "ip": ip, + "country": "", + "province": "", + "city": "", + "isp": "", + "latitude": 0, + "longitude": 0, + }, nil +} + +// processIDCardInfo 身份证归属地查询 + 真伪校验 +func processIDCardInfo(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + idcard, _ := params["idcard"].(string) + if idcard == "" { + return nil, xerr.NewErrMsg("身份证号不能为空") + } + + idcard = strings.ToUpper(idcard) + + // 1. 长度校验 + if len(idcard) != 18 { + return map[string]interface{}{ + "idcard": idcard, + "valid": false, + "reason": "身份证号长度必须为18位", + }, nil + } + + // 2. 格式校验 + if matched, _ := regexp.MatchString(`^\d{17}[\dX]$`, idcard); !matched { + return map[string]interface{}{ + "idcard": idcard, + "valid": false, + "reason": "身份证号格式不正确", + }, nil + } + + // 3. 校验位验证 + weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2} + checksumMap := []byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'} + + sum := 0 + for i := 0; i < 17; i++ { + v, _ := strconv.Atoi(string(idcard[i])) + sum += v * weights[i] + } + + if checksumMap[sum%11] != idcard[17] { + return map[string]interface{}{ + "idcard": idcard, + "valid": false, + "reason": "身份证校验位错误", + }, nil + } + + // 4. 出生日期 + birthday := fmt.Sprintf("%s-%s-%s", + idcard[6:10], + idcard[10:12], + idcard[12:14], + ) + + // 5. 性别 + gender := "男" + if (int(idcard[16])-'0')%2 == 0 { + gender = "女" + } + + // 6. 年龄 + age := 0 + if t, err := time.Parse("2006-01-02", birthday); err == nil { + now := time.Now() + age = now.Year() - t.Year() + if now.YearDay() < t.YearDay() { + age-- + } + } + + // 7. 行政区划(内置常用省份,可按需扩展) + provinceMap := map[string]string{ + "11": "北京市", + "12": "天津市", + "13": "河北省", + "14": "山西省", + "15": "内蒙古自治区", + "21": "辽宁省", + "22": "吉林省", + "23": "黑龙江省", + "31": "上海市", + "32": "江苏省", + "33": "浙江省", + "34": "安徽省", + "35": "福建省", + "36": "江西省", + "37": "山东省", + "41": "河南省", + "42": "湖北省", + "43": "湖南省", + "44": "广东省", + "45": "广西壮族自治区", + "46": "海南省", + "50": "重庆市", + "51": "四川省", + "52": "贵州省", + "53": "云南省", + "54": "西藏自治区", + "61": "陕西省", + "62": "甘肃省", + "63": "青海省", + "64": "宁夏回族自治区", + "65": "新疆维吾尔自治区", + } + + province := provinceMap[idcard[0:2]] + city := "" + district := "" + + logx.WithContext(ctx).Infof("身份证查询: %s", idcard) + + return map[string]interface{}{ + "idcard": idcard, + "valid": true, + "reason": "", + "province": province, + "city": city, + "district": district, + "birthday": birthday, + "gender": gender, + "age": age, + }, nil +} + +// processPhoneLocation 手机号归属地查询 +func (s *ToolboxService) processPhoneLocation(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填手机号 + mobile, ok := params["mobile"].(string) + if !ok || mobile == "" { + return nil, xerr.NewErrMsg("mobile 手机号参数不能为空且必须为字符串") + } + reqParams["phone"] = mobile + + // 调用天行SDK 手机号归属地接口 + resp, err := s.tianxingjuheClient.Get("mobilelocal/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据手机号归属地API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("手机号归属地接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析手机号归属地数据失败") + } + + return map[string]interface{}{ + "id": mobile, + "mobile": mobile, + "areaid": resultMap["areaid"], + "areacode": resultMap["areacode"], + "postcode": resultMap["postcode"], + "proviceid": resultMap["proviceid"], + "mobilearea": resultMap["mobilearea"], + "mobiletype": resultMap["mobiletype"], + "mobileprovice": resultMap["mobileprovice"], + }, nil +} + +// processBeijingTime 获取当前北京时间 +func processBeijingTime(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + url := "http://124.222.204.22/api/time/getapi.php?id=88888888&key=88888888&type=20" + + resp, err := httpClient.Get(url) + if err != nil { + logx.WithContext(ctx).Errorf("请求北京时间接口失败: %v", err) + return nil, xerr.NewErrMsg("获取北京时间失败") + } + defer resp.Body.Close() + + var apiResult map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&apiResult); err != nil { + return nil, xerr.NewErrMsg("解析北京时间响应失败") + } + + // 接口级错误处理 + if code, ok := apiResult["code"].(float64); ok && code != 200 { + return nil, xerr.NewErrMsg(" 系统繁忙!请一分钟后再试") + } + + // 提取并转换数据 + y, _ := apiResult["y"].(string) + m, _ := apiResult["m"].(string) + d, _ := apiResult["d"].(string) + h, _ := apiResult["h"].(string) + i, _ := apiResult["i"].(string) + s, _ := apiResult["s"].(string) + sjc, _ := apiResult["sjc"].(float64) + wecn, _ := apiResult["wecn"].(string) + + // 构建北京时间字符串 + dateStr := fmt.Sprintf("%s-%s-%s", y, m, d) + timeStr := fmt.Sprintf("%s:%s:%s", h, i, s) + datetimeStr := fmt.Sprintf("%s %s", dateStr, timeStr) + weekdayStr := fmt.Sprintf("星期%s", wecn) + + // 转换返回格式为前端期望的格式 + result := map[string]interface{}{ + "datetime": datetimeStr, + "date": dateStr, + "time": timeStr, + "weekday": weekdayStr, + "timestamp": int64(sjc), + } + + logx.WithContext(ctx).Infof("获取北京时间成功: %s", datetimeStr) + + return result, nil +} + +// processBankCard 银行卡归属地查询 +func processBankCard(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + cardNo, _ := params["card_no"].(string) + if cardNo == "" { + return nil, xerr.NewErrMsg("银行卡号不能为空") + } + + cardNo = strings.ReplaceAll(cardNo, " ", "") + if matched, _ := regexp.MatchString(`^\d{16,19}$`, cardNo); !matched { + return nil, xerr.NewErrMsg("银行卡号格式不正确") + } + + // Luhn 校验 + sum, double := 0, false + for i := len(cardNo) - 1; i >= 0; i-- { + digit, _ := strconv.Atoi(string(cardNo[i])) + if double { + digit *= 2 + if digit > 9 { + digit -= 9 + } + } + sum += digit + double = !double + } + if sum%10 != 0 { + return map[string]interface{}{ + "card_no": cardNo, + "valid": false, + "reason": "校验位错误", + }, nil + } + + // 银行识别 + bank, prefix := "未知银行", cardNo[:6] + switch { + case strings.HasPrefix(prefix, "622848"): + bank = "中国农业银行" + case strings.HasPrefix(prefix, "622260"): + bank = "交通银行" + case strings.HasPrefix(prefix, "436742"): + bank = "中国建设银行" + case strings.HasPrefix(prefix, "622202"): + bank = "中国工商银行" + } + + // 卡种判断 + cardType := "借记卡" + if len(cardNo) == 16 { + cardType = "信用卡" + } + + return map[string]interface{}{ + "card_no": cardNo, + "valid": true, + "bank": bank, + "card_type": cardType, + }, nil +} + +// processPlateParse 车牌号解析 +func processPlateParse(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + plate, _ := params["plate"].(string) + if plate == "" { + return nil, xerr.NewErrMsg("车牌号不能为空") + } + + if matched, _ := regexp.MatchString(`^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤川青藏琼宁][A-Z][A-Z0-9]{4,5}[A-Z0-9挂学警港澳]$`, plate); !matched { + return nil, xerr.NewErrMsg("车牌号格式不正确") + } + + provinceCode := string([]rune(plate)[0]) + provinceMap := map[string]string{ + "京": "北京", "津": "天津", "沪": "上海", "渝": "重庆", + "冀": "河北", "豫": "河南", "云": "云南", "辽": "辽宁", + "黑": "黑龙江", "湘": "湖南", "皖": "安徽", "鲁": "山东", + "新": "新疆", "苏": "江苏", "浙": "浙江", "赣": "江西", + "鄂": "湖北", "桂": "广西", "甘": "甘肃", "晋": "山西", + "蒙": "内蒙古", "陕": "陕西", "吉": "吉林", "闽": "福建", + "贵": "贵州", "粤": "广东", "川": "四川", "青": "青海", + "藏": "西藏", "琼": "海南", "宁": "宁夏", + } + + province := provinceMap[provinceCode] + isNewEnergy := strings.ContainsAny(plate, "DF") + plateType := "普通小型车" + if isNewEnergy { + plateType = "新能源小型车" + } + + return map[string]interface{}{ + "plate": plate, + "plate_type": plateType, + "province": province, + "is_new_energy": isNewEnergy, + }, nil +} + +// processMoneyToChinese 金额转换为中文 +func (s *ToolboxService) processMoneyToChinese(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 金额(必填) + money, ok := params["money"].(string) + if !ok || money == "" { + return nil, xerr.NewErrMsg("金额不能为空") + } + reqParams["money"] = money + + // 可选:单位类型,默认rmb(人民币元) + // type: usd美元单位元、usd_1美元单位分、rmb人民币单位元[默认]、rmb_1人民币单位分 + if typ, ok := params["type"].(string); ok && typ != "" { + reqParams["type"] = typ + } + + // 调用天行API + resp, err := s.tianxingjuheClient.Get("cnmoney/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求金额转大写接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("金额转大写接口异常: %s", resp.Msg)) + } + + // 解析result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析金额转大写数据失败") + } + + return map[string]interface{}{ + "cnresult": result["cnresult"], // 中文大写金额 + "enresult": result["enresult"], // 英语大写金额 + "fnresult": result["fnresult"], // 西式三位分节法数字 + }, nil +} + +// processPasswordStrength 密码强度检测 +func processPasswordStrength(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + pwd, _ := params["password"].(string) + if pwd == "" { + return nil, xerr.NewErrMsg("密码不能为空") + } + + length := len(pwd) + hasUpper, hasLower, hasDigit, hasSpecial := false, false, false, false + + for _, r := range pwd { + switch { + case unicode.IsUpper(r): + hasUpper = true + case unicode.IsLower(r): + hasLower = true + case unicode.IsDigit(r): + hasDigit = true + case unicode.IsPunct(r) || unicode.IsSymbol(r): + hasSpecial = true + } + } + + score, tips := 0, []string{} + + if length >= 8 { + score += 25 + tips = append(tips, "长度足够") + } else { + tips = append(tips, "长度不足") + } + + if hasUpper && hasLower { + score += 25 + tips = append(tips, "包含大小写字母") + } else { + tips = append(tips, "建议包含大小写字母") + } + + if hasDigit { + score += 25 + tips = append(tips, "包含数字") + } else { + tips = append(tips, "建议包含数字") + } + + if hasSpecial { + score += 25 + tips = append(tips, "包含特殊字符") + } else { + tips = append(tips, "建议包含特殊字符") + } + + level := "弱" + if score >= 75 { + level = "强" + } else if score >= 50 { + level = "中" + } + + return map[string]interface{}{ + "score": score, + "level": level, + "tips": tips, + }, nil +} + +// processDaysBetweenDates 两个日期相差天数 +func processDaysBetweenDates(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + startDate, _ := params["start_date"].(string) + endDate, _ := params["end_date"].(string) + + if startDate == "" || endDate == "" { + return nil, xerr.NewErrMsg("起止日期不能为空") + } + + layout := "2006-01-02" + start, err1 := time.Parse(layout, startDate) + end, err2 := time.Parse(layout, endDate) + + if err1 != nil || err2 != nil { + return nil, xerr.NewErrMsg("日期格式不正确,应为 YYYY-MM-DD") + } + + if start.After(end) { + start, end = end, start + } + + days := int(end.Sub(start).Hours() / 24) + + return map[string]interface{}{ + "start_date": startDate, + "end_date": endDate, + "days": days, + }, nil +} + +// 文件大小格式化 - 支持相互转换 +func processFileSizeFormat(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 获取输入参数 + sizeFloat, ok := params["size"].(float64) + if !ok || sizeFloat < 0 { + return nil, xerr.NewErrMsg("size 必须为非负数字") + } + + inputUnit, _ := params["input_unit"].(string) + outputUnit, _ := params["output_unit"].(string) + + // 如果没有指定输入单位,默认为字节 + if inputUnit == "" { + inputUnit = "B" + } + + // 如果没有指定输出单位,自动选择合适的单位 + if outputUnit == "" { + outputUnit = "auto" + } + + // 单位映射 + unitMap := map[string]float64{ + "B": 1, + "KB": 1024, + "MB": 1024 * 1024, + "GB": 1024 * 1024 * 1024, + "TB": 1024 * 1024 * 1024 * 1024, + } + + // 转换为字节 + bytes := sizeFloat * unitMap[inputUnit] + if bytes < 0 { + return nil, xerr.NewErrMsg("转换结果不能为负数") + } + + result := map[string]interface{}{ + "input_size": sizeFloat, + "input_unit": inputUnit, + "bytes": bytes, + } + + // 如果输出单位是 "auto",自动选择合适的单位 + if outputUnit == "auto" { + units := []string{"B", "KB", "MB", "GB", "TB"} + i := 0 + value := bytes + + for value >= 1024 && i < len(units)-1 { + value /= 1024 + i++ + } + + result["output_size"] = value + result["output_unit"] = units[i] + result["human"] = fmt.Sprintf("%.2f %s", value, units[i]) + } else { + // 指定单位转换 + if _, exists := unitMap[outputUnit]; !exists { + return nil, xerr.NewErrMsg(fmt.Sprintf("不支持的输出单位: %s", outputUnit)) + } + + outputSize := bytes / unitMap[outputUnit] + result["output_size"] = outputSize + result["output_unit"] = outputUnit + result["human"] = fmt.Sprintf("%.2f %s", outputSize, outputUnit) + } + + return result, nil +} + +// processHistoryToday 历史今天查询 +// processHistoryToday 历史今天查询(默认今天,3天缓存) +func (s *ToolboxService) processHistoryToday(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + dateStr, _ := params["date"].(string) + if dateStr == "" { + // 默认取今天的 MMDD + now := time.Now() + dateStr = fmt.Sprintf("%02d%02d", now.Month(), now.Day()) + } + + // 验证日期格式 (MMDD) + if len(dateStr) != 4 { + return nil, xerr.NewErrMsg("date 参数格式不正确,应为 MMDD 格式(如 0101)") + } + + month := dateStr[:2] + day := dateStr[2:4] + + monthInt, err := strconv.Atoi(month) + if err != nil || monthInt < 1 || monthInt > 12 { + return nil, xerr.NewErrMsg("月份参数不正确,应为 01-12") + } + + dayInt, err := strconv.Atoi(day) + if err != nil || dayInt < 1 || dayInt > 31 { + return nil, xerr.NewErrMsg("日期参数不正确,应为 01-31") + } + + // 查缓存 + if cached, ok := s.historyTodayCache.Load(dateStr); ok { + item := cached.(historyTodayCacheItem) + if time.Now().Before(item.ExpiredAt) { + logx.WithContext(ctx).Infof("[历史今天] 缓存命中: date=%s, 过期时间=%s", dateStr, item.ExpiredAt.Format("2006-01-02 15:04:05")) + return item.Data, nil + } + // 缓存过期,删除 + s.historyTodayCache.Delete(dateStr) + logx.WithContext(ctx).Infof("[历史今天] 缓存已过期,已删除: date=%s", dateStr) + } + + // 调用天行聚合 SDK + resp, err := s.tianxingjuheClient.Get("lishi/index", map[string]interface{}{ + "date": dateStr, + }) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析历史今天数据失败") + } + + data := map[string]interface{}{ + "list": resultMap["list"], + } + + // 写入缓存,3天后过期 + item := historyTodayCacheItem{ + Data: data, + ExpiredAt: time.Now().Add(3 * 24 * time.Hour), + } + s.historyTodayCache.Store(dateStr, item) + + logx.WithContext(ctx).Infof("[历史今天] API调用成功并写入缓存: date=%s, 过期时间=%s", dateStr, item.ExpiredAt.Format("2006-01-02 15:04:05")) + + return data, nil +} + +// processTenWhy 十万个为什么查询 +func (s *ToolboxService) processTenWhy(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 构建请求参数 + reqParams := map[string]interface{}{} + + if num, ok := params["num"].(float64); ok { + numInt := int(num) + if numInt < 1 || numInt > 10 { + return nil, xerr.NewErrMsg("num 参数范围为 1-10") + } + reqParams["num"] = numInt + } + + if page, ok := params["page"].(float64); ok { + pageInt := int(page) + if pageInt < 1 { + return nil, xerr.NewErrMsg("page 参数不能小于 1") + } + reqParams["page"] = pageInt + } + + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + if typeid, ok := params["typeid"].(float64); ok { + typeidInt := int(typeid) + if typeidInt < 1 || typeidInt > 11 { + return nil, xerr.NewErrMsg("typeid 参数范围为 1-11") + } + reqParams["typeid"] = typeidInt + } + + // 调用天行聚合 SDK + resp, err := s.tianxingjuheClient.Get("tenwhy/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据API失败: %v", err)) + } + + // 接口级错误处理 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("接口返回异常: %s", resp.Msg)) + } + + // 提取 result 数据 + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析十万个为什么数据失败") + } + + return map[string]interface{}{ + "list": resultMap["list"], + }, nil +} + +// 文本字数统计 +func processTextStats(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + text, _ := params["text"].(string) + if text == "" { + return map[string]interface{}{ + "chars": 0, + "words": 0, + "lines": 0, + }, nil + } + + chars := len([]rune(text)) + + words := 0 + for _, w := range strings.Fields(text) { + if strings.TrimSpace(w) != "" { + words++ + } + } + + lines := 1 + for _, r := range text { + if r == '\n' { + lines++ + } + } + + return map[string]interface{}{ + "chars": chars, + "words": words, + "lines": lines, + }, nil +} + +// processJFWords 近义词反义词查询(适合批量替换) +func (s *ToolboxService) processJFWords(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 构建请求参数 + reqParams := map[string]interface{}{} + + // 校验并获取必填参数 word(查询词) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("word 参数不能为空且必须为字符串类型") + } + reqParams["word"] = word + + // 调用天行聚合 SDK(近义词反义词接口:jfwords/index) + resp, err := s.tianxingjuheClient.Get("jfwords/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据近义词反义词API失败: %v", err)) + } + + // 接口级错误处理 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("接口返回异常: %s", resp.Msg)) + } + + // 提取 result 数据 + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析近义词反义词数据失败") + } + + // 返回近义词、反义词、查询词(字段可能为空,调用方需做非空判断) + return map[string]interface{}{ + "word": resultMap["word"], // 查询词 + "jyc": resultMap["jyc"], // 近义词(可能为空) + "fyc": resultMap["fyc"], // 反义词(可能为空) + }, nil +} + +// processDuilian 经典中华文化对联句子查询 +func (s *ToolboxService) processDuilian(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 本接口无额外业务请求参数,只需SDK内部携带key + reqParams := map[string]interface{}{} + + // 调用天行聚合 SDK 经典对联接口 + resp, err := s.tianxingjuheClient.Get("duilian/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据对联API失败: %v", err)) + } + + // 接口级错误处理 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("接口返回异常: %s", resp.Msg)) + } + + // 解析result数据 + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析经典对联数据失败") + } + + // 返回对联内容,业务端自行做非空判断 + return map[string]interface{}{ + "content": resultMap["content"], + }, nil +} + +// processGjDm 古今地名查询 +func (s *ToolboxService) processGjDm(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 构建请求参数 + reqParams := map[string]interface{}{} + + // 校验必填古地名 word + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("word 古地名参数不能为空且必须为字符串") + } + reqParams["word"] = word + + // 调用天行聚合 SDK 古今地名接口 + resp, err := s.tianxingjuheClient.Get("gjdm/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据古今地名API失败: %v", err)) + } + + // 接口业务状态码校验 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("古今地名接口返回异常: %s", resp.Msg)) + } + + // 解析result数据 + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析古今地名数据失败") + } + + // 返回古地名、沿革介绍,调用方自行做非空判断 + return map[string]interface{}{ + "area": resultMap["area"], + "introduce": resultMap["introduce"], + }, nil +} + +// processBcgm 本草纲目药材查询 +func (s *ToolboxService) processBcgm(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 构建请求参数 + reqParams := map[string]interface{}{} + + // 必传药材名称 name + name, ok := params["name"].(string) + if !ok || name == "" { + return nil, xerr.NewErrMsg("name 药材名称参数不能为空且必须为字符串") + } + reqParams["name"] = name + + // 可选 num 数量 1-10 + if num, ok := params["num"].(float64); ok { + numInt := int(num) + if numInt < 1 || numInt > 10 { + return nil, xerr.NewErrMsg("num 参数范围为 1-10") + } + reqParams["num"] = numInt + } + + // 可选 page 页码 最小1 + if page, ok := params["page"].(float64); ok { + pageInt := int(page) + if pageInt < 1 { + return nil, xerr.NewErrMsg("page 参数不能小于 1") + } + reqParams["page"] = pageInt + } + + // 调用天行SDK 本草纲目接口 + resp, err := s.tianxingjuheClient.Get("bcgm/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据本草纲目API失败: %v", err)) + } + + // 接口状态码校验 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("本草纲目接口返回异常: %s", resp.Msg)) + } + + // 解析result + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析本草纲目数据失败") + } + + // 返回列表 + return map[string]interface{}{ + "list": resultMap["list"], + }, nil +} + +// processIdiomQuiz 成语填字题目查询 +func (s *ToolboxService) processIdiomQuiz(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 接口无额外自定义参数,仅内置key即可请求 + reqParams := map[string]interface{}{} + + // 调用天行聚合SDK 成语填字接口 + resp, err := s.tianxingjuheClient.Get("idiomquiz/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据成语填字API失败: %v", err)) + } + + // 接口业务状态码校验 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("成语填字接口返回异常: %s", resp.Msg)) + } + + // 解析result数据 + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析成语填字数据失败") + } + + // 返回所有题目、答案、选项、解释字段 + return map[string]interface{}{ + "diff": resultMap["diff"], + "full": resultMap["full"], + "explan": resultMap["explan"], + "correct": resultMap["correct"], + "wrong_a": resultMap["wrong_a"], + "wrong_b": resultMap["wrong_b"], + "wrong_c": resultMap["wrong_c"], + "question": resultMap["question"], + }, nil +} + +// processPoemFill 诗词填空题目查询 +func (s *ToolboxService) processPoemFill(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行聚合SDK 诗词填空接口 + resp, err := s.tianxingjuheClient.Get("poemfill/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据诗词填空API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("诗词填空接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析诗词填空数据失败") + } + + return map[string]interface{}{ + "diff": resultMap["diff"], + "full": resultMap["full"], + "note": resultMap["note"], + "title": resultMap["title"], + "author": resultMap["author"], + "correct": resultMap["correct"], + "dynasty": resultMap["dynasty"], + "wrong_a": resultMap["wrong_a"], + "wrong_b": resultMap["wrong_b"], + "wrong_c": resultMap["wrong_c"], + "category": resultMap["category"], + "question": resultMap["question"], + }, nil +} + +// processFairytales 童话故事详情查询 +func (s *ToolboxService) processFairytales(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填故事ID + id, ok := params["id"].(string) + if !ok || id == "" { + return nil, xerr.NewErrMsg("id 故事ID参数不能为空且必须为字符串") + } + reqParams["id"] = id + + // 调用天行SDK 童话故事接口 + resp, err := s.tianxingjuheClient.Get("fairytales/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据童话故事API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("童话故事接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析童话故事数据失败") + } + + return map[string]interface{}{ + "id": resultMap["id"], + "title": resultMap["title"], + "typeid": resultMap["typeid"], + "content": resultMap["content"], + }, nil +} + +// processCaiHongPi 随机彩虹屁语录 +func (s *ToolboxService) processCaiHongPi(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行聚合SDK 彩虹屁接口 + resp, err := s.tianxingjuheClient.Get("caihongpi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据彩虹屁API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("彩虹屁接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析彩虹屁数据失败") + } + + return map[string]interface{}{ + "content": resultMap["content"], + }, nil +} + +// processJixiong 吉凶测试查询 +func (s *ToolboxService) processJixiong(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填数字号码 + number, ok := params["number"].(string) + if !ok || number == "" { + return nil, xerr.NewErrMsg("number 数字号码参数不能为空且必须为字符串") + } + reqParams["number"] = number + + // 调用天行SDK 吉凶测试接口 + resp, err := s.tianxingjuheClient.Get("jixiong/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据吉凶测试API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("吉凶测试接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析吉凶测试数据失败") + } + + return map[string]interface{}{ + "number": number, + "score": resultMap["score"], + "shuli": resultMap["shuli"], + "gaishu": resultMap["gaishu"], + "result": resultMap["result"], + "conclusion": resultMap["conclusion"], + }, nil +} + +// processSurname 姓氏起源查询 +func (s *ToolboxService) processSurname(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填姓氏参数 + xing, ok := params["xing"].(string) + if !ok || xing == "" { + return nil, xerr.NewErrMsg("xing 姓氏参数不能为空且必须为字符串") + } + reqParams["xing"] = xing + + // 调用天行SDK 姓氏起源接口 + resp, err := s.tianxingjuheClient.Get("surname/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据姓氏起源API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("姓氏起源接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析姓氏起源数据失败") + } + + return map[string]interface{}{ + "xing": resultMap["xing"], + "content": resultMap["content"], + }, nil +} + +// processJtwfcode 违章代码查询 +func (s *ToolboxService) processJtwfcode(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填违章代码 + code, ok := params["code"].(string) + if !ok || code == "" { + return nil, xerr.NewErrMsg("code 违章代码参数不能为空且必须为字符串") + } + reqParams["code"] = code + + // 调用天行SDK 违章代码查询接口 + resp, err := s.tianxingjuheClient.Get("jtwfcode/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据违章代码API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("违章代码接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析违章代码数据失败") + } + + return map[string]interface{}{ + "code": resultMap["code"], + "fine": resultMap["fine"], + "deduct": resultMap["deduct"], + "others": resultMap["others"], + "behavior": resultMap["behavior"], + }, nil +} + +// processHealthskill 健康小妙招查询 +func (s *ToolboxService) processHealthskill(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填关键词 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("word 关键词参数不能为空且必须为字符串") + } + reqParams["word"] = word + + // 调用天行SDK 健康小妙招接口 + resp, err := s.tianxingjuheClient.Get("healthskill/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据健康小妙招API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("健康小妙招接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析健康小妙招数据失败") + } + + list, ok := resultMap["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析健康小妙招列表失败") + } + + var skills []map[string]interface{} + for _, item := range list { + if m, ok := item.(map[string]interface{}); ok { + skills = append(skills, map[string]interface{}{ + "title": m["title"], + "content": m["content"], + }) + } + } + + return map[string]interface{}{ + "list": skills, + }, nil +} + +// processPyqwenan 朋友圈文案查询 +func (s *ToolboxService) processPyqwenan(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 无额外业务参数,直接调用天行接口 + resp, err := s.tianxingjuheClient.Get("pyqwenan/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据朋友圈文案API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("朋友圈文案接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析朋友圈文案数据失败") + } + + return map[string]interface{}{ + "source": resultMap["source"], + "content": resultMap["content"], + }, nil +} + +// processWomanNews 女性新闻查询 +func (s *ToolboxService) processWomanNews(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 可选参数:条数、页码、随机、关键词 + if num, ok := params["num"].(string); ok && num != "" { + reqParams["num"] = num + } + if page, ok := params["page"].(string); ok && page != "" { + reqParams["page"] = page + } + if rand, ok := params["rand"].(string); ok && rand != "" { + reqParams["rand"] = rand + } + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + // 固定传form=1 + reqParams["form"] = "1" + + // 调用天行SDK 女性新闻接口 + resp, err := s.tianxingjuheClient.Get("woman/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据女性新闻API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("女性新闻接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析女性新闻数据失败") + } + + // 解析新闻列表 + listArr, ok := resultMap["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析女性新闻列表失败") + } + + var newsList []map[string]interface{} + for _, item := range listArr { + if m, ok := item.(map[string]interface{}); ok { + newsList = append(newsList, map[string]interface{}{ + "id": m["id"], + "url": m["url"], + "ctime": m["ctime"], + "title": m["title"], + "picUrl": m["picUrl"], + "source": m["source"], + "description": m["description"], + }) + } + } + + return map[string]interface{}{ + "list": newsList, + "allnum": resultMap["allnum"], + "curpage": resultMap["curpage"], + }, nil +} + +// processDecide 常识判断题查询 +func (s *ToolboxService) processDecide(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 无额外业务参数,直接调用接口 + resp, err := s.tianxingjuheClient.Get("decide/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据判断题API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("判断题接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析判断题数据失败") + } + + return map[string]interface{}{ + "title": resultMap["title"], + "answer": resultMap["answer"], + "analyse": resultMap["analyse"], + }, nil +} + +// processEveryday 每日英语查询 +func (s *ToolboxService) processEveryday(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 无业务自定义参数,直接调用天行接口 + resp, err := s.tianxingjuheClient.Get("everyday/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据每日英语API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("每日英语接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析每日英语数据失败") + } + + return map[string]interface{}{ + "id": resultMap["id"], + "date": resultMap["date"], + "note": resultMap["note"], + "source": resultMap["source"], + "content": resultMap["content"], + }, nil +} + +// processAreaNews 地区新闻查询 +func (s *ToolboxService) processAreaNews(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填地区名称 + areaname, ok := params["areaname"].(string) + if !ok || areaname == "" { + return nil, xerr.NewErrMsg("areaname 地区名称参数不能为空且必须为字符串") + } + reqParams["areaname"] = areaname + + // 可选参数:页码(默认1) + page := "1" + if pageStr, ok := params["page"].(string); ok && pageStr != "" { + page = pageStr + } + reqParams["page"] = page + + // 可选参数:关键词 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 调用天行SDK 地区新闻接口 + resp, err := s.tianxingjuheClient.Get("areanews/index", reqParams) + if err != nil { + logx.WithContext(ctx).Errorf("[地区新闻] 请求失败: %v", err) + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据地区新闻API失败: %v", err)) + } + + if resp.Code != 200 { + logx.WithContext(ctx).Errorf("[地区新闻] API返回异常: code=%d, msg=%s", resp.Code, resp.Msg) + return nil, xerr.NewErrMsg(fmt.Sprintf("地区新闻接口返回异常: %s", resp.Msg)) + } + + logx.WithContext(ctx).Infof("[地区新闻] API响应: %+v", resp.Result) + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + logx.WithContext(ctx).Errorf("[地区新闻] 解析result失败,类型: %T", resp.Result) + return nil, xerr.NewErrMsg("解析地区新闻数据失败") + } + + // 解析新闻列表 + listArr, ok := resultMap["list"].([]interface{}) + if !ok { + logx.WithContext(ctx).Errorf("[地区新闻] 解析list失败,result结构: %+v", resultMap) + return nil, xerr.NewErrMsg("解析地区新闻列表失败") + } + + logx.WithContext(ctx).Infof("[地区新闻] 列表长度: %d", len(listArr)) + + // 字段白名单 + allowed := map[string]bool{ + "id": true, + "url": true, + "ctime": true, + "title": true, + "picUrl": true, + "source": true, + "description": true, + } + + var newsList []map[string]interface{} + for _, item := range listArr { + if m, ok := item.(map[string]interface{}); ok { + clean := map[string]interface{}{} + for k, v := range m { + if allowed[k] { + // 清理HTML标签(title和description) + if k == "title" || k == "description" { + if str, ok := v.(string); ok { + clean[k] = cleanHTMLContent(str) + } + } else { + clean[k] = v + } + } + } + // 至少要有标题(允许清理后为空,但原始必须有值) + if m["title"] != nil { + newsList = append(newsList, clean) + } + } + } + + logx.WithContext(ctx).Infof("[地区新闻] 清理后列表长度: %d", len(newsList)) + + // 如果清理后列表为空,但原始有数据,可能是HTML清理导致的问题 + if len(newsList) == 0 && len(listArr) > 0 { + // logx.WithContext(ctx).Warnf("[地区新闻] 清理后列表为空,原始有%d条数据", len(listArr)) + // 返回部分数据,不清理HTML + for _, item := range listArr { + if m, ok := item.(map[string]interface{}); ok { + clean := map[string]interface{}{} + for k, v := range m { + if allowed[k] { + clean[k] = v + } + } + if m["title"] != nil { + newsList = append(newsList, clean) + } + } + } + } + + if len(newsList) == 0 { + return nil, xerr.NewErrMsg("未查询到该地区新闻") + } + + return map[string]interface{}{ + "count": len(newsList), + "list": newsList, + }, nil +} + +// processYuanqu 元曲三百首查询 +func (s *ToolboxService) processYuanqu(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 必填参数 数量、页码 + num, ok := params["num"].(string) + if !ok || num == "" { + return nil, xerr.NewErrMsg("num 返回数量参数不能为空且必须为字符串") + } + reqParams["num"] = num + + page, ok := params["page"].(string) + if !ok || page == "" { + return nil, xerr.NewErrMsg("page 页码参数不能为空且必须为字符串") + } + reqParams["page"] = page + + // 可选 关键词搜索 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 调用天行SDK 元曲三百首接口 + resp, err := s.tianxingjuheClient.Get("yuanqu/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据元曲三百首API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("元曲三百首接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析元曲三百首数据失败") + } + + listArr, ok := resultMap["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析元曲三百首列表失败") + } + + var yuanquList []map[string]interface{} + for _, item := range listArr { + if m, ok := item.(map[string]interface{}); ok { + yuanquList = append(yuanquList, map[string]interface{}{ + "note": m["note"], + "title": m["title"], + "author": m["author"], + "chapter": m["chapter"], + "content": m["content"], + "translation": m["translation"], + }) + } + } + + return map[string]interface{}{ + "list": yuanquList, + }, nil +} + +// processBMI BMI标准体重计算 +func (s *ToolboxService) processBMI(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填身高 + height, ok := params["height"].(string) + if !ok || height == "" { + return nil, xerr.NewErrMsg("height 身高参数不能为空且必须为字符串") + } + reqParams["height"] = height + + // 校验必填体重 + weight, ok := params["weight"].(string) + if !ok || weight == "" { + return nil, xerr.NewErrMsg("weight 体重参数不能为空且必须为字符串") + } + reqParams["weight"] = weight + + // 可选性别 + if sex, ok := params["sex"].(string); ok && sex != "" { + reqParams["sex"] = sex + } + + // 调用天行SDK BMI接口 + resp, err := s.tianxingjuheClient.Get("bmi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据BMI标准体重API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("BMI标准体重接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析BMI标准体重数据失败") + } + + return map[string]interface{}{ + "bmi": resultMap["bmi"], + "tip": resultMap["tip"], + "healthy": resultMap["healthy"], + "normbmi": resultMap["normbmi"], + "normweight": resultMap["normweight"], + "idealweight": resultMap["idealweight"], + }, nil +} + +// processZhongyao 中药大全查询 +func (s *ToolboxService) processZhongyao(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填中药名称 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("word 中草药名称参数不能为空且必须为字符串") + } + reqParams["word"] = word + + // 可选返回数量(默认1) + num := 1 + if numStr, ok := params["num"].(string); ok && numStr != "" { + if n, err := strconv.Atoi(numStr); err == nil && n > 0 { + num = n + } + } + reqParams["num"] = num + + // 可选页码(默认1) + page := 1 + if pageStr, ok := params["page"].(string); ok && pageStr != "" { + if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { + page = p + } + } + reqParams["page"] = page + + // 调用天行SDK 中药大全接口 + resp, err := s.tianxingjuheClient.Get("zhongyao/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据中药大全API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("中药大全接口返回异常: %s", resp.Msg)) + } + + logx.WithContext(ctx).Infof("[中药大全] API响应: %+v", resp.Result) + + // 解析 result - 支持多种格式 + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析中药大全数据失败:返回格式不是对象") + } + + // 检查是否是列表格式(有些天行API返回 { list: [...] }) + if listData, hasList := resultMap["list"].([]interface{}); hasList && len(listData) > 0 { + // 取第一条记录 + if firstItem, ok := listData[0].(map[string]interface{}); ok { + resultMap = firstItem + } + } + + // 检查是否有数据 + if resultMap["title"] == nil && resultMap["content"] == nil { + return nil, xerr.NewErrMsg("未查询到该中药信息") + } + + // 清理HTML标签 + title, _ := resultMap["title"].(string) + content, _ := resultMap["content"].(string) + + // 如果是字符串类型,清理HTML标签 + if title != "" { + title = cleanHTMLContent(title) + } + if content != "" { + content = cleanHTMLContent(content) + } + + return map[string]interface{}{ + "title": title, + "content": content, + }, nil +} + +// processYaopin 药品说明书查询 +func (s *ToolboxService) processYaopin(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 校验必填药品名称 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("word 药品名称参数不能为空且必须为字符串") + } + reqParams["word"] = word + + // 可选参数:数量(默认10) + num := 10 + if numStr, ok := params["num"].(string); ok && numStr != "" { + if n, err := strconv.Atoi(numStr); err == nil && n > 0 { + num = n + } + } + reqParams["num"] = num + + // 可选参数:页码(默认1) + page := 1 + if pageStr, ok := params["page"].(string); ok && pageStr != "" { + if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { + page = p + } + } + reqParams["page"] = page + + // 调用天行SDK 药品说明书接口 + resp, err := s.tianxingjuheClient.Get("yaopin/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据药品说明书API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("药品说明书接口返回异常: %s", resp.Msg)) + } + + logx.WithContext(ctx).Infof("[药品说明书] API响应: %+v", resp.Result) + + // 解析 result - 支持多种格式 + var list []map[string]interface{} + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析药品说明书数据失败:返回格式不是对象") + } + + // 情况1:返回格式为 { list: [...] } + if listData, hasList := resultMap["list"].([]interface{}); hasList { + for _, item := range listData { + if m, ok := item.(map[string]interface{}); ok { + // 字段白名单 + 清理HTML标签 + clean := map[string]interface{}{} + if v, ok := m["title"].(string); ok { + clean["title"] = cleanHTMLContent(v) + } + if v, ok := m["content"].(string); ok { + clean["content"] = cleanHTMLContent(v) + } + if clean["title"] != nil { + list = append(list, clean) + } + } + } + } else if resultMap["title"] != nil { + // 情况2:返回格式为 { title: "...", content: "..." }(单条记录) + title, _ := resultMap["title"].(string) + content, _ := resultMap["content"].(string) + + clean := map[string]interface{}{ + "title": cleanHTMLContent(title), + "content": cleanHTMLContent(content), + } + list = append(list, clean) + } + + if len(list) == 0 { + return nil, xerr.NewErrMsg("未查询到该药品说明书") + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processSlogan 猜广告语查询 +func (s *ToolboxService) processSlogan(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 猜广告语接口 + resp, err := s.tianxingjuheClient.Get("slogan/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据猜广告语API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("猜广告语接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析猜广告语数据失败") + } + + return map[string]interface{}{ + "answer": resultMap["answer"], + "content": resultMap["content"], + }, nil +} + +// processZimi 猜字谜查询 +func (s *ToolboxService) processZimi(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 猜字谜接口 + resp, err := s.tianxingjuheClient.Get("zimi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据猜字谜API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("猜字谜接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析猜字谜数据失败") + } + + return map[string]interface{}{ + "answer": resultMap["answer"], + "reason": resultMap["reason"], + "content": resultMap["content"], + }, nil +} + +// processDujitang 毒鸡汤语录查询 +func (s *ToolboxService) processDujitang(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 毒鸡汤接口 + resp, err := s.tianxingjuheClient.Get("dujitang/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据毒鸡汤API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("毒鸡汤接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析毒鸡汤数据失败") + } + + return map[string]interface{}{ + "content": resultMap["content"], + }, nil +} + +// processOne ONE一个每日句子 +func (s *ToolboxService) processOne(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 可选指定日期 + if date, ok := params["date"].(string); ok && date != "" { + reqParams["date"] = date + } + // 可选是否随机 + if rand, ok := params["rand"].(string); ok && rand != "" { + reqParams["rand"] = rand + } + + // 调用天行SDK ONE一个接口 + resp, err := s.tianxingjuheClient.Get("one/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据ONE一个API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("ONE一个接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析ONE一个数据失败") + } + + return map[string]interface{}{ + "date": resultMap["date"], + "word": resultMap["word"], + "oneid": resultMap["oneid"], + "imgurl": resultMap["imgurl"], + "wordfrom": resultMap["wordfrom"], + "imgauthor": resultMap["imgauthor"], + }, nil +} + +// processProverb 文化谚语查询 +func (s *ToolboxService) processProverb(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 文化谚语接口 + resp, err := s.tianxingjuheClient.Get("proverb/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据文化谚语API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("文化谚语接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析文化谚语数据失败") + } + + return map[string]interface{}{ + "front": resultMap["front"], + "behind": resultMap["behind"], + }, nil +} + +// processCaichengyu 猜成语查询 +func (s *ToolboxService) processCaichengyu(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 猜成语接口 + resp, err := s.tianxingjuheClient.Get("caichengyu/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据猜成语API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("猜成语接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析猜成语数据失败") + } + + return map[string]interface{}{ + "abbr": resultMap["abbr"], + "study": resultMap["study"], + "answer": resultMap["answer"], + "pinyin": resultMap["pinyin"], + "source": resultMap["source"], + "question": resultMap["question"], + }, nil +} + +// processQiaomen 生活小窍门查询 +func (s *ToolboxService) processQiaomen(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 生活小窍门接口 + resp, err := s.tianxingjuheClient.Get("qiaomen/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据生活小窍门API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("生活小窍门接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析生活小窍门数据失败") + } + + return map[string]interface{}{ + "content": resultMap["content"], + }, nil +} + +// processRiddle 谜语大全查询 +func (s *ToolboxService) processRiddle(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 调用天行SDK 谜语大全接口 + resp, err := s.tianxingjuheClient.Get("riddle/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据谜语大全API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("谜语大全接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析谜语大全数据失败") + } + + return map[string]interface{}{ + "quest": resultMap["quest"], + "result": resultMap["answer"], + }, nil +} + +// processHealthTip 健康小提示 +func (s *ToolboxService) processHealthTip(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 1. 请求参数(该接口无需额外参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("healthtip/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天行数据健康小提示API失败: %v", err)) + } + + // 3. 校验返回状态 + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("健康小提示接口返回异常: %s", resp.Msg)) + } + + // 4. 解析 result + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析健康小提示数据失败") + } + + // 5. 返回给上层 + return map[string]interface{}{ + "content": resultMap["content"], + }, nil +} + +// processNutrient 查询食物营养成分 +func (s *ToolboxService) processNutrient( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数提取(必填) + var mode float64 + switch v := params["mode"].(type) { + case float64: + mode = v + case string: + if v == "" { + return nil, xerr.NewErrMsg("缺少必填参数 mode") + } + modeInt, err := strconv.Atoi(v) + if err != nil { + return nil, xerr.NewErrMsg("mode 参数必须是数字 0/1/2") + } + mode = float64(modeInt) + default: + return nil, xerr.NewErrMsg("缺少必填参数 mode") + } + + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("缺少必填参数 word") + } + + // 2. 可选参数 + num := 10 + if v, ok := params["num"].(float64); ok && v > 0 { + num = int(v) + } + + page := 1 + if v, ok := params["page"].(float64); ok && v > 0 { + page = int(v) + } + + // 3. 组装请求参数 + reqParams := map[string]interface{}{ + "mode": mode, + "word": word, + "num": num, + "page": page, + } + + // 4. 调用天行 API + resp, err := s.tianxingjuheClient.Get("nutrient/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求营养成分接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("营养成分接口异常: %s", resp.Msg)) + } + + // 5. 解析 result(天行 API 返回格式为 { list: [...] }) + var list []map[string]interface{} + + // 尝试从 result 中提取 list 字段 + switch v := resp.Result.(type) { + case map[string]interface{}: + // 天行 API 的标准格式:result.list 是数组 + if dataList, ok := v["list"].([]interface{}); ok { + for _, item := range dataList { + if m, ok := item.(map[string]interface{}); ok { + list = append(list, m) + } + } + } else { + // 兼容单个对象的情况 + list = append(list, v) + } + case []interface{}: + // 兼容直接返回数组的情况 + for _, item := range v { + if m, ok := item.(map[string]interface{}); ok { + list = append(list, m) + } + } + default: + return nil, xerr.NewErrMsg("解析营养成分数据失败") + } + + if len(list) == 0 { + return nil, xerr.NewErrMsg("未查询到相关数据") + } + + // 6. 返回标准化结果 + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processFxRate 汇率查询 +func (s *ToolboxService) processFxRate( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + var money float64 + switch v := params["money"].(type) { + case float64: + money = v + case string: + money, _ = strconv.ParseFloat(v, 64) + default: + return nil, xerr.NewErrMsg("参数 money 必须为大于 0 的数字") + } + if money <= 0 { + return nil, xerr.NewErrMsg("参数 money 必须为大于 0 的数字") + } + + fromCoin, ok := params["fromcoin"].(string) + if !ok || fromCoin == "" { + return nil, xerr.NewErrMsg("参数 fromcoin 不能为空") + } + + toCoin, ok := params["tocoin"].(string) + if !ok || toCoin == "" { + return nil, xerr.NewErrMsg("参数 tocoin 不能为空") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "money": int(money), + "fromcoin": fromCoin, + "tocoin": toCoin, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("fxrate/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求汇率接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("汇率接口异常: %s", resp.Msg)) + } + + // 4. 解析结果 + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析汇率数据失败") + } + + amount, ok := result["money"].(string) + if !ok || amount == "" { + return nil, xerr.NewErrMsg("汇率金额异常") + } + + // 5. 返回标准化结果 + return map[string]interface{}{ + "from": fromCoin, + "to": toCoin, + "amount": money, + "result": amount, + }, nil +} + +// processBulletin 每日简报 +func (s *ToolboxService) processBulletin( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(该接口无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("bulletin/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求每日简报接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("每日简报接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析每日简报数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("简报列表数据异常") + } + + // 5. 字段白名单过滤 + allowed := map[string]bool{ + "title": true, + "digest": true, + "mtime": true, + } + + news := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + // 最少保证有标题 + if clean["title"] != nil { + news = append(news, clean) + } + } + + // 6. 返回标准化结果 + return map[string]interface{}{ + "count": len(news), + "list": news, + }, nil +} + +// processCaizimi 猜灯谜 +func (s *ToolboxService) processCaizimi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("caizimi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求猜灯谜接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("猜灯谜接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析猜灯谜数据失败") + } + + // 4. 字段白名单(非常重要) + allowed := map[string]bool{ + "type": true, + "answer": true, + "riddle": true, + "disturb": true, + "description": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 5. 基础校验 + if clean["riddle"] == nil || clean["answer"] == nil { + return nil, xerr.NewErrMsg("灯谜数据不完整") + } + + return clean, nil +} + +// processHotLajifenlei 热搜垃圾分类 +func (s *ToolboxService) processHotLajifenlei( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:日期 + if date, ok := params["date"].(string); ok && date != "" { + reqParams["date"] = date + } + + // 可选:垃圾类型 + if typ, ok := params["type"].(float64); ok { + reqParams["type"] = int(typ) + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("hotlajifenlei/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求热搜垃圾分类接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("热搜垃圾分类接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析热搜垃圾分类数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("热搜列表数据异常") + } + + // 5. 字段白名单 + 枚举映射 + allowed := map[string]bool{ + "name": true, + "type": true, + "index": true, + } + + typeMap := map[int]string{ + 0: "可回收物", + 1: "有害垃圾", + 2: "厨余垃圾(湿)", + 3: "其他垃圾(干)", + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + // 垃圾类型可读化 + if t, ok := clean["type"].(float64); ok { + if name, exists := typeMap[int(t)]; exists { + clean["type_name"] = name + } + } + + list = append(list, clean) + } + + // 6. 返回标准化结果 + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processOilPrice 实时油价查询 +func (s *ToolboxService) processOilPrice( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + prov, ok := params["prov"].(string) + if !ok || prov == "" { + return nil, xerr.NewErrMsg("参数 prov 不能为空,例如:湖北、上海") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "prov": prov, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("oilprice/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求实时油价接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("实时油价接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析油价数据失败") + } + + // 5. 字段白名单 + 可读性增强 + allowed := map[string]bool{ + "p0": true, + "p89": true, + "p92": true, + "p95": true, + "p98": true, + "prov": true, + "time": true, + } + + labelMap := map[string]string{ + "p0": "0号柴油", + "p89": "89号汽油", + "p92": "92号汽油", + "p95": "95号汽油", + "p98": "98号汽油", + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 6. 生成前端友好的油价列表 + prices := make([]map[string]interface{}, 0) + for k, label := range labelMap { + if v, ok := clean[k].(string); ok && v != "" { + prices = append(prices, map[string]interface{}{ + "type": k, + "label": label, + "price": v, + }) + } + } + + // 7. 返回标准化结果 + return map[string]interface{}{ + "prov": clean["prov"], + "update": clean["time"], + "prices": prices, + }, nil +} + +// processChengyuJielong 成语接龙 +func (s *ToolboxService) processChengyuJielong( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("参数 word 不能为空,例如:一马当先") + } + + // 用户ID:如果没有提供,使用默认值(方便单人游戏) + userID, ok := params["userid"].(string) + if !ok || userID == "" { + userID = "1212" // 默认用户标识,单人游戏模式 + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "word": word, + "userid": userID, + } + + // 可选:状态维持时间 + if t, ok := params["statetime"].(float64); ok && t > 0 { + reqParams["statetime"] = int(t) + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("chengyujielong/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求成语接龙接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("成语接龙接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析成语接龙数据失败") + } + + // 5. 字段白名单(非常重要) + allowed := map[string]bool{ + "chengyu": true, + "pinyin": true, + "jieshi": true, + "chuchu": true, + "startstr": true, + "endstr": true, + "tip": true, + "grade": true, + "result": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 6. 游戏状态说明(给前端/AI用) + stateMap := map[int]string{ + 0: "开局", + 1: "答对", + 2: "开局失败", + 3: "接错成语", + 4: "不是成语", + 5: "胜利结束", + } + + if r, ok := clean["result"].(float64); ok { + clean["state"] = stateMap[int(r)] + clean["game_over"] = r >= 2 + } + + return clean, nil +} + +// processWeiboHot 微博热搜榜 +func (s *ToolboxService) processWeiboHot( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + resp, err := s.tianxingjuheClient.Get("weibohot/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求微博热搜接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("微博热搜接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析微博热搜数据失败") + } + + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("热搜列表数据异常") + } + + var hotList []map[string]interface{} + for _, item := range rawList { + info, _ := item.(map[string]interface{}) + hotList = append(hotList, map[string]interface{}{ + "keyword": info["hotword"], // 搜索词 + "brief": info["hottag"], // 简介 + "index": info["hotwordnum"], // 热度 + }) + } + + return map[string]interface{}{ + "count": len(hotList), + "list": hotList, + }, nil +} + +// processDomainParse 域名DNS解析查询 +func (s *ToolboxService) processDomainParse( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + domain, ok := params["domain"].(string) + if !ok || domain == "" { + return nil, xerr.NewErrMsg("参数 domain 不能为空,例如:tianyuanapi.com") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "domain": domain, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("domain/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求域名解析接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("域名解析接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析域名解析数据失败") + } + + // 5. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析解析记录列表失败") + } + + // 6. 不做字段白名单(DNS 字段不固定) + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + if m, ok := item.(map[string]interface{}); ok { + list = append(list, m) + } + } + + // 7. 返回标准化结果 + return map[string]interface{}{ + "domain": domain, + "count": len(list), + "list": list, + }, nil +} + +// processScenic 旅游景区查询 +func (s *ToolboxService) processScenic( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 至少一个检索参数 + word, _ := params["word"].(string) + city, _ := params["city"].(string) + province, _ := params["province"].(string) + + if word == "" && city == "" && province == "" { + return nil, xerr.NewErrMsg("word / city / province 至少填写一个") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{} + if word != "" { + reqParams["word"] = word + } + if city != "" { + reqParams["city"] = city + } + if province != "" { + reqParams["province"] = province + } + + // 可选:分页 + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + if page, ok := params["page"].(string); ok && page != "" { + reqParams["page"] = page + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("scenic/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求旅游景区接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("旅游景区接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析旅游景区数据失败") + } + + // 5. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("景区列表数据异常") + } + + // 6. 字段白名单 + allowed := map[string]bool{ + "name": true, + "province": true, + "city": true, + "content": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["name"] != nil { + list = append(list, clean) + } + } + + // 7. 返回标准化结果 + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processMingyan 名人名言查询 +func (s *ToolboxService) processMingyan( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:名言类型 + if tid, ok := params["typeid"].(float64); ok { + reqParams["typeid"] = int(tid) + } + + // 可选:返回数量 + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } else { + reqParams["num"] = 1 + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("mingyan/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求名人名言接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("名人名言接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析名人名言数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("名言列表数据异常") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "content": true, + "author": true, + "typeid": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["content"] != nil { + list = append(list, clean) + } + } + + // 6. 返回标准化结果 + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processTianqiShiju 天气诗句查询 +func (s *ToolboxService) processTianqiShiju( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:天气类型 + if tqtype, ok := params["tqtype"].(float64); ok { + reqParams["tqtype"] = int(tqtype) + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("tianqishiju/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求天气诗句接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("天气诗句接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析天气诗句数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "content": true, + "author": true, + "source": true, + "tqtype": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 5. 天气类型可读化 + tqTypeMap := map[int]string{ + 1: "风", 2: "云", 3: "雨", 4: "雪", + 5: "霜", 6: "露", 7: "雾", 8: "雷", + 9: "晴", 10: "阴", + } + + if t, ok := clean["tqtype"].(float64); ok { + if name, exists := tqTypeMap[int(t)]; exists { + clean["weather"] = name + } + } + + return clean, nil +} + +// processJieQi 二十四节气查询 +func (s *ToolboxService) processJieQi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + name, ok := params["word"].(string) + if !ok || name == "" { + return nil, xerr.NewErrMsg("参数 word 不能为空,例如:立春") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "word": name, + } + + // 可选:年份 + if year, ok := params["year"].(string); ok && year != "" { + reqParams["year"] = year + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("jieqi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求二十四节气接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("二十四节气接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析二十四节气数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "name": true, + "day": true, + "jieshao": true, + "yiji": true, + "shiju": true, + "xishu": true, + "meishi": true, + "yuanyin": true, + "nameimg": true, + "date": true, // 内嵌对象 + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 6. 公历 / 农历字段可读化(可选) + if date, ok := clean["date"].(map[string]interface{}); ok { + clean["greg_date"] = date["gregdate"] + clean["lunar_date"] = date["lunardate"] + clean["lunar_year"] = date["cnyear"] + clean["lunar_zodiac"] = date["cnzodiac"] + } + + return clean, nil +} + +// processBloodPair 血型配对查询(娱乐) +func (s *ToolboxService) processBloodPair( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + me, ok1 := params["me"].(string) + he, ok2 := params["he"].(string) + + if !ok1 || !ok2 || me == "" || he == "" { + return nil, xerr.NewErrMsg("参数 me 和 he 不能为空,例如:me=O&he=A") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "me": me, + "he": he, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("blood/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求血型配对接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("血型配对接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析血型配对数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "pair": true, + "title": true, + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 6. 增加娱乐声明(非常重要) + clean["notice"] = "本结果仅供娱乐,不代表科学结论" + + return clean, nil +} + +// processZodiacPair 生肖配对查询(娱乐) +func (s *ToolboxService) processZodiacPair( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + me, ok1 := params["me"].(string) + he, ok2 := params["he"].(string) + + if !ok1 || !ok2 || me == "" || he == "" { + return nil, xerr.NewErrMsg("参数 me 和 he 不能为空,例如:me=猴&he=羊") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "me": me, + "he": he, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("zodiac/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求生肖配对接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("生肖配对接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析生肖配对数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "title": true, + "fcontent": true, + "mcontent": true, + "fcontent1": true, + "mcontent1": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 6. 明确娱乐声明(非常重要) + clean["notice"] = "本结果仅供娱乐,不代表科学结论" + + return clean, nil +} + +// processSayLove 土味情话 +func (s *ToolboxService) processSayLove( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("saylove/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求土味情话接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("土味情话接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析土味情话数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 5. 兜底校验 + if clean["content"] == nil { + return nil, xerr.NewErrMsg("土味情话内容为空") + } + + return clean, nil +} + +// processStar 星座运势查询(娱乐) +func (s *ToolboxService) processStar( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + astro, ok := params["astro"].(string) + if !ok || astro == "" { + return nil, xerr.NewErrMsg("参数 astro 不能为空,例如:金牛座 或 taurus") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "astro": astro, + } + + // 可选:日期 + if date, ok := params["date"].(string); ok && date != "" { + reqParams["date"] = date + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("star/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求星座运势接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("星座运势接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析星座运势数据失败") + } + + // 5. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("星座运势列表数据异常") + } + + // 6. 字段白名单 + allowed := map[string]bool{ + "type": true, + "content": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["type"] != nil && clean["content"] != nil { + list = append(list, clean) + } + } + + // 7. 返回标准化结果 + return map[string]interface{}{ + "astro": astro, + "count": len(list), + "list": list, + }, nil +} + +// processPinyin 汉字转拼音 +func (s *ToolboxService) processPinyin( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + text, ok := params["text"].(string) + if !ok || text == "" { + return nil, xerr.NewErrMsg("参数 text 不能为空") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "text": text, + } + + // 可选:type + if typ, ok := params["type"].(float64); ok { + reqParams["type"] = int(typ) + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("pinyin/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求汉字转拼音接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("汉字转拼音接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析汉字转拼音数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "pinyin": true, + "jianxie": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processNetHot 百度热搜榜 +func (s *ToolboxService) processNetHot( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("nethot/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求百度热搜接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("百度热搜接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析百度热搜数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("热搜列表数据异常") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "keyword": true, + "brief": true, + "index": true, + "trend": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["keyword"] != nil { + list = append(list, clean) + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processEnSentence 英语一句话 +func (s *ToolboxService) processEnSentence( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("ensentence/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求英语一句话接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("英语一句话接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析英语一句话数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "en": true, + "zh": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processVerse 优美诗句查询 +func (s *ToolboxService) processVerse( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:返回数量 + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + + // 可选:翻页 + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 可选:搜索词 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("verse/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求优美诗句接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("优美诗句接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析优美诗句数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("诗句列表数据异常") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "author": true, + "source": true, + "content": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["content"] != nil { + list = append(list, clean) + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processPoetry 唐诗三百首查询 +func (s *ToolboxService) processPoetry( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:返回数量 + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + + // 可选:翻页 + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 可选:搜索词 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("poetry/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求唐诗三百首接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("唐诗三百首接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析唐诗三百首数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("唐诗列表数据异常") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "title": true, + "author": true, + "kind": true, + "intro": true, + "content": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["title"] != nil && clean["content"] != nil { + list = append(list, clean) + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processPoetries 唐诗大全查询 +func (s *ToolboxService) processPoetries( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 必填参数校验 + num, ok1 := params["num"].(float64) + page, ok2 := params["page"].(float64) + if !ok1 || !ok2 || num <= 0 || page <= 0 { + return nil, xerr.NewErrMsg("参数 num 和 page 为必填项") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "num": int(num), + "page": int(page), + } + + // 可选:随机 + if randVal, ok := params["rand"].(float64); ok { + reqParams["rand"] = int(randVal) + } + + // 可选:关键词 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 可选:作者 + if author, ok := params["author"].(string); ok && author != "" { + reqParams["author"] = author + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("poetries/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求唐诗大全接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("唐诗大全接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析唐诗大全数据失败") + } + + // 5. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("唐诗列表数据异常") + } + + // 6. 字段白名单 + allowed := map[string]bool{ + "title": true, + "author": true, + "content": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["title"] != nil && clean["content"] != nil { + list = append(list, clean) + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processPiTongLiShi 简说历史查询 +func (s *ToolboxService) processPiTongLiShi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("pitlishi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求简说历史接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("简说历史接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析简说历史数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processMnPara 小段子查询 +func (s *ToolboxService) processMnPara( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("mnpara/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求小段子接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("小段子接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析小段子数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "author": true, + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processSkl 顺口溜查询 +func (s *ToolboxService) processSkl( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("skl/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求顺口溜接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("顺口溜接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析顺口溜数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processSentence 精美句子查询 +func (s *ToolboxService) processSentence( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数(无需业务参数) + reqParams := map[string]interface{}{} + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("sentence/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求精美句子接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("精美句子接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析精美句子数据失败") + } + + // 4. 字段白名单 + allowed := map[string]bool{ + "author": true, + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processEnWords 英汉词典查询 +func (s *ToolboxService) processEnWords( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("参数 word 不能为空,例如:lexicon") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "word": word, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("enwords/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求英汉词典接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("英汉词典接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析英汉词典数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "word": true, + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processLexicon 中文词典查询 +func (s *ToolboxService) processLexicon( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("参数 word 不能为空,例如:作坊") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "word": word, + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("lexicon/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求中文词典接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("中文词典接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析中文词典数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "word": true, + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processLunar 中国老黄历查询 +func (s *ToolboxService) processLunar( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:日期 + if date, ok := params["date"].(string); ok && date != "" { + reqParams["date"] = date + } + + // 可选:按农历查询 + if typ, ok := params["type"].(float64); ok { + reqParams["type"] = int(typ) + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("lunar/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求老黄历接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("老黄历接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析老黄历数据失败") + } + + // 4. 字段白名单(只保留常用字段) + allowed := map[string]bool{ + "lunardate": true, + "gregoriandate": true, + "shengxiao": true, + "fitness": true, + "taboo": true, + "chongsha": true, + "shenwei": true, + "taishen": true, + "lunar_festival": true, + "festival": true, + "jieqi": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + return clean, nil +} + +// processXingZuo 星座配对查询(娱乐) +func (s *ToolboxService) processXingZuo( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 参数校验 + me, ok := params["me"].(string) + if !ok || me == "" { + return nil, xerr.NewErrMsg("参数 me 不能为空,例如:金牛") + } + + // 2. 请求参数 + reqParams := map[string]interface{}{ + "me": me, + } + + // 可选:双人模式 + if he, ok := params["he"].(string); ok && he != "" { + reqParams["he"] = he + } + + // 可选:全部配对 + if all, ok := params["all"].(float64); ok { + reqParams["all"] = int(all) + } + + // 3. 调用天行 API + resp, err := s.tianxingjuheClient.Get("xingzuo/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求星座配对接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("星座配对接口异常: %s", resp.Msg)) + } + + // 4. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析星座配对数据失败") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "title": true, + "grade": true, + "content": true, + } + + clean := map[string]interface{}{} + for k, v := range result { + if allowed[k] { + clean[k] = v + } + } + + // 6. 娱乐声明 + clean["notice"] = "本结果仅供娱乐,不代表科学结论" + + return clean, nil +} + +// processGodReply 神回复查询 +func (s *ToolboxService) processGodReply(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:返回数量 + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("godreply/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求神回复接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("神回复接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析神回复数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("神回复列表数据异常") + } + + // 5. 字段白名单 + allowed := map[string]bool{ + "title": true, + "content": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + if clean["content"] != nil { + list = append(list, clean) + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processXiehou 歇后语查询 +func (s *ToolboxService) processXiehou( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 1. 请求参数 + reqParams := map[string]interface{}{} + + // 可选:返回数量(1~10) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 10 { + reqParams["num"] = int(num) + } + + // 2. 调用天行 API + resp, err := s.tianxingjuheClient.Get("xiehou/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求歇后语接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("歇后语接口异常: %s", resp.Msg)) + } + + // 3. 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析歇后语数据失败") + } + + // 4. 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("歇后语列表数据异常") + } + + // 5. 字段白名单(只保留需要字段) + allowed := map[string]bool{ + "quest": true, // 谜面 + "result": true, // 谜底 + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + // 至少要有谜面和谜底 + if clean["quest"] != nil && clean["result"] != nil { + list = append(list, clean) + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processRKL 绕口令查询 +func (s *ToolboxService) processRKL( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 可选:返回数量 1~10 + if num, ok := params["num"].(float64); ok && num > 0 && num <= 10 { + reqParams["num"] = int(num) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("rkl/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求绕口令接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("绕口令接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析绕口令数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("绕口令列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + // 字段白名单 + clean := map[string]interface{}{} + if content, ok := raw["content"]; ok && content != nil { + // 去除HTML标签 + if contentStr, ok := content.(string); ok { + // 移除所有HTML标签 + re := regexp.MustCompile(`<[^>]+>`) + cleanContent := re.ReplaceAllString(contentStr, "") + // 替换

等换行标签为换行符 + cleanContent = regexp.MustCompile(`(?i)`).ReplaceAllString(cleanContent, "\n") + // 移除多余的空行 + cleanContent = regexp.MustCompile(`\n{3,}`).ReplaceAllString(cleanContent, "\n\n") + // 去除首尾空白 + cleanContent = strings.TrimSpace(cleanContent) + clean["content"] = cleanContent + list = append(list, clean) + } + } + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processCName 网络取名 +func (s *ToolboxService) processCName( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(1~50) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 50 { + reqParams["num"] = int(num) + } + + // 性别:1男 2女 3中性 + if sex, ok := params["sex"].(float64); ok && sex >= 1 && sex <= 3 { + reqParams["sex"] = int(sex) + } + + // 包含的字 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 名字字数(1~3) + if wordnum, ok := params["wordnum"].(float64); ok && wordnum >= 1 && wordnum <= 3 { + reqParams["wordnum"] = int(wordnum) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("cname/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求网络取名接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("网络取名接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析网络取名数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("网络取名列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if naming, ok := raw["naming"]; ok && naming != nil { + clean["naming"] = naming + } + if sex, ok := raw["sex"]; ok { + clean["sex"] = sex + } + if wordnum, ok := raw["wordnum"]; ok { + clean["wordnum"] = wordnum + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processShares 股市术语查询 +func (s *ToolboxService) processShares( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 搜索词(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("股市术语查询缺少必填参数:word") + } + reqParams["word"] = word + + // 返回数量(必填) + num, ok := params["num"].(float64) + if !ok || num <= 0 { + num = 5 // 给一个安全默认值 + } + reqParams["num"] = int(num) + + // 页码(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("shares/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求股市术语接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("股市术语接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析股市术语数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("股市术语列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if term, ok := raw["term"]; ok { + clean["term"] = term + } + if content, ok := raw["content"]; ok { + clean["content"] = content + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processHotWord 网络流行语查询 +func (s *ToolboxService) processHotWord( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 搜索词(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("网络流行语查询缺少必填参数:word") + } + reqParams["word"] = word + + // 返回数量(必填) + num, ok := params["num"].(float64) + if !ok || num <= 0 { + num = 5 // 给一个安全默认值 + } + reqParams["num"] = int(num) + + // 页码(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("hotword/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求网络流行语接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("网络流行语接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析网络流行语数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("网络流行语列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if title, ok := raw["title"]; ok { + clean["title"] = title + } + if content, ok := raw["content"]; ok { + clean["content"] = content + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processCityRiddle 地名谜语查询 +func (s *ToolboxService) processCityRiddle( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 地名谜语接口无需额外参数 + reqParams := map[string]interface{}{} + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("cityriddle/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求地名谜语接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("地名谜语接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析地名谜语数据失败") + } + + // 字段白名单 + clean := map[string]interface{}{} + if quest, ok := result["quest"]; ok { + clean["quest"] = quest + } + if answer, ok := result["result"]; ok { + clean["answer"] = answer + } + + return map[string]interface{}{ + "count": 1, + "list": []map[string]interface{}{clean}, + }, nil +} + +// processWenDa 一战到底知识竞答 +func (s *ToolboxService) processWenDa( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 一战到底接口无需额外参数 + reqParams := map[string]interface{}{} + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("wenda/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求一战到底接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("一战到底接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析一战到底数据失败") + } + + // 字段白名单 + clean := map[string]interface{}{} + if quest, ok := result["quest"]; ok { + clean["quest"] = quest + } + if answer, ok := result["result"]; ok { + clean["answer"] = answer + } + + return map[string]interface{}{ + "count": 1, + "list": []map[string]interface{}{clean}, + }, nil +} + +// processChengYu 成语典故查询 +func (s *ToolboxService) processChengYu( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 搜索词(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("成语典故查询缺少必填参数:word") + } + reqParams["word"] = word + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 50 { + reqParams["num"] = int(num) + } else if numStr, ok := params["num"].(string); ok && numStr != "" { + if numInt, err := strconv.Atoi(numStr); err == nil && numInt > 0 && numInt <= 50 { + reqParams["num"] = numInt + } + } + + // 搜索模式(可选) + if mode, ok := params["mode"].(float64); ok && mode >= 0 && mode <= 2 { + reqParams["mode"] = int(mode) + } else if modeStr, ok := params["mode"].(string); ok && modeStr != "" { + if modeInt, err := strconv.Atoi(modeStr); err == nil && modeInt >= 0 && modeInt <= 2 { + reqParams["mode"] = modeInt + } + } + + // 页码(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } else if pageStr, ok := params["page"].(string); ok && pageStr != "" { + if pageInt, err := strconv.Atoi(pageStr); err == nil && pageInt > 0 { + reqParams["page"] = pageInt + } + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("chengyu/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求成语典故接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("成语典故接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析成语典故数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("成语典故列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if chengyu, ok := raw["chengyu"]; ok { + clean["chengyu"] = chengyu + } + if pinyin, ok := raw["pinyin"]; ok { + clean["pinyin"] = pinyin + } + if diangu, ok := raw["diangu"]; ok { + clean["diangu"] = diangu + } + if chuchu, ok := raw["chuchu"]; ok { + clean["chuchu"] = chuchu + } + if fanli, ok := raw["fanli"]; ok { + clean["fanli"] = fanli + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processNaoWan 脑筋急转弯查询 +func (s *ToolboxService) processNaoWan( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(可选,默认5条) + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } else { + reqParams["num"] = 5 // 默认返回5条 + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("naowan/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求脑筋急转弯接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("脑筋急转弯接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析脑筋急转弯数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("脑筋急转弯列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if quest, ok := raw["quest"]; ok { + clean["quest"] = quest + } + if result, ok := raw["result"]; ok { + clean["result"] = result + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil + +} + +// processDOB 生日性格查询 +func (s *ToolboxService) processDOB( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 月份(必填)- 支持字符串和数字类型 + var m int + switch v := params["m"].(type) { + case float64: + if v < 1 || v > 12 { + return nil, xerr.NewErrMsg("生日性格查询缺少必填参数:m(月份 1-12)") + } + m = int(v) + case string: + if v == "" { + return nil, xerr.NewErrMsg("生日性格查询缺少必填参数:m(月份 1-12)") + } + mInt, err := strconv.Atoi(v) + if err != nil || mInt < 1 || mInt > 12 { + return nil, xerr.NewErrMsg("生日性格查询参数 m 必须是 1-12 的数字") + } + m = mInt + default: + return nil, xerr.NewErrMsg("生日性格查询缺少必填参数:m(月份 1-12)") + } + reqParams["m"] = m + + // 日期(必填)- 支持字符串和数字类型 + var d int + switch v := params["d"].(type) { + case float64: + if v < 1 || v > 31 { + return nil, xerr.NewErrMsg("生日性格查询缺少必填参数:d(日期 1-31)") + } + d = int(v) + case string: + if v == "" { + return nil, xerr.NewErrMsg("生日性格查询缺少必填参数:d(日期 1-31)") + } + dInt, err := strconv.Atoi(v) + if err != nil || dInt < 1 || dInt > 31 { + return nil, xerr.NewErrMsg("生日性格查询参数 d 必须是 1-31 的数字") + } + d = dInt + default: + return nil, xerr.NewErrMsg("生日性格查询缺少必填参数:d(日期 1-31)") + } + reqParams["d"] = d + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("dob/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求生日性格接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("生日性格接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析生日性格数据失败") + } + + return map[string]interface{}{ + "title": result["title"], + "content": result["content"], + }, nil +} + +// processDictum 名言警句查询 +func (s *ToolboxService) processDictum( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(必填) + num, ok := params["num"].(float64) + if !ok || num <= 0 { + num = 5 // 给一个安全默认值 + } + reqParams["num"] = int(num) + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("dictum/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求名言警句接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("名言警句接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析名言警句数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("名言警句列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if mrname, ok := raw["mrname"]; ok { + clean["mrname"] = mrname + } + if content, ok := raw["content"]; ok { + clean["content"] = content + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processJoke 雷人笑话查询 +func (s *ToolboxService) processJoke( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(必填) + num, ok := params["num"].(float64) + if !ok || num <= 0 { + num = 5 // 给一个安全默认值 + } + reqParams["num"] = int(num) + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("joke/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求雷人笑话接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("雷人笑话接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析雷人笑话数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("雷人笑话列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if title, ok := raw["title"]; ok { + clean["title"] = title + } + if content, ok := raw["content"]; ok { + clean["content"] = content + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processDream 周公解梦查询 +func (s *ToolboxService) processDream( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 搜索词(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("周公解梦查询缺少必填参数:word") + } + reqParams["word"] = word + + // 返回数量(必填) + num, ok := params["num"].(float64) + if !ok || num <= 0 { + num = 5 // 给一个安全默认值 + } + reqParams["num"] = int(num) + + // 页码(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("dream/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求周公解梦接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("周公解梦接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析周公解梦数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("周公解梦列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if title, ok := raw["title"]; ok { + clean["title"] = title + } + if typ, ok := raw["type"]; ok { + clean["type"] = typ + } + if content, ok := raw["result"]; ok { + clean["result"] = content + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processCaiPu 菜谱查询 +func (s *ToolboxService) processCaiPu( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 搜索词(可选) + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + + // 页码(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("caipu/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求菜谱查询接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("菜谱查询接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析菜谱查询数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("菜谱查询列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if name, ok := raw["cp_name"]; ok { + clean["cp_name"] = name + } + if typ, ok := raw["type_name"]; ok { + clean["type_name"] = typ + } + if yuanliao, ok := raw["yuanliao"]; ok { + clean["yuanliao"] = yuanliao + } + if tiaoliao, ok := raw["tiaoliao"]; ok { + clean["tiaoliao"] = tiaoliao + } + if zuofa, ok := raw["zuofa"]; ok { + clean["zuofa"] = zuofa + } + if tishi, ok := raw["tishi"]; ok { + clean["tishi"] = tishi + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processWxNew 微信文章精选查询 +func (s *ToolboxService) processWxNew( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(必填) + num, ok := params["num"].(float64) + if !ok || num <= 0 { + num = 10 + } + reqParams["num"] = int(num) + + // 页码(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 随机(可选) + if rand, ok := params["rand"].(float64); ok && rand == 1 { + reqParams["rand"] = 1 + } + + // 关键词(可选) + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("wxnew/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求微信文章精选接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("微信文章精选接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析微信文章精选数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("微信文章精选列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if title, ok := raw["title"]; ok { + clean["title"] = title + } + if url, ok := raw["url"]; ok { + clean["url"] = url + } + if username, ok := raw["username"]; ok { + clean["username"] = username + } + if ctime, ok := raw["ctime"]; ok { + clean["ctime"] = ctime + } + if picurl, ok := raw["picurl"]; ok { + clean["picurl"] = picurl + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processConstellation 星座运势查询 +func (s *ToolboxService) processConstellation(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 必填:星座名称 + astro, ok := params["astro"].(string) + if !ok || astro == "" { + return nil, xerr.NewErrMsg("astro 星座名称参数不能为空且必须为字符串") + } + reqParams["astro"] = astro + + // 可选:日期 (fortune参数转换为date) + if fortune, ok := params["fortune"].(string); ok && fortune != "" { + now := time.Now() + switch fortune { + case "tomorrow": + date := now.AddDate(0, 0, 1) + reqParams["date"] = date.Format("2006-01-02") + case "week": + // 本周返回当前日期 + reqParams["date"] = now.Format("2006-01-02") + case "month": + // 本月返回当前日期 + reqParams["date"] = now.Format("2006-01-02") + default: // today + reqParams["date"] = now.Format("2006-01-02") + } + } + + resp, err := s.tianxingjuheClient.Get("star/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求星座运势API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("星座运势接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析星座运势数据失败") + } + + // 解析 list + rawList, ok := resultMap["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("星座运势列表数据异常") + } + + // 将 list 转换为扁平化的 map + result := map[string]interface{}{ + "astro": astro, + } + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + typeName, ok := raw["type"].(string) + content, hasContent := raw["content"].(string) + + if !ok || !hasContent { + continue + } + + // 将 type 映射到对应的字段名 + switch typeName { + case "综合指数": + result["summary"] = content + case "爱情指数": + result["love"] = content + case "工作指数": + result["work"] = content + case "财运指数": + result["money"] = content + case "健康指数": + result["health"] = content + case "幸运颜色": + result["color"] = content + case "幸运数字": + result["number"] = content + case "贵人星座": + result["qfriend"] = content + case "今日概述": + result["today_summary"] = content + } + } + + return result, nil +} + +// processFanyi 在线翻译 +func (s *ToolboxService) processFanyi(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 必填:待翻译文本 + content, ok := params["content"].(string) + if !ok || content == "" { + return nil, xerr.NewErrMsg("content 待翻译文本参数不能为空且必须为字符串") + } + reqParams["content"] = content + + // 可选:源语言 (默认自动检测) + if from, ok := params["from"].(string); ok && from != "" { + reqParams["from"] = from + } + + // 可选:目标语言 (默认中文) + if to, ok := params["to"].(string); ok && to != "" { + reqParams["to"] = to + } + + resp, err := s.tianxingjuheClient.Get("fanyi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求在线翻译API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("在线翻译接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析翻译数据失败") + } + + return map[string]interface{}{ + "content": resultMap["content"], + "from": resultMap["from"], + "to": resultMap["to"], + "result": resultMap["result"], + }, nil +} + +// processHoliday 节假日查询 +func (s *ToolboxService) processHoliday(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + // 调试日志:打印接收到的所有参数 + logx.Infof("processHoliday 收到的参数: %+v", params) + + reqParams := map[string]interface{}{} + + // 获取查询方式(1=按年,2=按月),默认按年 + var queryType int = 1 + if typ, ok := params["type"]; ok { + switch v := typ.(type) { + case float64: + queryType = int(v) + case string: + if val, err := strconv.Atoi(v); err == nil { + queryType = val + } + case json.Number: + if val, err := strconv.Atoi(v.String()); err == nil { + queryType = val + } + } + } + + // 将type直接传给天行API + reqParams["type"] = queryType + + // 传当前日期 + reqParams["date"] = time.Now().Format("2006-01-02") + + logx.Infof("调用天行API: type=%d, date=%s", queryType, reqParams["date"]) + + resp, err := s.tianxingjuheClient.Get("jiejiari/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求节假日查询API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("节假日查询接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析节假日数据失败") + } + + rawList, ok := resultMap["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("节假日列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + clean := map[string]interface{}{} + for _, k := range []string{"date", "name", "info", "tip", "cnweekday", "lunaryear", "lunarmonth", "lunarday", "wage", "isnotwork", "daycode"} { + if v, ok := raw[k]; ok { + clean[k] = v + } + } + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processGarbage 垃圾分类查询 +func (s *ToolboxService) processGarbage(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { + reqParams := map[string]interface{}{} + + // 必填:垃圾名称 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("word 垃圾名称参数不能为空且必须为字符串") + } + reqParams["word"] = word + + resp, err := s.tianxingjuheClient.Get("lajifenlei/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求垃圾分类查询API失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("垃圾分类查询接口返回异常: %s", resp.Msg)) + } + + resultMap, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析垃圾分类数据失败") + } + + // 解析列表 + rawList, ok := resultMap["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("垃圾分类列表数据异常") + } + + typeMap := map[int]string{ + 0: "可回收物", + 1: "有害垃圾", + 2: "厨余垃圾(湿)", + 3: "其他垃圾(干)", + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + clean := map[string]interface{}{ + "name": raw["name"], + } + if t, ok := raw["type"].(float64); ok { + clean["type"] = int(t) + if name, exists := typeMap[int(t)]; exists { + clean["type_name"] = name + } + } + if explain, ok := raw["explain"]; ok { + clean["explain"] = explain + } + if tip, ok := raw["tip"]; ok { + clean["tip"] = tip + } + if contain, ok := raw["contain"]; ok { + clean["contain"] = contain + } + list = append(list, clean) + } + + return map[string]interface{}{ + "word": word, + "count": len(list), + "list": list, + }, nil +} + +// processZone 地区搜索查询 +func (s *ToolboxService) processZone(ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 地区名称(必填) + area, ok := params["area"].(string) + if !ok || area == "" { + return nil, xerr.NewErrMsg("地区搜索缺少必填参数:area") + } + reqParams["area"] = area + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + + // 翻页(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("citylookup/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求地区搜索接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("地区搜索接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析地区搜索数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("地区搜索列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["adcode"]; ok { + clean["adcode"] = v + } + if v, ok := raw["areacn"]; ok { + clean["areacn"] = v + } + if v, ok := raw["areaen"]; ok { + clean["areaen"] = v + } + if v, ok := raw["areaid"]; ok { + clean["areaid"] = v + } + if v, ok := raw["citycn"]; ok { + clean["citycn"] = v + } + if v, ok := raw["cityen"]; ok { + clean["cityen"] = v + } + if v, ok := raw["areatype"]; ok { + clean["areatype"] = v + } + if v, ok := raw["latitude"]; ok { + clean["latitude"] = v + } + if v, ok := raw["longitude"]; ok { + clean["longitude"] = v + } + if v, ok := raw["provincecn"]; ok { + clean["provincecn"] = v + } + if v, ok := raw["provinceen"]; ok { + clean["provinceen"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processHuaYu 花语箴言查询 +func (s *ToolboxService) processHuaYu( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 花的名字(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("花语箴言查询缺少必填参数:word") + } + reqParams["word"] = word + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("huayu/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求花语箴言接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("花语箴言接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析花语箴言数据失败") + } + + return map[string]interface{}{ + "cnflower": result["cnflower"], + "enflower": result["enflower"], + "flowerlang": result["flowerlang"], + "flowerprov": result["flowerprov"], + }, nil +} + +// processMoodPoetry 情绪诗句查询 +func (s *ToolboxService) processMoodPoetry( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 分类查询(可选,默认随机) + if typ, ok := params["type"].(float64); ok && typ >= 1 && typ <= 4 { + reqParams["type"] = int(typ) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("moodpoetry/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求情绪诗句接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("情绪诗句接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析情绪诗句数据失败") + } + + return map[string]interface{}{ + "type": result["type"], + "title": result["title"], + "author": result["author"], + "content": result["content"], + }, nil +} + +// processPcTerm 计算机术语查询 +func (s *ToolboxService) processPcTerm( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 术语缩写(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("计算机术语查询缺少必填参数:word") + } + reqParams["word"] = word + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("pcterm/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求计算机术语接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("计算机术语接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析计算机术语数据失败") + } + + return map[string]interface{}{ + "abbr": result["abbr"], + "type": result["type"], + "notes": result["notes"], + }, nil +} + +// processTarga 扩展名查询 +func (s *ToolboxService) processTarga( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 文件后缀名(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("扩展名查询缺少必填参数:word") + } + reqParams["word"] = word + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("targa/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求扩展名查询接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("扩展名查询接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析扩展名查询数据失败") + } + + // 清理HTML标签 + targa := "" + notes := "" + + if v, ok := result["targa"].(string); ok { + targa = v + } + + if v, ok := result["notes"].(string); ok { + // 移除HTML标签 + re := regexp.MustCompile(`<[^>]+>`) + notes = re.ReplaceAllString(v, "") + // 替换换行标签 + notes = regexp.MustCompile(`(?i)`).ReplaceAllString(notes, "\n") + // 去除多余空行 + notes = regexp.MustCompile(`\n{3,}`).ReplaceAllString(notes, "\n\n") + // 去除首尾空白 + notes = strings.TrimSpace(notes) + } + + return map[string]interface{}{ + "targa": targa, + "notes": notes, + }, nil +} + +// processDuiShiCi 填对诗词查询 +func (s *ToolboxService) processDuiShiCi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 填对诗词接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("duishici/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求填对诗词接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("填对诗词接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析填对诗词数据失败") + } + + return map[string]interface{}{ + "quest": result["quest"], + "answer": result["answer"], + "source": result["source"], + }, nil +} + +// processMsdl 民俗对联查询 +func (s *ToolboxService) processMsdl( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 10 { + reqParams["num"] = int(num) + } + + // 分类查询(可选) + if fenlei, ok := params["fenlei"].(string); ok && fenlei != "" { + reqParams["fenlei"] = fenlei + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("msdl/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求民俗对联接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("民俗对联接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析民俗对联数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("民俗对联列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["fenlei"]; ok { + clean["fenlei"] = v + } + if v, ok := raw["hengpi"]; ok { + clean["hengpi"] = v + } + if v, ok := raw["shanglian"]; ok { + clean["shanglian"] = v + } + if v, ok := raw["xialian"]; ok { + clean["xialian"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processTimeZone 国际时区查询 +func (s *ToolboxService) processTimeZone( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 国家/地区(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("国际时区查询缺少必填参数:word") + } + reqParams["word"] = word + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("timezone/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求国际时区接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("国际时区接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析国际时区数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("国际时区列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if country, ok := raw["country"]; ok { + clean["country"] = country + } + if areaname, ok := raw["areaname"]; ok { + clean["areaname"] = areaname + } + if timezone, ok := raw["timezone"]; ok { + clean["timezone"] = timezone + } + if continent, ok := raw["continent"]; ok { + clean["continent"] = continent + } + if countryen, ok := raw["countryen"]; ok { + clean["countryen"] = countryen + } + if countryezm, ok := raw["countryezm"]; ok { + clean["countryezm"] = countryezm + } + if countryszm, ok := raw["countryszm"]; ok { + clean["countryszm"] = countryszm + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processDuoYinZi 多音字查询 +func (s *ToolboxService) processDuoYinZi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + logx.Infof("processDuoYinZi 收到的参数: %+v", params) + + reqParams := map[string]interface{}{} + + // 多音字(必填) + word, ok := params["word"].(string) + logx.Infof("word 值: %v, 类型: %T, ok: %v", word, params["word"], ok) + if !ok || word == "" { + return nil, xerr.NewErrMsg("多音字查询缺少必填参数:word") + } + reqParams["word"] = word + + logx.Infof("调用天行API: word=%s", word) + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("duoyinzi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求多音字接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("该字无多音字.")) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析多音字数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("多音字列表数据异常") + } + + // 将多音字信息合并成一个字符串 + list := make([]string, 0, len(rawList)) + for _, item := range rawList { + if s, ok := item.(string); ok { + list = append(list, s) + } + } + + return map[string]interface{}{ + "word": result["hanzi"], + "content": strings.Join(list, "\n"), + }, nil +} + +// processDailyTel 全国常用电话 +func (s *ToolboxService) processDailyTel( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 机构名称或电话(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("全国常用电话查询缺少必填参数:word") + } + reqParams["word"] = word + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("dailytel/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求全国常用电话接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("全国常用电话接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析全国常用电话数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("全国常用电话列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if name, ok := raw["name"]; ok { + clean["name"] = name + } + if cate, ok := raw["cate"]; ok { + clean["cate"] = cate + } + if tel, ok := raw["tel"]; ok { + clean["tel"] = tel + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processObdCode 汽车 OBD 故障码查询 +func (s *ToolboxService) processObdCode( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // OBD 故障码(必填) + code, ok := params["code"].(string) + if !ok || code == "" { + return nil, xerr.NewErrMsg("汽车OBD查询缺少必填参数:code") + } + reqParams["code"] = code + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("obdcode/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求汽车OBD接口失败: %v", err)) + } + + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("汽车OBD接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析汽车OBD数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("汽车OBD列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["code"]; ok { + clean["code"] = v + } + if v, ok := raw["descr"]; ok { + clean["descr"] = v + } + if v, ok := raw["ennote"]; ok { + clean["ennote"] = v + } + if v, ok := raw["zhnote"]; ok { + clean["zhnote"] = v + } + if v, ok := raw["carmodel"]; ok { + clean["carmodel"] = v + } + if v, ok := raw["category"]; ok { + clean["category"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processChaiZi 汉语拆字查询 +func (s *ToolboxService) processChaiZi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 查询内容(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("汉语拆字查询缺少必填参数:word") + } + reqParams["word"] = word + + // 查询类型(可选,默认 0) + var queryType int + if typ, ok := params["type"]; ok { + switch v := typ.(type) { + case float64: + queryType = int(v) + case string: + if val, err := strconv.Atoi(v); err == nil { + queryType = val + } + case json.Number: + if val, err := strconv.Atoi(v.String()); err == nil { + queryType = val + } + } + } + reqParams["type"] = queryType + + logx.Infof("汉字拆解 - type: %d, word: %s", queryType, word) + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("chaizi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求汉语拆字接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("汉语拆字接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析汉语拆字数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("汉语拆字列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + clean := map[string]interface{}{} + if texts, ok := raw["texts"]; ok { + clean["texts"] = texts + } + if words, ok := raw["words"]; ok { + clean["words"] = words + } + list = append(list, clean) + } + + // 将列表合并成文本格式 + contentList := make([]string, 0, len(list)) + for _, item := range list { + if texts, ok := item["texts"].(string); ok { + contentList = append(contentList, texts) + } + if words, ok := item["words"].(string); ok { + contentList = append(contentList, words) + } + } + + return map[string]interface{}{ + "word": word, + "content": strings.Join(contentList, "\n"), + }, nil +} + +// processShiJing 诗经大全查询 +func (s *ToolboxService) processShiJing( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 10 { + reqParams["num"] = int(num) + } + + // 关键词搜索(可选) + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("shijing/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求诗经大全接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("诗经大全接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析诗经大全数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("诗经大全列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["name"]; ok { + clean["name"] = v + } + if v, ok := raw["author"]; ok { + clean["author"] = v + } + if v, ok := raw["content"]; ok { + clean["content"] = v + } + if v, ok := raw["note"]; ok { + clean["note"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processChePai 车牌归属地查询 +func (s *ToolboxService) processChePai( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 车牌代码/城市/省份(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("车牌归属地查询缺少必填参数:word") + } + reqParams["word"] = word + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("chepai/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求车牌归属地接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("车牌归属地接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析车牌归属地数据失败") + } + + return map[string]interface{}{ + "code": result["code"], + "province": result["province"], + "city": result["city"], + "citycode": result["citycode"], + }, nil +} + +// processFlmj 分类名句查询 +func (s *ToolboxService) processFlmj( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // Tag 类型(必填) + typ, ok := params["type"].(string) + if !ok || typ == "" { + return nil, xerr.NewErrMsg("分类名句查询缺少必填参数:type") + } + reqParams["type"] = typ + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 20 { + reqParams["num"] = int(num) + } + + // 翻页(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 随机(可选) + if randVal, ok := params["rand"].(float64); ok && randVal == 1 { + reqParams["rand"] = 1 + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("flmj/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求分类名句接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("分类名句接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析分类名句数据失败") + } + + // 检查是否返回了列表(num > 1 时) + if list, hasList := result["list"]; hasList { + if listArray, isArray := list.([]interface{}); isArray && len(listArray) > 0 { + // 返回第一个结果 + if firstItem, ok := listArray[0].(map[string]interface{}); ok { + return map[string]interface{}{ + "source": firstItem["source"], + "content": firstItem["content"], + }, nil + } + } + } + + return map[string]interface{}{ + "source": result["source"], + "content": result["content"], + }, nil + +} + +// processXhZd 新华字典查询 +func (s *ToolboxService) processXhZd( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 查询内容(必填) + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("新华字典查询缺少必填参数:word") + } + reqParams["word"] = word + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 { + reqParams["num"] = int(num) + } + + // 翻页(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 查询类型(可选,默认 0) + if typ, ok := params["type"].(float64); ok && typ >= 0 && typ <= 2 { + reqParams["type"] = int(typ) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("xhzd/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求新华字典接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("新华字典接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析新华字典数据失败") + } + + logx.Infof("新华字典API返回的result: %+v", result) + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok || len(rawList) == 0 { + return nil, xerr.NewErrMsg("新华字典列表数据异常") + } + + // 取第一个元素 + firstItem, ok := rawList[0].(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("新华字典数据格式异常") + } + + // 去除HTML标签 + htmlTagRegex := regexp.MustCompile(`<[^>]+>`) + + content := "" + if c, ok := firstItem["content"].(string); ok { + content = htmlTagRegex.ReplaceAllString(c, "") + } + + explain := "" + if e, ok := firstItem["explain"].(string); ok { + explain = htmlTagRegex.ReplaceAllString(e, "") + } + + return map[string]interface{}{ + "word": firstItem["hanzi"], + "py": firstItem["py"], + "pyyb": firstItem["pyyb"], + "wubi": firstItem["wubi"], + "bihua": firstItem["bihua"], + "bushou": firstItem["bushou"], + "bishun": firstItem["bishun"], + "content": content, + "explain": explain, + }, nil +} + +// processTeleCode 标准电码查询 //改 +func (s *ToolboxService) processTeleCode( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 查询内容(必填) + city, ok := params["city"].(string) + if !ok || city == "" { + return nil, xerr.NewErrMsg("标准电码查询缺少必填参数:city") + } + reqParams["text"] = city + + // 返回类型(可选,默认 0) + if str, ok := params["str"].(float64); ok && (str == 0 || str == 1) { + reqParams["str"] = int(str) + } + + // 转换类型(可选,默认 0) + if typ, ok := params["type"].(float64); ok && (typ == 0 || typ == 1) { + reqParams["type"] = int(typ) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("telecode/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求标准电码接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("标准电码接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析标准电码数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("标准电码列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["code"]; ok { + clean["code"] = v + } + if v, ok := raw["text"]; ok { + clean["text"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processCharConvert 简繁转换 +func (s *ToolboxService) processCharConvert( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 待转换字符(必填) + text, ok := params["text"].(string) + if !ok || text == "" { + return nil, xerr.NewErrMsg("简繁转换缺少必填参数:text") + } + reqParams["text"] = text + + // 转换类型(可选,默认 0 转繁体) + if typ, ok := params["type"].(float64); ok && (typ == 0 || typ == 1) { + reqParams["type"] = int(typ) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("charconvert/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求简繁转换接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("简繁转换接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析简繁转换数据失败") + } + + return map[string]interface{}{ + "intext": result["intext"], + "outtext": result["outtext"], + }, nil +} + +// processCheckIPv6 检查域名 IPv6 支持情况 +func (s *ToolboxService) processCheckIPv6( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 域名(必填) + domain, ok := params["domain"].(string) + if !ok || domain == "" { + return nil, xerr.NewErrMsg("检查域名IPv6缺少必填参数:domain") + } + reqParams["domain"] = domain + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("checkipv6/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求检查域名IPv6接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("检查域名IPv6接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析检查域名IPv6数据失败") + } + + return map[string]interface{}{ + "domain": result["domain"], + "ipv6": result["ipv6"], + "isipv6": result["isipv6"], + }, nil +} + +// processWorldTime 全球时间查询 +func (s *ToolboxService) processWorldTime( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 城市名称(必填) + city, ok := params["city"].(string) + if !ok || city == "" { + return nil, xerr.NewErrMsg("全球时间查询缺少必填参数:city") + } + reqParams["city"] = city + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("worldtime/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求全球时间接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("全球时间接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析全球时间数据失败") + } + + return map[string]interface{}{ + "city": result["city"], + "encity": result["encity"], + "country": result["country"], + "encountry": result["encountry"], + "countryezm": result["countryezm"], + "countrycode": result["countrycode"], + "strtime": result["strtime"], + "timestamp": result["timestamp"], + "timeZone": result["timeZone"], + "week": result["week"], + "enweek": result["enweek"], + "weeknum": result["weeknum"], + "nowmonth": result["nowmonth"], + "ennowmonth": result["ennowmonth"], + "noon": result["noon"], + "ennoon": result["ennoon"], + "summertime": result["summertime"], + "cursummertime": result["cursummertime"], + }, nil +} + +// processWxHotTopic 腾讯热搜榜查询 +func (s *ToolboxService) processWxHotTopic( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("wxhottopic/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求腾讯热搜榜接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("腾讯热搜榜接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析腾讯热搜榜数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("腾讯热搜榜列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["word"]; ok { + clean["word"] = v + } + if v, ok := raw["index"]; ok { + clean["index"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processZmsc 最美宋词查询 +func (s *ToolboxService) processZmsc( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("zmsc/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求最美宋词接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("最美宋词接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析最美宋词数据失败") + } + + return map[string]interface{}{ + "source": result["source"], + "content": result["content"], + }, nil +} + +// processHsjz 失恋分手句子查询 +func (s *ToolboxService) processHsjz( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("hsjz/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求失恋分手句子接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("失恋分手句子接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析失恋分手句子数据失败") + } + + return map[string]interface{}{ + "content": result["content"], + }, nil +} + +// processGjmj 古籍名句查询 +func (s *ToolboxService) processGjmj( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("gjmj/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求古籍名句接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("古籍名句接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析古籍名句数据失败") + } + + return map[string]interface{}{ + "source": result["source"], + "content": result["content"], + }, nil +} + +// processCnMoney 金额转大写 +func (s *ToolboxService) processCnMoney( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 金额(必填) + money, ok := params["money"].(string) + if !ok || money == "" { + return nil, xerr.NewErrMsg("金额转大写缺少必填参数:money") + } + reqParams["money"] = money + + // 单位类型(可选) + if typ, ok := params["type"].(string); ok && typ != "" { + reqParams["type"] = typ + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("cnmoney/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求金额转大写接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("金额转大写接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析金额转大写数据失败") + } + + return map[string]interface{}{ + "cnresult": result["cnresult"], + "enresult": result["enresult"], + "fnresult": result["fnresult"], + }, nil +} + +// processLzmy 励志古言查询 +func (s *ToolboxService) processLzmy( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("lzmy/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求励志古言接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("励志古言接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析励志古言数据失败") + } + + return map[string]interface{}{ + "saying": result["saying"], + "source": result["source"], + "transl": result["transl"], + }, nil +} + +// processBaiKeTiKu 百科题库查询 +func (s *ToolboxService) processBaiKeTiKu( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("baiketiku/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求百科题库接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("百科题库接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析百科题库数据失败") + } + + return map[string]interface{}{ + "title": result["title"], + "answer": result["answer"], + "answerA": result["answerA"], + "answerB": result["answerB"], + "answerC": result["answerC"], + "answerD": result["answerD"], + "analytic": result["analytic"], + }, nil +} + +// processDialogue 经典台词查询 +func (s *ToolboxService) processDialogue( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("dialogue/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求经典台词接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("经典台词接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析经典台词数据失败") + } + + return map[string]interface{}{ + "type": result["type"], + "source": result["source"], + "english": result["english"], + "dialogue": result["dialogue"], + }, nil +} + +// processTianGou 舔狗日记查询 +func (s *ToolboxService) processTianGou( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("tiangou/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求舔狗日记接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("舔狗日记接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析舔狗日记数据失败") + } + + return map[string]interface{}{ + "content": result["content"], + }, nil +} + +// processRobotLog 机器人日志查询 +func (s *ToolboxService) processRobotLog( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 返回数量(可选) + if num, ok := params["num"].(float64); ok && num > 0 && num <= 50 { + reqParams["num"] = int(num) + } + + // 翻页(可选) + if page, ok := params["page"].(float64); ok && page > 0 { + reqParams["page"] = int(page) + } + + // 用户唯一身份ID(可选) + if uniqueID, ok := params["uniqueid"].(string); ok && uniqueID != "" { + reqParams["uniqueid"] = uniqueID + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("robotlog/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求机器人日志接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("机器人日志接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析机器人日志数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("机器人日志列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["time"]; ok { + clean["time"] = v + } + if v, ok := raw["content"]; ok { + clean["content"] = v + } + if v, ok := raw["datatype"]; ok { + clean["datatype"] = v + } + if v, ok := raw["uniqueid"]; ok { + clean["uniqueid"] = v + } + if v, ok := raw["inputword"]; ok { + clean["inputword"] = v + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processAqi 空气质量指数查询 +func (s *ToolboxService) processAqi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 地区名称(必填) + city, ok := params["city"].(string) + logx.Infof("city 值: %v, 类型: %T, ok: %v", city, params["city"], ok) + if !ok || city == "" { + return nil, xerr.NewErrMsg("空气质量指数查询缺少必填参数:area") + } + reqParams["area"] = city + + logx.Infof("调用天行API: area=%s", city) + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("aqi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求空气质量指数接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("空气质量指数接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析空气质量指数数据失败") + } + + return map[string]interface{}{ + "area": result["area"], + "area_code": result["area_code"], + "time": result["time"], + "aqi": result["aqi"], + "num": result["num"], + "quality": result["quality"], + "primary_pollutant": result["primary_pollutant"], + "co": result["co"], + "o3": result["o3"], + "o3_8h": result["o3_8h"], + "no2": result["no2"], + "so2": result["so2"], + "pm10": result["pm10"], + "pm2_5": result["pm2_5"], + }, nil +} + +// processAddressParse 物流地址解析 +func (s *ToolboxService) processAddressParse( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 待解析文本(必填) + address, ok := params["address"].(string) + if !ok || address == "" { + return nil, xerr.NewErrMsg("物流地址解析缺少必填参数:address") + } + reqParams["text"] = address + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("addressparse/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求物流地址解析接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("物流地址解析接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析物流地址解析数据失败") + } + + return map[string]interface{}{ + "province": result["province"], + "city": result["city"], + "district": result["district"], + "detail": result["detail"], + "name": result["name"], + "mobile": result["mobile"], + "postcode": result["postcode"], + }, nil +} + +// processPet 宠物大全查询 +func (s *ToolboxService) processPet( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 每页返回数量(默认10,前端可选) + num := 10 + if v, ok := params["num"].(float64); ok && v > 0 { + num = int(v) + } + reqParams["num"] = num + + // 翻页(默认1,前端可选) + page := 1 + if v, ok := params["page"].(float64); ok && v > 0 { + page = int(v) + } + reqParams["page"] = page + + // 宠物名称(可选,用于搜索) + if name, ok := params["name"].(string); ok && name != "" { + reqParams["name"] = name + } + + // 宠物类型(可选):0猫科、1犬类、2爬行类、3小宠物类、4水族类 + if typ, ok := params["type"].(float64); ok && typ >= 0 && typ <= 4 { + reqParams["type"] = int(typ) + } + + // 调用天行 API + resp, err := s.tianxingjuheClient.Get("pet/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求宠物大全接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("宠物大全接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析宠物大全数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("宠物大全列表数据异常") + } + + // 字段白名单 + allowed := map[string]bool{ + "name": true, + "engName": true, + "pettype": true, + "nation": true, + "life": true, + "price": true, + "characters": true, + "feature": true, + "characterFeature": true, + "feedPoints": true, + "careKnowledge": true, + "easyOfDisease": true, + "coverURL": true, + "desc": true, + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + for k, v := range raw { + if allowed[k] { + clean[k] = v + } + } + + // 至少要有宠物名称 + if clean["name"] != nil { + list = append(list, clean) + } + } + + if len(list) == 0 { + return nil, xerr.NewErrMsg("未查询到相关宠物信息") + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processHotReview 云音乐热评查询 +func (s *ToolboxService) processHotReview( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("hotreview/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求云音乐热评接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("云音乐热评接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析云音乐热评数据失败") + } + + return map[string]interface{}{ + "source": result["source"], + "content": result["content"], + }, nil +} + +// processAnsLaJiFenLei 垃圾分类问答查询 +func (s *ToolboxService) processAnsLaJiFenLei( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("anslajifenlei/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求垃圾分类问答接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("垃圾分类问答接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析垃圾分类问答数据失败") + } + + typeMap := map[int]string{ + 0: "可回收物", + 1: "有害垃圾", + 2: "厨余垃圾", + 3: "其他垃圾", + } + + clean := map[string]interface{}{ + "name": result["name"], + "type": result["type"], + "explain": result["explain"], + } + if t, ok := result["type"].(float64); ok { + if name, exists := typeMap[int(t)]; exists { + clean["type_name"] = name + } + } + + return clean, nil +} + +// processDouYinHot 抖音热搜榜查询 +func (s *ToolboxService) processDouYinHot( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 该接口无需额外参数,仅需 key + resp, err := s.tianxingjuheClient.Get("douyinhot/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求抖音热搜榜接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("抖音热搜榜接口异常: %s", resp.Msg)) + } + + // 解析 result + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析抖音热搜榜数据失败") + } + + // 解析 list + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("抖音热搜榜列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + + clean := map[string]interface{}{} + if v, ok := raw["word"]; ok { + clean["word"] = v + } + if v, ok := raw["label"]; ok { + clean["label"] = v + } + if v, ok := raw["hotindex"]; ok { + clean["hotindex"] = v + } + if v, ok := raw["label"]; ok { + // 转换label为可读文本 + labelMap := map[int]string{ + 0: "普通", + 1: "新", + 2: "荐", + 3: "热", + } + if labelInt, ok := v.(float64); ok { + clean["label_text"] = labelMap[int(labelInt)] + } + } + + list = append(list, clean) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processZaoAnWords 获取早安心语 +func (s *ToolboxService) processZaoAnWords( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 该接口无需额外业务参数,仅自带key + reqParams := map[string]interface{}{} + + // 请求早安心语接口 + resp, err := s.tianxingjuheClient.Get("zaoan/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求早安心语接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("早安心语接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析早安心语数据失败") + } + + content, _ := result["content"].(string) + + return map[string]interface{}{ + "content": content, + }, nil +} + +// processWanAnWords 获取晚安心语 +func (s *ToolboxService) processWanAnWords( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 该接口无业务参数,仅自带 key + reqParams := map[string]interface{}{} + + // 请求晚安心语接口 + resp, err := s.tianxingjuheClient.Get("wanan/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求晚安心语接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("晚安心语接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析晚安心语数据失败") + } + + content, _ := result["content"].(string) + + return map[string]interface{}{ + "content": content, + }, nil +} + +// processAreaData 全国五级行政区划查询 +func (s *ToolboxService) processAreaData( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 逐级行政区ID参数 + if v, ok := params["province"].(string); ok && v != "" { + reqParams["province"] = v + } + if v, ok := params["city"].(string); ok && v != "" { + reqParams["city"] = v + } + if v, ok := params["county"].(string); ok && v != "" { + reqParams["county"] = v + } + if v, ok := params["town"].(string); ok && v != "" { + reqParams["town"] = v + } + if v, ok := params["village"].(string); ok && v != "" { + reqParams["village"] = v + } + + // 请求行政区划接口 + resp, err := s.tianxingjuheClient.Get("area/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求行政区划接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("行政区划接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析行政区划数据失败") + } + + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("行政区划列表数据异常") + } + + list := make([]map[string]interface{}, 0, len(rawList)) + for _, item := range rawList { + raw, ok := item.(map[string]interface{}) + if !ok { + continue + } + info := map[string]interface{}{ + "provinceid": raw["provinceid"], + "provincename": raw["provincename"], + "cityid": raw["cityid"], + "cityname": raw["cityname"], + "countyid": raw["countyid"], + "countyname": raw["countyname"], + "townid": raw["townid"], + "townname": raw["townname"], + "villageid": raw["villageid"], + "villagename": raw["villagename"], + } + list = append(list, info) + } + + return map[string]interface{}{ + "count": len(list), + "list": list, + }, nil +} + +// processMgJuZi 获取民国句子 +func (s *ToolboxService) processMgJuZi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 请求民国句子接口 + resp, err := s.tianxingjuheClient.Get("mgjuzi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求民国句子接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("民国句子接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析民国句子数据失败") + } + + return map[string]interface{}{ + "author": result["author"], + "content": result["content"], + }, nil +} + +// processSongCi 精选宋词查询 +func (s *ToolboxService) processSongCi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{ + "num": 1, + "page": 1, + } + + // 数量 1-10 + if num, ok := params["num"].(int); ok && num >= 1 && num <= 10 { + reqParams["num"] = num + } + // 页码 + if page, ok := params["page"].(int); ok && page > 0 { + reqParams["page"] = page + } + // 搜索关键词 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + resp, err := s.tianxingjuheClient.Get("songci/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求宋词接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("宋词接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析宋词数据失败") + } + + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("宋词列表数据异常") + } + + var songList []map[string]interface{} + for _, item := range rawList { + d, _ := item.(map[string]interface{}) + songList = append(songList, map[string]interface{}{ + "title": d["title"], + "author": d["author"], + "content": d["content"], + "translate": d["yiwen"], + "tags": d["tags"], + }) + } + + return map[string]interface{}{ + "count": len(songList), + "list": songList, + }, nil +} + +// processStory 故事大全查询 +func (s *ToolboxService) processStory( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{ + "num": 5, + "page": 1, + "type": 4, + } + + // 故事类型 1成语 2睡前 3童话 4寓言 + if t, ok := params["type"].(int); ok { + reqParams["type"] = t + } + // 数量 + if num, ok := params["num"].(int); ok && num > 0 { + reqParams["num"] = num + } + // 页码 + if page, ok := params["page"].(int); ok && page > 0 { + reqParams["page"] = page + } + // 搜索关键词 + if word, ok := params["word"].(string); ok && word != "" { + reqParams["word"] = word + } + + resp, err := s.tianxingjuheClient.Get("story/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求故事大全接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("故事大全接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析故事数据失败") + } + + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("故事列表数据异常") + } + + var storyList []map[string]interface{} + for _, item := range rawList { + d, _ := item.(map[string]interface{}) + storyList = append(storyList, map[string]interface{}{ + "title": d["title"], + "content": d["content"], + "storyType": d["storytype"], + }) + } + + return map[string]interface{}{ + "count": len(storyList), + "list": storyList, + }, nil +} + +// processHotLaJiFenLei 热搜垃圾分类查询 +func (s *ToolboxService) processHotLaJiFenLei( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + // 查询日期 + if date, ok := params["date"].(string); ok && date != "" { + reqParams["date"] = date + } + // 垃圾类型 + if typ, ok := params["type"].(int); ok { + reqParams["type"] = typ + } + + resp, err := s.tianxingjuheClient.Get("hotlajifenlei/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求热搜垃圾分类接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("热搜垃圾分类接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析垃圾分类数据失败") + } + + rawList, ok := result["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("垃圾分类列表数据异常") + } + + var garbageList []map[string]interface{} + for _, item := range rawList { + info, _ := item.(map[string]interface{}) + garbageList = append(garbageList, map[string]interface{}{ + "garbageName": info["name"], + "garbageType": info["type"], + "hotIndex": info["index"], + }) + } + + return map[string]interface{}{ + "count": len(garbageList), + "list": garbageList, + }, nil +} + +// processQingShi 获取古代情诗 +func (s *ToolboxService) processQingShi( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + reqParams := map[string]interface{}{} + + resp, err := s.tianxingjuheClient.Get("qingshi/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求古代情诗接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("古代情诗接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析古代情诗数据失败") + } + + return map[string]interface{}{ + "author": result["author"], + "source": result["source"], + "content": result["content"], + }, nil +} + +// processChengyuJieLong 成语接龙 +func (s *ToolboxService) processChengyuJieLong( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + // 必填参数 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("缺少必填参数:word(成语)") + } + userid, ok := params["userid"].(string) + if !ok || userid == "" { + return nil, xerr.NewErrMsg("缺少必填参数:userid(用户唯一标识)") + } + + reqParams := map[string]interface{}{ + "word": word, + "userid": userid, + } + + // 可选:状态维持时间(秒) + if statetime, ok := params["statetime"].(int); ok && statetime > 0 { + reqParams["statetime"] = statetime + } + + // 调用天聚数行接口 + resp, err := s.tianxingjuheClient.Get("chengyujielong/index", reqParams) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求成语接龙接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("成语接龙接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析成语接龙数据失败") + } + + return map[string]interface{}{ + "tip": result["tip"], + "grade": result["grade"], + "chengyu": result["chengyu"], + "pinyin": result["pinyin"], + "jieshi": result["jieshi"], + "chuchu": result["chuchu"], + "startstr": result["startstr"], + "endstr": result["endstr"], + "result": result["result"], // 0=开局,1=胜,2-4=负,5=赢局 + }, nil +} + +// processLaJiFenLei 垃圾分类查询 +func (s *ToolboxService) processLaJiFenLei( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + // 必传垃圾名称 + word, ok := params["word"].(string) + if !ok || word == "" { + return nil, xerr.NewErrMsg("请传入要查询的垃圾名称word") + } + + req := map[string]interface{}{ + "word": word, + "num": 10, + "mode": 0, + "page": 1, + } + + // 可选参数赋值 + if num, v := params["num"].(int); v && num > 0 { + req["num"] = num + } + if mode, v := params["mode"].(int); v { + req["mode"] = mode + } + if page, v := params["page"].(int); v && page > 0 { + req["page"] = page + } + + resp, err := s.tianxingjuheClient.Get("lajifenlei/index", req) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("垃圾分类接口请求失败:%v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("垃圾分类接口异常:%s", resp.Msg)) + } + + resData, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("垃圾分类数据解析失败") + } + + listArr, ok := resData["list"].([]interface{}) + if !ok { + return nil, xerr.NewErrMsg("无垃圾分类结果数据") + } + + var resList []map[string]interface{} + typeMap := map[int]string{ + 0: "可回收垃圾", + 1: "有害垃圾", + 2: "厨余(湿)垃圾", + 3: "其他(干)垃圾", + } + + for _, item := range listArr { + itemMap, _ := item.(map[string]interface{}) + tp, _ := itemMap["type"].(int) + resList = append(resList, map[string]interface{}{ + "garbageName": itemMap["name"], + "typeCode": tp, + "typeName": typeMap[tp], + "explain": itemMap["explain"], + "contain": itemMap["contain"], + "tip": itemMap["tip"], + "isPreJudge": itemMap["aipre"], + }) + } + + return map[string]interface{}{ + "total": len(resList), + "list": resList, + }, nil +} + +// processHanZiPinYin 汉字转拼音 +func (s *ToolboxService) processHanZiPinYin( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + text, ok := params["text"].(string) + if !ok || text == "" { + return nil, xerr.NewErrMsg("请传入需要转换的汉字文本text") + } + + req := map[string]interface{}{ + "text": text, + "type": 0, + } + if typ, ok := params["type"].(int); ok { + req["type"] = typ + } + + resp, err := s.tianxingjuheClient.Get("pinyin/index", req) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("汉字转拼音请求失败:%v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("接口异常:%s", resp.Msg)) + } + + res, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析拼音数据失败") + } + + return map[string]interface{}{ + "fullPinyin": res["pinyin"], + "shortPinyin": res["jianxie"], + }, nil +} + +// processShortHistory 简说历史 +func (s *ToolboxService) processShortHistory( + ctx context.Context, + params map[string]interface{}, +) (map[string]interface{}, error) { + + resp, err := s.tianxingjuheClient.Get("pitlishi/index", nil) + if err != nil { + return nil, xerr.NewErrMsg(fmt.Sprintf("请求简说历史接口失败: %v", err)) + } + if resp.Code != 200 { + return nil, xerr.NewErrMsg(fmt.Sprintf("简说历史接口异常: %s", resp.Msg)) + } + + result, ok := resp.Result.(map[string]interface{}) + if !ok { + return nil, xerr.NewErrMsg("解析历史内容失败") + } + + return map[string]interface{}{ + "historyInfo": result["content"], + }, nil +} + +// ==================== 工具列表 ==================== + +// ToolInfo 工具信息 +type ToolInfo struct { + Key string `json:"key"` + Name string `json:"name"` + Desc string `json:"desc"` +} + +// ListTools 返回所有可用工具列表 +func (s *ToolboxService) ListTools() []ToolInfo { + return []ToolInfo{ + {Key: "ip-location", Name: "IP地址查询", Desc: "查询IP地址归属地、运营商等信息"}, + {Key: "idcard-info", Name: "身份证归属地", Desc: "查询身份证归属地、性别、出生日期等信息"}, + {Key: "phone-location", Name: "手机号归属地", Desc: "查询手机号码归属地、运营商等信息"}, + {Key: "beijing-time", Name: "北京时间查询", Desc: "获取当前北京时间"}, + {Key: "bank-card", Name: "银行卡识别", Desc: "识别银行卡发卡行与卡种"}, + {Key: "plate-parse", Name: "车牌号解析", Desc: "解析车牌类型与归属地"}, + {Key: "money-to-chinese", Name: "金额大写转换", Desc: "阿拉伯数字转中文大写金额"}, + {Key: "password-strength", Name: "密码强度检测", Desc: "检测密码安全性并给出建议"}, + {Key: "days-between-dates", Name: "日期间隔计算", Desc: "计算两个日期相差的天数"}, + {Key: "file-size-format", Name: "文件大小格式化", Desc: "支持各种文件单位之间的相互转换"}, + {Key: "text-stats", Name: "文本字数统计", Desc: "统计字符数、单词数、行数"}, + {Key: "history-today", Name: "历史今天查询", Desc: "查询历史上指定日期的重要事件"}, + {Key: "ten-why", Name: "十万个为什么", Desc: "百科类问答查询,支持分类检索和关键词搜索"}, + {Key: "jf-words", Name: "近义词反义词", Desc: "查询词语的近义词和反义词,适合批量替换场景"}, + {Key: "duilian", Name: "经典对联查询", Desc: "随机获取一句经典中华文化对联句子"}, + {Key: "gjdm", Name: "古今地名查询", Desc: "查询古代地名对应的现代城市及沿革变迁说明"}, + {Key: "bcgm", Name: "本草纲目查询", Desc: "按药材名模糊查询《本草纲目》原文及功效主治"}, + {Key: "idiom-quiz", Name: "成语填字", Desc: "获取成语填空题目、答案、干扰项及成语解释"}, + {Key: "poem-fill", Name: "诗词填空", Desc: "获取古诗词填空题目、作者、答案及干扰项"}, + {Key: "fairytales", Name: "童话故事", Desc: "根据ID查询经典童话故事全文内容"}, + {Key: "caihongpi", Name: "彩虹屁", Desc: "随机生成一条夸张走心的夸人语录"}, + {Key: "joke", Name: "趣味笑话", Desc: "随机返回一条轻松幽默的笑话段子"}, + {Key: "pinyin", Name: "汉字转拼音", Desc: "将中文汉字转换为带声调的标准拼音"}, + {Key: "everyday", Name: "每日英语", Desc: "随机返回一句经典英文句子+中文释义+出处"}, + {Key: "decide", Name: "常识判断题", Desc: "随机返回生活常识判断题,含答案与解析"}, + {Key: "woman", Name: "女性新闻", Desc: "聚合女性关注的职场、健康、时尚新闻资讯"}, + {Key: "pyqwenan", Name: "朋友圈文案", Desc: "随机返回适合发朋友圈的走心短句"}, + {Key: "healthskill", Name: "健康小妙招", Desc: "提供饮食、睡眠、运动、减压等养生建议"}, + {Key: "jtwfcode", Name: "违章代码查询", Desc: "查询交通违法代码对应的扣分、罚款、违法行为"}, + {Key: "areanews", Name: "地区新闻", Desc: "全国各省市地方新闻,支持按地区筛选检索"}, + {Key: "yuanqu", Name: "元曲三百首", Desc: "随机返回元曲名句,含作者、曲牌、注释、译文"}, + {Key: "bmi", Name: "BMI标准体重", Desc: "根据身高体重计算BMI指数、健康评分、标准体重"}, + {Key: "zhongyao", Name: "中药大全", Desc: "查询中草药别名、性味、功效、用法用量等信息"}, + {Key: "yaopin", Name: "药品说明书", Desc: "查询中西药说明书、适应症、禁忌、用法用量"}, + {Key: "slogan", Name: "猜广告语", Desc: "随机返回广告语,含谜面与品牌答案"}, + {Key: "zimi", Name: "猜字谜", Desc: "随机返回传统字谜,附谜底与解析"}, + {Key: "dujitang", Name: "毒鸡汤", Desc: "随机返回扎心毒鸡汤语录,清醒又真实"}, + {Key: "one", Name: "ONE一个", Desc: "每日同步一句精美短句,支持指定日期与随机"}, + {Key: "proverb", Name: "文化谚语", Desc: "随机返回中国传统谚语,富含生活哲理智慧"}, + {Key: "caichengyu", Name: "猜成语", Desc: "根据提示猜成语,返回谜底、拼音、出处、释义"}, + {Key: "qiaomen", Name: "生活小窍门", Desc: "提供清洁、收纳、烹饪、省电等实用生活技巧"}, + {Key: "riddle", Name: "谜语大全", Desc: "随机返回传统谜语,含谜面、谜底及解释"}, + // ─── 新增天行SDK工具 ─── + {Key: "jixiong", Name: "吉凶测试", Desc: "输入数字测试吉凶寓意"}, + {Key: "surname", Name: "姓氏起源", Desc: "查询百家姓起源、郡望、堂号等信息"}, + {Key: "healthtip", Name: "健康小贴士", Desc: "随机返回一条实用健康养生小贴士"}, + {Key: "nutrient", Name: "营养素查询", Desc: "查询食物营养成分、功效与推荐摄入量"}, + {Key: "fxrate", Name: "汇率查询", Desc: "查询主要货币对的实时汇率"}, + {Key: "bulletin", Name: "新闻简报", Desc: "获取最新热门新闻简报摘要"}, + {Key: "caizimi", Name: "彩字谜", Desc: "随机返回彩字谜题目"}, + {Key: "hotlajifenlei", Name: "热搜垃圾分类", Desc: "获取热搜垃圾分类榜单"}, + {Key: "oilprice", Name: "实时油价", Desc: "查询全国各地最新油价"}, + {Key: "chengyujielong", Name: "成语接龙", Desc: "输入成语进行接龙游戏"}, + {Key: "weibohot", Name: "微博热搜", Desc: "获取微博实时热搜榜单"}, + {Key: "domain", Name: "域名解析", Desc: "查询域名Whois信息与解析记录"}, + {Key: "scenic", Name: "景点查询", Desc: "查询全国各地旅游景点信息"}, + {Key: "mingyan", Name: "名言警句", Desc: "随机返回古今中外经典名言"}, + {Key: "tianqishiju", Name: "天气诗句", Desc: "根据天气返回对应古诗词名句"}, + {Key: "jieqi", Name: "节气查询", Desc: "查询二十四节气详情与习俗"}, + {Key: "blood", Name: "血型配对", Desc: "查询血型之间的性格配对分析"}, + {Key: "zodiac", Name: "生肖配对", Desc: "查询十二生肖之间的配对分析"}, + {Key: "saylove", Name: "情话大全", Desc: "随机返回一句浪漫甜蜜情话"}, + {Key: "star", Name: "星座查询", Desc: "查询十二星座详细信息"}, + {Key: "nethot", Name: "网络热搜", Desc: "获取全网实时热搜排行榜"}, + {Key: "ensentence", Name: "英文句子", Desc: "随机返回经典英文句子"}, + {Key: "verse", Name: "诗词名句", Desc: "随机返回经典诗词名句"}, + {Key: "poetry", Name: "古诗词", Desc: "按条件搜索古诗词"}, + {Key: "poetries", Name: "古诗词集", Desc: "获取古诗词集合数据"}, + {Key: "pitlishi", Name: "谐音历史", Desc: "查询谐音梗相关的历史知识"}, + {Key: "mnpara", Name: "美文段落", Desc: "随机返回经典美文段落"}, + {Key: "skl", Name: "十苦乐", Desc: "随机返回十苦乐相关内容"}, + {Key: "sentence", Name: "唯美句子", Desc: "随机返回唯美文艺短句"}, + {Key: "enwords", Name: "英语单词", Desc: "查询英语单词释义与用法"}, + {Key: "lexicon", Name: "词典查询", Desc: "查询词语释义、近义词、反义词"}, + {Key: "lunar", Name: "农历查询", Desc: "查询中国老黄历信息"}, + {Key: "xingzuo", Name: "星座配对", Desc: "查询两个星座的配对指数"}, + {Key: "godreply", Name: "神回复", Desc: "随机返回搞笑神回复段子"}, + {Key: "xiehou", Name: "歇后语", Desc: "随机返回经典歇后语"}, + {Key: "rkl", Name: "绕口令", Desc: "随机返回经典绕口令"}, + {Key: "cname", Name: "起名大全", Desc: "根据姓氏推荐好听的名字"}, + {Key: "shares", Name: "股票查询", Desc: "查询股票实时行情数据"}, + {Key: "hotword", Name: "热词", Desc: "获取最新网络热词与解释"}, + {Key: "cityriddle", Name: "城市谜语", Desc: "猜城市名称趣味谜语"}, + {Key: "wenda", Name: "知识问答", Desc: "随机返回趣味知识问答"}, + {Key: "chengyu", Name: "成语词典", Desc: "查询成语释义、出处、造句"}, + {Key: "naowan", Name: "脑筋急转弯", Desc: "随机返回脑筋急转弯题目"}, + {Key: "dob", Name: "生日书", Desc: "根据生日查询性格与命运分析"}, + {Key: "dictum", Name: "格言", Desc: "随机返回经典格言警句"}, + {Key: "dream", Name: "周公解梦", Desc: "查询梦境解析与寓意"}, + {Key: "caipu", Name: "菜谱大全", Desc: "查询菜谱做法与食材信息"}, + {Key: "wxnew", Name: "微信精选", Desc: "获取微信热门文章精选"}, + {Key: "constellation", Name: "星座运势", Desc: "查询十二星座今日/明日/本周/月度运势"}, + {Key: "fanyi", Name: "在线翻译", Desc: "支持多语种互译,自动识别源语言"}, + {Key: "holiday", Name: "节假日查询", Desc: "查询指定年份公历、农历、节假日信息"}, + {Key: "garbage", Name: "垃圾分类查询", Desc: "输入垃圾名称查询分类及投放说明"}, + {Key: "zone", Name: "地区搜索查询", Desc: "查询省市区三级行政区划代码及名称"}, + // ─── 第二批新增工具 ─── + {Key: "timezone", Name: "时区查询", Desc: "查询全球各时区的当前时间"}, + {Key: "duoyinzi", Name: "多音字查询", Desc: "查询汉字的多音字及不同读音"}, + {Key: "dailytel", Name: "全国常用电话", Desc: "汇总政府机关、银行、电商、快递、学校等全国常用机构对外联系电话。"}, + {Key: "obdcode", Name: "OBD故障码", Desc: "查询汽车OBD故障码含义及解决方案"}, + {Key: "chaizi", Name: "汉字拆解", Desc: "查询汉字的拆字结构及含义"}, + {Key: "huayu", Name: "华语句子", Desc: "随机返回优美华语句子"}, + {Key: "moodpoetry", Name: "心情诗词", Desc: "根据心情推荐古诗词"}, + {Key: "pcterm", Name: "电脑术语", Desc: "查询电脑相关术语解释"}, + {Key: "targa", Name: "塔罗牌", Desc: "塔罗牌占卜解读"}, + {Key: "duishici", Name: "对诗词", Desc: "诗词对仗游戏"}, + {Key: "msdl", Name: "民乐歌词", Desc: "中国传统民乐歌词查询"}, + {Key: "shijing", Name: "诗经", Desc: "《诗经》原文及释义"}, + {Key: "chepai", Name: "车牌查询", Desc: "查询车牌归属地及限行信息"}, + {Key: "flmj", Name: "法律名句", Desc: "法律法规经典名言"}, + {Key: "xhzd", Name: "新华字典", Desc: "汉字部首、笔画、释义查询"}, + {Key: "telecode", Name: "电话区号", Desc: "查询国内外电话区号"}, + {Key: "charconvert", Name: "字符转换", Desc: "各种字符编码格式转换"}, + {Key: "checkipv6", Name: "IPv6检测", Desc: "检测IPv6地址格式及归属"}, + {Key: "worldtime", Name: "世界时间", Desc: "查询全球主要城市当前时间"}, + {Key: "wxhottopic", Name: "微信热点", Desc: "获取微信实时热点话题"}, + {Key: "zmsc", Name: "字明说", Desc: "汉字谐音趣味解读"}, + {Key: "hsjz", Name: "失恋分手句子", Desc: "随机返回失恋分手相关的经典句子"}, + {Key: "gjmj", Name: "国际民调", Desc: "国际民调数据查询"}, + {Key: "lzmy", Name: "励志名言", Desc: "励志名言警句"}, + {Key: "baiketiku", Name: "百科题库", Desc: "百科知识问答题库"}, + {Key: "dialogue", Name: "对话生成", Desc: "智能对话内容生成"}, + {Key: "tiangou", Name: "舔狗日记", Desc: "搞笑舔狗日记"}, + {Key: "robotlog", Name: "机器人日志", Desc: "机器人运行日志查询"}, + {Key: "aqi", Name: "空气质量", Desc: "查询城市空气质量指数"}, + {Key: "addressparse", Name: "地址解析", Desc: "智能地址格式化与解析"}, + {Key: "pet", Name: "宠物百科", Desc: "宠物饲养知识大全"}, + {Key: "hotreview", Name: "热点评论", Desc: "热点事件网友评论"}, + {Key: "anslajifenlei", Name: "按师垃圾分类", Desc: "按垃圾分类指南"}, + {Key: "douyinhot", Name: "抖音热搜", Desc: "获取抖音实时热搜榜单"}, + {Key: "zaowanwords", Name: "早安晚安语", Desc: "适合发朋友圈的早安晚安文案"}, + {Key: "wananwords", Name: "晚安语", Desc: "温馨晚安问候语"}, + {Key: "areadata", Name: "地区数据", Desc: "各地区统计数据分析"}, + {Key: "mgjuzi", Name: "美工句子", Desc: "美工设计灵感句子"}, + {Key: "songci", Name: "宋词", Desc: "宋词原文及赏析"}, + {Key: "story", Name: "故事大全", Desc: "各类故事集锦"}, + {Key: "qingshi", Name: "情诗", Desc: "经典情诗词句"}, + {Key: "lajifenlei", Name: "垃圾分类", Desc: "垃圾分类查询指南"}, + {Key: "hanzipinyin", Name: "汉字拼音", Desc: "汉字拼音查询"}, + {Key: "shorthistory", Name: "简明历史", Desc: "历史事件简明介绍"}, + } +} + +// SupportedKeys 返回所有支持的 toolKey(用于校验) +func (s *ToolboxService) SupportedKeys() map[string]bool { + keys := make(map[string]bool, len(s.processors)) + for k := range s.processors { + keys[k] = true + } + return keys +} + +// RegisterTool 动态注册工具(可选,方便扩展) +func (s *ToolboxService) RegisterTool(key string, processor ToolProcessor) error { + if _, exists := s.processors[key]; exists { + return fmt.Errorf("工具 %s 已存在", key) + } + s.processors[key] = processor + return nil +} + +// stripHTMLTags 移除HTML标签,保留纯文本 +func stripHTMLTags(html string) string { + // 移除所有HTML标签 + re := regexp.MustCompile(`<[^>]*>`) + text := re.ReplaceAllString(html, "") + + // 移除多余的空白字符 + text = strings.TrimSpace(text) + text = regexp.MustCompile(`\s+`).ReplaceAllString(text, " ") + + return text +} + +// cleanHTMLContent 清理HTML内容,智能处理换行 +func cleanHTMLContent(html string) string { + if html == "" { + return html + } + + // 移除`).ReplaceAllString(html, "") + + // 移除`).ReplaceAllString(html, "") + + // 移除所有HTML标签 + re := regexp.MustCompile(`<[^>]*>`) + text := re.ReplaceAllString(html, "") + + // 替换HTML实体 + entityMap := map[string]string{ + "<": "<", + ">": ">", + "&": "&", + """: "\"", + " ": " ", + "'": "'", + "”": "”", + "“": "“", + "…": "…", + } + for entity, char := range entityMap { + text = strings.ReplaceAll(text, entity, char) + } + + // 规范化空白字符:将连续空白替换为单个空格 + text = regexp.MustCompile(`\s+`).ReplaceAllString(text, " ") + + // 清理首尾空白 + text = strings.TrimSpace(text) + + return text +} diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index e45c658..2aef863 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -4,6 +4,7 @@ import ( "qnc-server/app/main/api/internal/config" "qnc-server/app/main/api/internal/middleware" "qnc-server/app/main/api/internal/service" + tianxingjuhe "qnc-server/app/main/api/internal/service/tianxingjuhe_sdk" tianyuanapi "qnc-server/app/main/api/internal/service/tianyuanapi_sdk" "qnc-server/app/main/model" "time" @@ -99,6 +100,7 @@ type ServiceContext struct { DictService *service.DictService ImageService *service.ImageService AuthorizationService *service.AuthorizationService + ToolboxService *service.ToolboxService } // NewServiceContext 创建服务上下文 @@ -201,6 +203,23 @@ func NewServiceContext(c config.Config) *ServiceContext { dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel) imageService := service.NewImageService() + tianxingjuheTimeout := c.Tianxingjuhe.Timeout + if tianxingjuheTimeout <= 0 { + tianxingjuheTimeout = 30 + } + tianxingjuheClient, tjErr := tianxingjuhe.NewClient(tianxingjuhe.Config{ + BaseURL: c.Tianxingjuhe.URL, + Key: c.Tianxingjuhe.Key, + Timeout: tianxingjuheTimeout, + }) + if tjErr != nil { + logx.Errorf("初始化天行聚合API失败: %+v", tjErr) + } + var toolboxService *service.ToolboxService + if tianxingjuheClient != nil { + toolboxService = service.NewToolboxService(tianxingjuheClient) + } + // ============================== 异步任务服务 ============================== asynqServer := asynq.NewServer( asynq.RedisClientOpt{Addr: c.CacheRedis[0].Host, Password: c.CacheRedis[0].Pass}, @@ -296,6 +315,7 @@ func NewServiceContext(c config.Config) *ServiceContext { DictService: dictService, ImageService: imageService, AuthorizationService: authorizationService, + ToolboxService: toolboxService, } } diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index 555f293..47d74ae 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -2199,6 +2199,10 @@ type RoleListItem struct { MenuIds []string `json:"menu_ids"` // 关联的菜单ID列表 } +type ServeUploadFileReq struct { + Name string `path:"name"` +} + type ShortLinkRedirectResp struct { } @@ -2251,6 +2255,16 @@ type TeamStatisticsResp struct { MonthNewMembers int64 `json:"month_new_members"` // 本月新增成员 } +type ToolboxQueryReq struct { + ToolKey string `json:"tool_key"` + Params map[string]interface{} `json:"params,optional"` +} + +type ToolboxQueryResp struct { + ToolKey string `json:"tool_key"` + Result map[string]interface{} `json:"result"` +} + type UpdateMenuReq struct { Id string `path:"id"` // 菜单ID Pid *string `json:"pid,optional"` // 父菜单ID @@ -2334,6 +2348,14 @@ type UpgradeSubordinateResp struct { Success bool `json:"success"` } +type UploadImageReq struct { + ImageBase64 string `json:"image_base64"` +} + +type UploadImageResp struct { + Url string `json:"url"` +} + type User struct { Id string `json:"id"` Mobile string `json:"mobile"`