From 3c90144d51673d98a968f42c6a1397fe04c069e5 Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Thu, 29 Jan 2026 16:51:11 +0800 Subject: [PATCH] f --- .../processors/qygl/qygl3f8e_processor.go | 1150 ++++++----------- 1 file changed, 387 insertions(+), 763 deletions(-) diff --git a/internal/domains/api/services/processors/qygl/qygl3f8e_processor.go b/internal/domains/api/services/processors/qygl/qygl3f8e_processor.go index 0db5a7e..036a4f0 100644 --- a/internal/domains/api/services/processors/qygl/qygl3f8e_processor.go +++ b/internal/domains/api/services/processors/qygl/qygl3f8e_processor.go @@ -5,14 +5,10 @@ import ( "encoding/json" "errors" "fmt" - "math/rand" - "regexp" "sort" "strconv" "strings" "sync" - "time" - "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/shared/logger" @@ -122,11 +118,12 @@ type CompanyInfo struct { Name string CreditCode string Relationship []string - RelationshipVal int // 关系权重值 - RelationCount int // 关系数量 - AdminPenalty int // 行政处罚数量 - Executed int // 被执行人数量 - Dishonest int // 失信被执行人数量 + RelationshipVal int // 关系权重值 + RelationCount int // 关系数量 + AdminPenalty int // 行政处罚数量 + Executed int // 被执行人数量 + Dishonest int // 失信被执行人数量 + RegCapValue float64 // 注册资本数值(用于排序,单位统一为“万”,含“亿”时换算为万) } // parseCompaniesFrom6S1BResponse 从QYGL6S1B响应中解析企业列表 @@ -248,6 +245,10 @@ func parseCompaniesFrom6S1BResponse(response []byte) ([]CompanyInfo, error) { relationship = append(relationship, "tm") } + // 解析 regCap 用于排序(优选注册资本最高的企业去查询详情) + regCapStr := companyJson.Get("regCap").String() + regCapValue := parseRegCapValue(regCapStr) + companies = append(companies, CompanyInfo{ Index: i, Data: companyJson, @@ -259,6 +260,7 @@ func parseCompaniesFrom6S1BResponse(response []byte) ([]CompanyInfo, error) { AdminPenalty: adminPenalty, Executed: executed, Dishonest: dishonest, + RegCapValue: regCapValue, }) } @@ -292,13 +294,37 @@ func sortCompaniesByPriority(companies []CompanyInfo) []CompanyInfo { return sorted[i].RelationshipVal > sorted[j].RelationshipVal } - // 最后按关系数量排序 - return sorted[i].RelationCount > sorted[j].RelationCount + // 然后按关系数量排序 + if sorted[i].RelationCount != sorted[j].RelationCount { + return sorted[i].RelationCount > sorted[j].RelationCount + } + + // 最后按注册资本排序:优选 regCap 价值最高的企业去查询详情 + return sorted[i].RegCapValue > sorted[j].RegCapValue }) return sorted } +// BasicInfo 企业基础信息,供前端 CQYGL3F8E 展示用(BACKEND_MAPPING_SPEC 2.2) +type BasicInfo struct { + RegStatus string `json:"regStatus"` + RegCapital string `json:"regCapital"` + RegCapitalCurrency string `json:"regCapitalCurrency"` + CreditCode string `json:"creditCode"` + RegNumber string `json:"regNumber"` + EntType string `json:"entType"` + Industry string `json:"industry"` + EstiblishTime string `json:"estiblishTime"` + RegInstitute string `json:"regInstitute"` + ApprovedTime string `json:"approvedTime,omitempty"` + LegalPersonName string `json:"legalPersonName,omitempty"` + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Website string `json:"website,omitempty"` + RegAddress string `json:"regAddress,omitempty"` +} + // EnrichedCompanyInfo 增强的企业信息 type EnrichedCompanyInfo struct { CompanyInfo @@ -453,38 +479,38 @@ func enrichCompaniesWithDetails(ctx context.Context, companies []CompanyInfo, id detailMu.Unlock() }() - // 调用QYGL66SL- 企业涉诉信息 - // detailWg.Add(1) - // go func() { - // defer detailWg.Done() - // result := callQYGL66SLProcessorSafely(ctx, comp.CreditCode, comp.Name, deps) + // 调用QYGL5S1I- 企业涉诉信息 + detailWg.Add(1) + go func() { + defer detailWg.Done() + result := callQYGL5S1IProcessorSafely(ctx, comp.CreditCode, comp.Name, deps) - // enriched.LawsuitInfo = result - // // QYGL66SL返回的是特殊格式,需要检查是否有数据 - // hasData := false - // if resultMap, ok := result.(map[string]interface{}); ok { - // for _, v := range resultMap { - // if vMap, ok := v.(map[string]interface{}); ok { - // if msg, ok := vMap["msg"].(string); ok && msg == "成功" { - // hasData = true - // break - // } - // } - // } - // } + enriched.LawsuitInfo = result + // QYGL5S1I返回的是特殊格式,需要检查是否有数据 + hasData := false + if resultMap, ok := result.(map[string]interface{}); ok { + for _, v := range resultMap { + if vMap, ok := v.(map[string]interface{}); ok { + if msg, ok := vMap["msg"].(string); ok && msg == "成功" { + hasData = true + break + } + } + } + } - // detailMu.Lock() - // status := "success" - // if !hasData { - // status = "empty" - // } - // processorResults = append(processorResults, processorResult{ - // processorType: "QYGL66SL", - // status: status, - // hasData: hasData, - // }) - // detailMu.Unlock() - // }() + detailMu.Lock() + status := "success" + if !hasData { + status = "empty" + } + processorResults = append(processorResults, processorResult{ + processorType: "QYGL5S1I", + status: status, + hasData: hasData, + }) + detailMu.Unlock() + }() // 调用QYGL7D9A - 欠税公告 detailWg.Add(1) @@ -647,579 +673,347 @@ func callProcessorSafely(ctx context.Context, processorType, entCode string, dep } // callProcessorSafely 安全调用处理器 -func callQYGL66SLProcessorSafely(ctx context.Context, entCode string, entName string, deps *processors.ProcessorDependencies) interface{} { - // 使用 zap.L() 获取全局日志器,它会使用 zap.ReplaceGlobals 设置的日志器 - // 这样可以确保使用配置文件中的按级别分文件设置 - log := logger.L() - - // 参数验证:确保必填字段不为空 - if entName == "" { - log.Warn("QYGL66SL处理器调用失败:企业名称为空", - zap.String("ent_code", entCode), - ) - return buildEmptyQYGL66SLResponse() - } - - // 使用 QYGL66SLReq 的 json 键名:ent_code, ent_name, auth_date, auth_authorize_file_code - // 处理器内部会再映射为 API 的 orgName/uscc/inquiredAuth/authAuthorizeFileCode - authDate := generateAuthDateRange() - authFileCode := generateAuthAuthorizeFileCode() - - params := map[string]interface{}{ - "ent_code": entCode, // 可选字段,可以为空 - "ent_name": entName, - "auth_date": authDate, - "auth_authorize_file_code": authFileCode, - } - - paramsBytes, err := json.Marshal(params) +func callQYGL5S1IProcessorSafely(ctx context.Context, entCode string, entName string, deps *processors.ProcessorDependencies) interface{} { + paramsBytes, err := json.Marshal(map[string]interface{}{ + "ent_code": entCode, + "ent_name": entName, + }) if err != nil { - log.Error("QYGL66SL处理器参数序列化失败", - zap.Error(err), - zap.String("ent_code", entCode), - zap.String("ent_name", entName), - zap.String("auth_date", authDate), - zap.String("auth_authorize_file_code", authFileCode), - ) - return buildEmptyQYGL66SLResponse() + return map[string]interface{}{} } - - response, err := ProcessQYGL66SLRequest(ctx, paramsBytes, deps) + response, err := ProcessQYGL5S1IRequest(ctx, paramsBytes, deps) if err != nil { - // 检查错误类型,区分不同的失败原因并记录详细日志 - if errors.Is(err, processors.ErrInvalidParam) { - log.Warn("QYGL66SL处理器参数验证失败", - zap.Error(err), - zap.String("ent_code", entCode), - zap.String("ent_name", entName), - zap.String("auth_date", authDate), - zap.String("auth_authorize_file_code", authFileCode), - zap.String("error_type", "ErrInvalidParam"), - zap.String("可能原因", "auth_date格式不符合要求或ent_name格式不正确"), - ) - return buildEmptyQYGL66SLResponse() - } else if errors.Is(err, processors.ErrNotFound) { - log.Error("QYGL66SL查询结果为空(正常业务情况)", - zap.String("ent_code", entCode), - zap.String("ent_name", entName), - zap.String("error_type", "ErrNotFound"), - ) - return buildEmptyQYGL66SLResponse() - } else if errors.Is(err, processors.ErrDatasource) { - log.Error("QYGL66SL数据源错误", - zap.Error(err), - zap.String("ent_code", entCode), - zap.String("ent_name", entName), - zap.String("error_type", "ErrDatasource"), - ) - return buildEmptyQYGL66SLResponse() - } else { - log.Error("QYGL66SL处理器调用失败(系统错误)", - zap.Error(err), - zap.String("ent_code", entCode), - zap.String("ent_name", entName), - zap.String("auth_date", authDate), - zap.String("error_type", "ErrSystem"), - zap.String("可能原因", "网络问题、超时、API调用失败等"), - ) - return buildEmptyQYGL66SLResponse() - } + return map[string]interface{}{} } - - // 解析响应 - var result interface{} - if err := json.Unmarshal(response, &result); err != nil { - // 只记录前200字符的响应预览 - responsePreview := string(response) - if len(responsePreview) > 200 { - responsePreview = responsePreview[:200] + "..." - } - log.Error("QYGL66SL响应解析失败", - zap.Error(err), - zap.String("ent_code", entCode), - zap.String("ent_name", entName), - zap.String("response_preview", responsePreview), - zap.String("可能原因", "API返回了非JSON格式的数据"), - ) - return buildEmptyQYGL66SLResponse() - } - - resultBytes, _ := json.Marshal(result) - - // 处理 entout 数据 - entoutWrapped := processEntoutData(resultBytes) - - // 处理 xgbzxr 数据 - xgbzxrWrapped := processXgbzxrData(resultBytes) - - // 处理 sxbzxr 数据 - sxbzxrWrapped := processSxbzxrData(resultBytes) - - wrappedResult := map[string]interface{}{ - "entout": entoutWrapped, - "sxbzxr": sxbzxrWrapped, - "xgbzxr": xgbzxrWrapped, - } - - return wrappedResult + return normalizeQYGL5S1IBzxr(response) } -// buildEmptyQYGL66SLResponse 构建空的响应 -func buildEmptyQYGL66SLResponse() map[string]interface{} { - return map[string]interface{}{ - "entout": map[string]interface{}{ - "msg": "没有找到", - }, - "sxbzxr": map[string]interface{}{ - "msg": "没有找到", - }, - "xgbzxr": map[string]interface{}{ - "msg": "没有找到", - }, +// normalizeQYGL5S1IBzxr 将 QYGL5S1I 响应中的 xgbzxr 统一为 { "data": { "xgbzxr": [...] } } 或空数组时附带 msg 结构 +func normalizeQYGL5S1IBzxr(response []byte) interface{} { + var m map[string]interface{} + if err := json.Unmarshal(response, &m); err != nil { + return map[string]interface{}{} } + // 只规范化 xgbzxr,sxbzxr 保持原始结构(例如仅返回 { "msg": "没有找到" }) + m = normalizeQYGL5S1IOneBzxr(m, "xgbzxr") + // 规范化 entout 结构,生成 entout.data.civil / criminal 等及汇总 count 字段 + m = normalizeQYGL5S1IEntout(m) + return m } -// processEntoutData 处理 entout 数据 -func processEntoutData(resultBytes []byte) map[string]interface{} { - entoutResult := gjson.GetBytes(resultBytes, "entout") - if !entoutResult.Exists() || entoutResult.Raw == "{}" || entoutResult.Raw == "null" { - return map[string]interface{}{ - "msg": "没有找到", - } - } - - var entoutData map[string]interface{} - if err := json.Unmarshal([]byte(entoutResult.Raw), &entoutData); err != nil { - return map[string]interface{}{ - "msg": "没有找到", - } - } - - // 如果 entoutData 包含 "data" 字段,提取 data 字段的内容 - // 因为有些响应格式是 { "data": { "civil": {...} } },需要提取内部的 data - var actualData map[string]interface{} - if dataField, ok := entoutData["data"].(map[string]interface{}); ok { - actualData = dataField - } else { - // 如果没有 data 字段,直接使用 entoutData(可能已经是 data 的内容) - actualData = entoutData - } - - // 清理数据,将 "-" 转换为合适的默认值 - cleanedData := cleanEntoutData(actualData) - - // 检查是否有有效数据 - hasData := hasValidEntoutData(cleanedData) - if !hasData { - return map[string]interface{}{ - "msg": "没有找到", - } - } - - // 构建返回格式 - entoutWrapped := map[string]interface{}{ - "msg": "成功", - "data": cleanedData, - } - - return entoutWrapped -} - -// processArrayData 统一处理数组数据(xgbzxr 和 sxbzxr 使用相同的处理逻辑) -func processArrayData(resultBytes []byte, fieldName string) map[string]interface{} { - arrayResult := gjson.GetBytes(resultBytes, fieldName) - if !arrayResult.Exists() || !arrayResult.IsArray() { - return map[string]interface{}{ - "msg": "没有找到", - } - } - - var dataList []map[string]interface{} - for _, item := range arrayResult.Array() { - var itemMap map[string]interface{} - if err := json.Unmarshal([]byte(item.Raw), &itemMap); err == nil { - // 清理数据 - itemMap = cleanMapData(itemMap) - // 检查是否为空数据(所有字段都是空值) - if !isEmptyMap(itemMap) { - dataList = append(dataList, itemMap) - } - } - } - - if len(dataList) == 0 { - return map[string]interface{}{ - "msg": "没有找到", - } - } - - return map[string]interface{}{ - "msg": "成功", - "data": map[string]interface{}{ - fieldName: dataList, - }, - } -} - -// processXgbzxrData 处理 xgbzxr 数据 -func processXgbzxrData(resultBytes []byte) map[string]interface{} { - return processArrayData(resultBytes, "xgbzxr") -} - -// processSxbzxrData 处理 sxbzxr 数据 -func processSxbzxrData(resultBytes []byte) map[string]interface{} { - return processArrayData(resultBytes, "sxbzxr") -} - -// cleanEntoutData 清理 entout 数据,将 "-" 转换为合适的默认值 -func cleanEntoutData(data map[string]interface{}) map[string]interface{} { - cleaned := make(map[string]interface{}) - - // 定义需要特殊处理的字段 - specialFields := map[string]func(interface{}) interface{}{ - "count": cleanCountData, - "crc": cleanNumericValue, - } - - // 定义需要保留结构的字段(即使清理后为空也要保留) - structuralFields := map[string]bool{ - "cases_tree": true, - "civil": true, - "criminal": true, - "administrative": true, - "bankrupt": true, - "implement": true, - "preservation": true, - "count": true, - "crc": true, - } - - // 处理所有字段,确保所有字段都被正确映射 - for key, value := range data { - if value == nil { - // 跳过 nil 值,但保留空对象和空数组 - continue - } - - if handler, ok := specialFields[key]; ok { - cleaned[key] = handler(value) - } else { - // 对于其他字段(如 cases_tree, civil, implement 等),使用 cleanValue 处理 - cleanedValue := cleanValue(value) - // 如果是结构字段,即使清理后为空也保留 - if structuralFields[key] { - cleaned[key] = cleanedValue - } else if !isEmptyValue(cleanedValue) { - // 非结构字段,只有当清理后的值不为空时才添加 - cleaned[key] = cleanedValue - } - } - } - - return cleaned -} - -// isNumericField 根据字段命名规律判断是否为数值字段 -// 规律分析: -// - money_ 或 count_ 开头 → 数值类型 -// - _level 或 _gj 结尾 → 数值类型(但需排除 n_ 开头的字符串字段) -// - n_ 开头的字段需要更精确判断: -// - 包含 "je"(金额相关)→ 数值类型 -// - 包含 "level"(等级)→ 数值类型 -// - 包含 "gj"(估计)→ 数值类型 -// - 包含 "crc"(变更码)→ 数值类型 -// - 包含 "sqzxbdje"、"sjdwje"、"wzxje"、"sqbqse"(金额相关)→ 数值类型 -// - 其他 n_ 开头 → 字符串类型(如 n_jaay, n_laay, n_jafs, n_ssdw, n_slcx 等) -func isNumericField(fieldName string) bool { - // 特殊情况 - if fieldName == "stage_type" || fieldName == "case_type" || fieldName == "money_wei_percent" { - return true - } - - // money_ 或 count_ 开头的字段通常是数值类型 - if strings.HasPrefix(fieldName, "money_") || strings.HasPrefix(fieldName, "count_") { - return true - } - - // _level 或 _gj 结尾的字段通常是数值类型 - if strings.HasSuffix(fieldName, "_level") || strings.HasSuffix(fieldName, "_gj") { - return true - } - - // n_ 开头的字段需要精确判断 - if strings.HasPrefix(fieldName, "n_") { - // 数值类型的 n_ 字段特征 - numericPatterns := []string{ - "je", // 金额相关:n_jabdje, n_qsbdje, n_ccxzxje, n_pcpcje, n_bqqpcje, n_fzje - "crc", // 变更码:n_crc - "sqzxbdje", // 申请执行标的金额 - "sjdwje", // 实际到位金额 - "wzxje", // 未执行金额 - "sqbqse", // 申请保全数额 - } - for _, pattern := range numericPatterns { - if strings.Contains(fieldName, pattern) { - return true - } - } - // 如果 n_ 开头但不包含上述模式,通常是字符串类型 - return false - } - - return false -} - -// cleanCountData 清理 count 数据,数值字段将 "-" 转换为 0,字符串字段转换为空字符串 -func cleanCountData(value interface{}) interface{} { - if value == nil { - return nil - } - - countMap, ok := value.(map[string]interface{}) +// normalizeQYGL5S1IOneBzxr 将 m[key] 规范为 { "data": { key: [...] }, "msg": "..." } +func normalizeQYGL5S1IOneBzxr(m map[string]interface{}, key string) map[string]interface{} { + raw, ok := m[key] if !ok { - return cleanValue(value) + return m } - - cleaned := make(map[string]interface{}) - for key, val := range countMap { - if isNumericField(key) { - cleaned[key] = cleanNumericValue(val) - } else { - // 字符串字段(如 area_stat, ay_stat, jafs_stat, larq_stat) - cleaned[key] = cleanStringValue(val) - } + objMap, ok := raw.(map[string]interface{}) + if !ok { + return m } - - return cleaned -} - -// cleanNumericValue 清理数值,将 "-" 转换为 0 -func cleanNumericValue(value interface{}) interface{} { - if value == nil { - return 0 - } - if str, ok := value.(string); ok { - if str == "-" || str == "" { - return 0 - } - // 尝试转换为整数 - if num, err := strconv.ParseFloat(str, 64); err == nil { - return int(num) - } - return 0 - } - // 如果已经是数字类型,直接返回 - if num, ok := value.(float64); ok { - return int(num) - } - return value -} - -// cleanStringValue 清理字符串,将 "-" 转换为空字符串 -func cleanStringValue(value interface{}) interface{} { - if value == nil { - return "" - } - if str, ok := value.(string); ok { - if str == "-" { - return "" - } - return str - } - return value -} - -// cleanValue 清理单个值,将 "-" 转换为合适的默认值 -func cleanValue(value interface{}) interface{} { - switch v := value.(type) { - case string: - if v == "-" { - return "" - } - return v - case map[string]interface{}: - // 检查是否是案件类型的数据结构(包含 cases 和 count) - if cases, ok := v["cases"]; ok { - cleaned := make(map[string]interface{}) - // 清理 cases - cleaned["cases"] = cleanArrayData(cases.([]interface{})) - // 清理 count(如果存在) - if count, ok := v["count"]; ok { - cleaned["count"] = cleanCountData(count) + // 已有 data. 结构则不再处理 + if data, hasData := objMap["data"]; hasData { + if dataMap, ok := data.(map[string]interface{}); ok { + if _, hasKey := dataMap[key]; hasKey { + return m } - // 保留其他字段 - for key, val := range v { - if key != "cases" && key != "count" { - cleaned[key] = cleanValue(val) + } + } + // 从现有字段取出列表:优先 data.,否则顶层 + var list []interface{} + if data, hasData := objMap["data"]; hasData { + if dataMap, ok := data.(map[string]interface{}); ok { + if arr, ok := dataMap[key].([]interface{}); ok { + list = arr + } + } + } + if list == nil { + if arr, ok := objMap[key].([]interface{}); ok { + list = arr + } + } + if list == nil { + list = []interface{}{} + } + msg, _ := objMap["msg"].(string) + // 空结果时:只返回 msg 结构(例如 { "xgbzxr": { "msg": "没有找到" } }) + if len(list) == 0 { + if msg == "" { + msg = "没有找到" + } + m[key] = map[string]interface{}{ + "msg": msg, + } + return m + } + // 有数据时:返回带 data. 的结构,不带 msg + m[key] = map[string]interface{}{ + "data": map[string]interface{}{ + key: list, + }, + } + return m +} + +// normalizeQYGL5S1IEntout 将 QYGL5S1I 返回的 entout 映射为前端需要的结构: +// +// entout: { +// data: { +// civil: { cases: [...] }, +// criminal: { cases: [...] }, +// administrative: { cases: [...] }, +// implement: { cases: [...] }, +// bankrupt: { cases: [...] }, +// preservation: { cases: [...] }, +// jurisdict: { cases: [...] }, +// compensate: { cases: [...] }, +// count: <总案件数>, +// count_wei_total: <汇总未结案件数> +// } +// } +// +// 如果 entout 只有 msg(例如 { "msg": "没有找到" }),则保持原样。 +func normalizeQYGL5S1IEntout(m map[string]interface{}) map[string]interface{} { + raw, ok := m["entout"] + if !ok { + return m + } + entMap, ok := raw.(map[string]interface{}) + if !ok { + return m + } + + // 如果只有 msg,没有任何案件分类信息,则不做结构化转换 + hasCasesField := false + categoryKeys := []string{ + "civil", + "criminal", + "administrative", + "implement", + "bankrupt", + "preservation", + "jurisdict", + "compensate", + } + for _, key := range categoryKeys { + if v, ok := entMap[key]; ok { + if catMap, ok := v.(map[string]interface{}); ok { + if _, has := catMap["cases"]; has { + hasCasesField = true + break } } - return cleaned - } - return cleanMapData(v) - case []interface{}: - return cleanArrayData(v) - case float64: - // JSON 数字类型 - return v - case nil: - return nil - default: - return v - } -} - -// cleanMapData 清理 map 数据 -// 根据字段命名规律自动判断字段类型,减少硬编码 -func cleanMapData(data map[string]interface{}) map[string]interface{} { - cleaned := make(map[string]interface{}) - hasValidData := false - - // 定义需要保留的结构字段(即使为空也保留) - structuralFields := map[string]bool{ - "cases_tree": true, - "civil": true, - "criminal": true, - "administrative": true, - "bankrupt": true, - "implement": true, - "preservation": true, - } - - for key, value := range data { - var cleanedValue interface{} - // 根据字段命名规律判断是否为数值字段 - if isNumericField(key) { - cleanedValue = cleanNumericValue(value) - } else { - cleanedValue = cleanValue(value) - } - cleaned[key] = cleanedValue - - // 检查是否有有效数据 - if !isEmptyValue(cleanedValue) { - hasValidData = true - } else if structuralFields[key] { - // 结构字段即使为空也保留,并标记为有数据 - hasValidData = true } } - - // 如果所有字段都是空值且没有结构字段,返回空 map - if !hasValidData { - return make(map[string]interface{}) + if !hasCasesField { + // 例如 { "entout": { "msg": "没有找到" } },直接返回 + return m } - return cleaned -} + data := make(map[string]interface{}) + totalCases := 0 + totalWei := 0 -// cleanArrayData 清理数组数据 -func cleanArrayData(data []interface{}) []interface{} { - cleaned := make([]interface{}, 0) + for _, key := range categoryKeys { + var casesArr []interface{} - for _, item := range data { - cleanedItem := cleanValue(item) - // 如果是 map,检查是否为空 - if itemMap, ok := cleanedItem.(map[string]interface{}); ok { - if !isEmptyMap(itemMap) { - cleaned = append(cleaned, cleanedItem) + if v, ok := entMap[key]; ok { + if catMap, ok := v.(map[string]interface{}); ok { + // 提取 cases + if arr, ok := catMap["cases"].([]interface{}); ok { + casesArr = arr + } + // 汇总 count_wei_total + if cntRaw, ok := catMap["count"]; ok { + if cntMap, ok := cntRaw.(map[string]interface{}); ok { + totalWei += toInt(cntMap["count_wei_total"]) + } + } } - } else if !isEmptyValue(cleanedItem) { - cleaned = append(cleaned, cleanedItem) } + + if casesArr == nil { + casesArr = []interface{}{} + } + data[key] = map[string]interface{}{ + "cases": casesArr, + } + totalCases += len(casesArr) } - return cleaned + data["count"] = totalCases + data["count_wei_total"] = totalWei + + m["entout"] = map[string]interface{}{ + "data": data, + } + return m } -// isEmptyValue 检查值是否为空 -func isEmptyValue(value interface{}) bool { - if value == nil { - return true - } - switch v := value.(type) { +// toInt 尝试将任意类型转换为 int,用于解析统计字段 +func toInt(v interface{}) int { + switch val := v.(type) { + case int: + return val + case int64: + return int(val) + case float64: + return int(val) case string: - return v == "" || v == "-" - case map[string]interface{}: - return isEmptyMap(v) - case []interface{}: - return len(v) == 0 - case float64: - return v == 0 - default: - return false + if val == "" { + return 0 + } + if n, err := strconv.Atoi(val); err == nil { + return n + } } + return 0 } -// isEmptyMap 检查 map 是否为空(所有字段都是空值) -func isEmptyMap(data map[string]interface{}) bool { - if len(data) == 0 { - return true +// parseRegCapValue 解析 regCap 字符串为可比较数值(单位统一为“万”)。 +// 如 "100.000000万人民币" -> 100.0,"1.5亿人民币" -> 15000.0(1亿=10000万)。用于排序时优选注册资本最高的企业。 +func parseRegCapValue(regCapStr string) float64 { + regCapStr = strings.TrimSpace(regCapStr) + if regCapStr == "" { + return 0 } - for _, value := range data { - if !isEmptyValue(value) { - return false + // 先找数字部分(可能含小数点) + var numStr strings.Builder + for _, r := range regCapStr { + if (r >= '0' && r <= '9') || r == '.' { + numStr.WriteRune(r) + } else if numStr.Len() > 0 { + break } } - return true + if numStr.Len() == 0 { + return 0 + } + val, err := strconv.ParseFloat(numStr.String(), 64) + if err != nil { + return 0 + } + if strings.Contains(regCapStr, "亿") { + val *= 10000 // 1亿 = 10000万 + } + return val } -// hasValidEntoutData 检查 entout 是否有有效数据 -func hasValidEntoutData(data map[string]interface{}) bool { - // 如果数据中有任何结构字段存在,就认为有数据(即使清理后为空) - // 这样可以确保即使所有字段都是 "-",也能返回结构化的数据 - structuralFields := []string{"civil", "criminal", "administrative", "bankrupt", "implement", "preservation", "cases_tree", "count", "crc"} - - for _, field := range structuralFields { - if _, exists := data[field]; exists { - return true +// valueAfterColon 取冒号后的部分,若无冒号则返回原串 +func valueAfterColon(s string) string { + for i, c := range s { + if c == ':' { + return strings.TrimSpace(s[i+1:]) } } - - // 检查各个案件类型是否有有效数据 - caseTypes := []string{"civil", "criminal", "administrative", "bankrupt", "implement", "preservation"} - - for _, caseType := range caseTypes { - if caseData, ok := data[caseType].(map[string]interface{}); ok { - if cases, ok := caseData["cases"].([]interface{}); ok && len(cases) > 0 { - return true - } - } - } - - // 检查 cases_tree - if casesTree, ok := data["cases_tree"].(map[string]interface{}); ok { - for _, treeData := range casesTree { - if treeArray, ok := treeData.([]interface{}); ok && len(treeArray) > 0 { - return true - } - } - } - - return false + return s } -// contains 检查字符串切片是否包含指定字符串 -func contains(slice []string, item string) bool { - for _, s := range slice { - if s == item { +// getStr 从 map 中安全取字符串 +func getStr(m map[string]interface{}, key string) string { + if v, ok := m[key]; ok && v != nil { + switch val := v.(type) { + case string: + return val + case float64: + return strconv.FormatFloat(val, 'f', -1, 64) + } + } + return "" +} + +// buildBasicInfo 按 BACKEND_MAPPING_SPEC 2.2 从 companyMap 推导 BasicInfo 结构 +func buildBasicInfo(companyMap map[string]interface{}) BasicInfo { + basic := BasicInfo{ + RegStatus: valueAfterColon(getStr(companyMap, "orgStatus")), + RegCapital: getStr(companyMap, "regCap"), + RegCapitalCurrency: valueAfterColon(getStr(companyMap, "regCapCur")), + CreditCode: getStr(companyMap, "creditNo"), + RegNumber: getStr(companyMap, "regNo"), + EntType: valueAfterColon(getStr(companyMap, "orgType")), + Industry: getStr(companyMap, "industry"), + EstiblishTime: getStr(companyMap, "esDate"), + RegInstitute: getStr(companyMap, "regorgProvince"), + ApprovedTime: "", + LegalPersonName: getStr(companyMap, "frName"), + Phone: getStr(companyMap, "phone"), + Email: getStr(companyMap, "email"), + Website: getStr(companyMap, "website"), + RegAddress: getStr(companyMap, "regAddress"), + } + return basic +} + +// hasRelationship 判断 relationship 是否包含任一目标 +func hasRelationship(relationship []string, targets ...string) bool { + set := make(map[string]bool) + for _, t := range targets { + set[t] = true + } + for _, r := range relationship { + if set[r] { return true } } return false } -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) +// buildStockHolderItem 投资类记录且存在出资/持股信息时生成 stockHolderItem +func buildStockHolderItem(companyMap map[string]interface{}, relationship []string) (map[string]interface{}, bool) { + if !hasRelationship(relationship, "sh", "his_sh", "lp", "his_lp") { + return nil, false + } + ryName := getStr(companyMap, "ryName") + conform := getStr(companyMap, "conform") + subConAmt := getStr(companyMap, "subConAmt") + fundedRatio := getStr(companyMap, "fundedRatio") + if ryName == "" && subConAmt == "" && fundedRatio == "" { + return nil, false + } + orgHolderType := conform + if orgHolderType == "" { + orgHolderType = "其他" + } + item := map[string]interface{}{ + "orgHolderName": ryName, + "orgHolderType": orgHolderType, + "subscriptAmt": subConAmt, + "investRate": fundedRatio, + } + if investDate := getStr(companyMap, "investDate"); investDate != "" { + item["investDate"] = investDate + } + return item, true } -// generateAuthAuthorizeFileCode 生成随机授权文件代码,格式:AUTH + 6位随机数字 -func generateAuthAuthorizeFileCode() string { - // 生成6位随机数字(100000-999999) - randomNum := rand.Intn(900000) + 100000 - return fmt.Sprintf("AUTH%d", randomNum) +// parsePositionToTypeJoin 从 position 解析职位名,如 "410C:执行董事兼总经理" -> ["执行董事兼总经理"] +func parsePositionToTypeJoin(position string) []string { + if position == "" { + return nil + } + name := valueAfterColon(position) + if name == "" { + return []string{position} + } + return []string{name} +} + +// buildStaffList 高管类记录时从 position 解析出 staffList +func buildStaffList(companyMap map[string]interface{}, relationship []string) (map[string]interface{}, bool) { + if !hasRelationship(relationship, "tm", "his_tm") { + return nil, false + } + position := getStr(companyMap, "position") + typeJoin := parsePositionToTypeJoin(position) + if len(typeJoin) == 0 { + typeJoin = []string{} + } + return map[string]interface{}{ + "result": []map[string]interface{}{ + {"typeJoin": typeJoin}, + }, + }, true } // buildFinalResponse 构建最终响应 @@ -1270,211 +1064,33 @@ func buildFinalResponse(enrichedCompanies []EnrichedCompanyInfo, allCompanies [] companyMap["relationship"] = company.Relationship } - hasRel := func(target string) bool { - for _, r := range company.Relationship { - if r == target { - return true - } - } - return false - } - - // basicInfo 分组(按前端数据格式) - getVal := func(keys ...string) interface{} { - for _, k := range keys { - if v, ok := companyMap[k]; ok && v != nil { - return v - } - } - return nil - } - - // 处理 regCap:分离数字和单位 - // 例如:"100.000000万人民币" -> regCapital: "100.000000", regCapitalCurrency: "万人民币" - regCapital := "" - regCapitalCurrency := "万人民币" - if regCapVal := getVal("regCap", "regCapital"); regCapVal != nil { - if regCapStr, ok := regCapVal.(string); ok && regCapStr != "" { - // 使用正则表达式提取数字部分(包括小数点) - re := regexp.MustCompile(`[\d.]+`) - matches := re.FindString(regCapStr) - if matches != "" { - regCapital = matches - // 提取单位部分(数字之后的所有内容) - if numIndex := strings.Index(regCapStr, matches); numIndex >= 0 { - unitPart := regCapStr[numIndex+len(matches):] - unitPart = strings.TrimSpace(unitPart) - if unitPart != "" { - regCapitalCurrency = unitPart - } - } - } else { - // 如果没有找到数字,直接使用原值 - regCapital = regCapStr - } + // 辅助函数:将历史记录数据转换为前端期望的 { items, total } 格式 + emptyItemsTotal := func() map[string]interface{} { + return map[string]interface{}{ + "items": []interface{}{}, + "total": 0, } } - // 如果 regCapCur 有值,优先使用它 - if regCapCurVal := getVal("regCapCur", "regCapitalCurrency"); regCapCurVal != nil { - if regCapCurStr, ok := regCapCurVal.(string); ok && regCapCurStr != "" { - regCapitalCurrency = regCapCurStr - } - } - - // 构建完整的 basicInfo(按前端期望的格式) - basicInfo := map[string]interface{}{} - // regStatus(从 orgStatus 获取) - if v := getVal("orgStatus", "regStatus"); v != nil { - basicInfo["regStatus"] = v - } - // creditCode(从 creditNo 获取) - if v := getVal("creditNo", "creditCode"); v != nil { - basicInfo["creditCode"] = v - } - // regCapital(从 regCap 中提取的数字部分) - if regCapital != "" { - basicInfo["regCapital"] = regCapital - } - // regCapitalCurrency(从 regCap 中提取的单位部分,或从 regCapCur 获取) - if regCapitalCurrency != "" { - basicInfo["regCapitalCurrency"] = regCapitalCurrency - } - // estiblishTime(从 esDate 获取) - if v := getVal("esDate", "estiblishTime", "establishDate"); v != nil { - basicInfo["estiblishTime"] = v - } - // regNumber(从 regNo 获取) - if v := getVal("regNo", "regNumber"); v != nil { - basicInfo["regNumber"] = v - } - // entType(从 orgType 获取) - if v := getVal("orgType", "entType"); v != nil { - basicInfo["entType"] = v - } - // industry - if v := getVal("industry"); v != nil { - basicInfo["industry"] = v - } - // legalPersonName(从 ryName 获取) - if v := getVal("ryName", "legalPersonName"); v != nil { - basicInfo["legalPersonName"] = v - } - // 保留其他可能的字段 - if v := getVal("regInstitute"); v != nil { - basicInfo["regInstitute"] = v - } - if v := getVal("approvedTime"); v != nil { - basicInfo["approvedTime"] = v - } - if v := getVal("phone"); v != nil { - basicInfo["phone"] = v - } - if v := getVal("email"); v != nil { - basicInfo["email"] = v - } - if v := getVal("website"); v != nil { - basicInfo["website"] = v - } - if v := getVal("regAddress"); v != nil { - basicInfo["regAddress"] = v - } - companyMap["basicInfo"] = basicInfo - - // stockHolderItem(仅股东关系时整理) - if hasRel("sh") { - stockHolderItem := map[string]interface{}{} - if v := getVal("orgHolderName", "holderName", "shareholderName"); v != nil { - stockHolderItem["orgHolderName"] = v - } - if v := getVal("orgHolderType", "holderType"); v != nil { - stockHolderItem["orgHolderType"] = v - } - if v := getVal("subscriptAmt", "investAmount", "contributionAmount"); v != nil { - stockHolderItem["subscriptAmt"] = v - } - if v := getVal("investRate", "shareRate"); v != nil { - stockHolderItem["investRate"] = v - } - if v := getVal("investDate", "contributionDate"); v != nil { - stockHolderItem["investDate"] = v - } - companyMap["stockHolderItem"] = stockHolderItem - } - - // staffList(仅高管关系时整理) - if hasRel("tm") { - staffList := map[string]interface{}{ - "result": []interface{}{}, - } - // 尝试从原始数据中提取职位信息 - typeJoin := []string{} - // 常见职位字段名 - if v := getVal("position", "post", "positionName", "postName"); v != nil { - if posStr, ok := v.(string); ok && posStr != "" { - typeJoin = append(typeJoin, posStr) - } else if posArr, ok := v.([]interface{}); ok { - for _, p := range posArr { - if pStr, ok := p.(string); ok && pStr != "" { - typeJoin = append(typeJoin, pStr) - } - } - } - } - // 如果 typeJoin 为空,尝试从 typeJoin 字段直接获取 - if len(typeJoin) == 0 { - if v := getVal("typeJoin"); v != nil { - if tjArr, ok := v.([]interface{}); ok { - for _, tj := range tjArr { - if tjStr, ok := tj.(string); ok && tjStr != "" { - typeJoin = append(typeJoin, tjStr) - } - } - } else if tjStr, ok := v.(string); ok && tjStr != "" { - typeJoin = append(typeJoin, tjStr) - } - } - } - // 如果有职位信息,添加到 result - if len(typeJoin) > 0 { - staffList["result"] = []interface{}{ - map[string]interface{}{ - "typeJoin": typeJoin, - }, - } - } - companyMap["staffList"] = staffList - } - - // 辅助函数:将历史记录数据转换为前端期望的格式 - // 如果是数组,转换为 { items: [], total: 0 } 格式 - // 如果是空对象,转换为空数组 convertHistoryData := func(data interface{}) interface{} { if data == nil { - return []interface{}{} + return emptyItemsTotal() } - // 如果是数组,转换为 { items: [], total: 0 } 格式 if arr, ok := data.([]interface{}); ok { return map[string]interface{}{ "items": arr, "total": len(arr), } } - // 如果是 map,检查是否为空 if dataMap, ok := data.(map[string]interface{}); ok { if len(dataMap) == 0 { - return []interface{}{} + return emptyItemsTotal() } - // 如果已经有 items 和 total 字段,直接返回 if _, hasItems := dataMap["items"]; hasItems { return dataMap } - // 否则转换为 { items: [], total: 0 } 格式 - return map[string]interface{}{ - "items": []interface{}{}, - "total": 0, - } + return emptyItemsTotal() } - return []interface{}{} + return emptyItemsTotal() } // 检查是否是已处理的企业(前3个) @@ -1484,22 +1100,30 @@ func buildFinalResponse(enrichedCompanies []EnrichedCompanyInfo, allCompanies [] companyMap["financing_history"] = convertHistoryData(enriched.FinancingHistory) companyMap["punishment_info"] = convertHistoryData(enriched.PunishmentInfo) companyMap["abnormal_info"] = convertHistoryData(enriched.AbnormalInfo) - // lawsuit_info 改为 lawsuitInfo(驼峰命名) companyMap["lawsuitInfo"] = enriched.LawsuitInfo companyMap["own_tax"] = convertHistoryData(enriched.OwnTax) companyMap["tax_contravention"] = convertHistoryData(enriched.TaxContravention) } else { - // 未处理的企业,添加空的详细信息 - companyMap["invest_history"] = map[string]interface{}{} - companyMap["financing_history"] = map[string]interface{}{} - companyMap["punishment_info"] = map[string]interface{}{} - companyMap["abnormal_info"] = map[string]interface{}{} - // lawsuitInfo 使用默认格式(与已处理企业保持一致) - companyMap["lawsuitInfo"] = buildEmptyQYGL66SLResponse() - companyMap["own_tax"] = map[string]interface{}{} - companyMap["tax_contravention"] = map[string]interface{}{} + // 未处理的企业,添加空的详细信息(符合 spec:items/total 格式) + companyMap["invest_history"] = emptyItemsTotal() + companyMap["financing_history"] = emptyItemsTotal() + companyMap["punishment_info"] = emptyItemsTotal() + companyMap["abnormal_info"] = emptyItemsTotal() + companyMap["lawsuitInfo"] = map[string]interface{}{} + companyMap["own_tax"] = emptyItemsTotal() + companyMap["tax_contravention"] = emptyItemsTotal() } + // 每条 item 必须包含 basicInfo(BACKEND_MAPPING_SPEC 2.2) + companyMap["basicInfo"] = buildBasicInfo(companyMap) + // 投资类记录增加 stockHolderItem(SPEC 2.4) + if stockHolderItem, ok := buildStockHolderItem(companyMap, company.Relationship); ok { + companyMap["stockHolderItem"] = stockHolderItem + } + // 高管类记录增加 staffList(SPEC 2.5) + if staffList, ok := buildStaffList(companyMap, company.Relationship); ok { + companyMap["staffList"] = staffList + } enhancedDatalist = append(enhancedDatalist, companyMap) }