diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index ed4b9f7..1727eab 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -939,7 +939,7 @@ type YYSYK8R3Req struct { type YYSYF2T7Req struct { MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` - DateRange string `json:"date_range" validate:"required,validAuthDate" ` + DateRange string `json:"date_range" validate:"required,validDateRange"` } type QYGL5S1IReq struct { @@ -974,11 +974,6 @@ type IVYZP0T4Req struct { MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` } -type IVYZF2T7Req struct { - MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` - DateRange string `json:"date_range" validate:"required,validAuthDate" ` -} - type IVYZX5QZReq struct { ReturnURL string `json:"return_url" validate:"required,validReturnURL"` } diff --git a/internal/domains/api/services/api_request_service.go b/internal/domains/api/services/api_request_service.go index 368e70f..0946d79 100644 --- a/internal/domains/api/services/api_request_service.go +++ b/internal/domains/api/services/api_request_service.go @@ -206,6 +206,7 @@ func registerAllProcessors(combService *comb.CombService) { "YYSYK8R3": yysy.ProcessYYSYK8R3Request, //手机空号检测查询 "YYSYH6F3": yysy.ProcessYYSYH6F3Request, //运营商三要素即时版查询 "YYSYK9R4": yysy.ProcessYYSYK9R4Request, //全网手机三要素验证1979周更新版 + "YYSYF2T7": yysy.ProcessYYSYF2T7Request, //手机二次放号检测查询 // IVYZ系列处理器 "IVYZ0B03": ivyz.ProcessIVYZ0B03Request, diff --git a/internal/domains/api/services/form_config_service.go b/internal/domains/api/services/form_config_service.go index 0b53ad4..802d23e 100644 --- a/internal/domains/api/services/form_config_service.go +++ b/internal/domains/api/services/form_config_service.go @@ -361,6 +361,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string frontendRules = append(frontendRules, "日期格式") case rule == "validAuthDate": frontendRules = append(frontendRules, "授权日期格式") + case rule == "validDateRange": + frontendRules = append(frontendRules, "日期范围格式(YYYYMMDD-YYYYMMDD)") case rule == "validTimeRange": frontendRules = append(frontendRules, "时间范围格式") case rule == "validMobileType": @@ -398,6 +400,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation return "text" // time_range是HH:MM-HH:MM格式,使用文本输入 } else if strings.Contains(validation, "授权日期格式") { return "text" // auth_date是YYYYMMDD-YYYYMMDD格式,使用文本输入 + } else if strings.Contains(validation, "日期范围格式") { + return "text" // date_range 为 YYYYMMDD-YYYYMMDD,使用文本输入便于直接输入 } else if strings.Contains(validation, "日期") { return "date" } else if strings.Contains(validation, "链接") { diff --git a/internal/domains/api/services/processors/ivyz/ivyzp2q6_processor.go b/internal/domains/api/services/processors/ivyz/ivyzp2q6_processor.go index 65e3f0c..bf053d2 100644 --- a/internal/domains/api/services/processors/ivyz/ivyzp2q6_processor.go +++ b/internal/domains/api/services/processors/ivyz/ivyzp2q6_processor.go @@ -7,7 +7,7 @@ import ( "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/zhicha" + "tyapi-server/internal/infrastructure/external/shumai" ) // ProcessIVYZP2Q6Request IVYZP2Q6 API处理方法 - 身份认证二要素 @@ -21,37 +21,66 @@ func ProcessIVYZP2Q6Request(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - // 加密姓名 - encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) + reqFormData := map[string]interface{}{ + "idcard": paramsDto.IDCard, + "name": paramsDto.Name, } - // 加密身份证号 - encryptedIDCard, err := deps.ZhichaService.Encrypt(paramsDto.IDCard) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } + // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded + apiPath := "/v4/id_card/check" // 接口路径,根据数脉文档填写(如 v4/xxx) - reqData := map[string]interface{}{ - "name": encryptedName, - "idCard": encryptedIDCard, - } - - respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI011", reqData) + // 先尝试使用政务接口(app_id2 和 app_secret2) + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, true) if err != nil { - if errors.Is(err, zhicha.ErrDatasource) { - return nil, errors.Join(processors.ErrDatasource, err) - } else { - return nil, errors.Join(processors.ErrSystem, err) + // 使用实时接口(app_id 和 app_secret)重试 + respBytes, err = deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData, false) + // 如果重试后仍然失败,返回错误 + if err != nil { + if errors.Is(err, shumai.ErrNotFound) { + // 查无记录情况 + return nil, errors.Join(processors.ErrNotFound, err) + } else if errors.Is(err, shumai.ErrDatasource) { + // 数据源错误 + return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + // 系统错误 + return nil, errors.Join(processors.ErrSystem, err) + } else { + // 其他未知错误 + return nil, errors.Join(processors.ErrSystem, err) + } } } - // 将响应数据转换为JSON字节 - respBytes, err := json.Marshal(respData) - if err != nil { + // 数据源返回 result(0-一致/1-不一致/2-无记录),映射为 state(1-匹配/2-不匹配/3-异常情况) + var dsResp struct { + Result int `json:"result"` // 0-一致 1-不一致 2-无记录(预留) + } + if err := json.Unmarshal(respBytes, &dsResp); err != nil { return nil, errors.Join(processors.ErrSystem, err) } - return respBytes, nil + state := resultToState(dsResp.Result) + + out := map[string]interface{}{ + "data": map[string]interface{}{ + "errMsg": "", + "state": state, + }, + } + return json.Marshal(out) +} + +// resultToState 将数据源 result 映射为接口 state:1-匹配 2-不匹配 3-异常情况 +func resultToState(result int) int { + switch result { + case 0: // 一致 → 匹配 + return 1 + case 1: // 不一致 → 不匹配 + return 2 + case 2: // 无记录(预留) → 异常情况 + return 3 + default: + return 3 // 未知/异常 → 异常情况 + } } diff --git a/internal/domains/api/services/processors/yysy/yysy3e7f_processor.go b/internal/domains/api/services/processors/yysy/yysy3e7f_processor.go index 839eb05..d44b85c 100644 --- a/internal/domains/api/services/processors/yysy/yysy3e7f_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy3e7f_processor.go @@ -7,7 +7,7 @@ import ( "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/zhicha" + "tyapi-server/internal/infrastructure/external/shumai" ) // ProcessYYSY3E7FRequest YYSY3E7F API处理方法 - 空号检测 @@ -20,30 +20,25 @@ func ProcessYYSY3E7FRequest(ctx context.Context, params []byte, deps *processors if err := deps.Validator.ValidateStruct(paramsDto); err != nil { return nil, errors.Join(processors.ErrInvalidParam, err) } - - encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) + reqFormData := map[string]interface{}{ + "mobile": paramsDto.MobileNo, } - reqData := map[string]interface{}{ - "phone": encryptedMobileNo, - } - - respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI010", reqData) + // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded + apiPath := "/v4/mobile_empty/check" // 接口路径,根据数脉文档填写( + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData) if err != nil { - if errors.Is(err, zhicha.ErrDatasource) { + if errors.Is(err, shumai.ErrDatasource) { + // 数据源错误 return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + // 系统错误 + return nil, errors.Join(processors.ErrSystem, err) } else { + // 其他未知错误 return nil, errors.Join(processors.ErrSystem, err) } } - // 将响应数据转换为JSON字节 - respBytes, err := json.Marshal(respData) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } - return respBytes, nil } diff --git a/internal/domains/api/services/processors/yysy/yysy6d9a_processor.go b/internal/domains/api/services/processors/yysy/yysy6d9a_processor.go index 2de31c4..3e920ec 100644 --- a/internal/domains/api/services/processors/yysy/yysy6d9a_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy6d9a_processor.go @@ -4,12 +4,21 @@ import ( "context" "encoding/json" "errors" + "strings" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/zhicha" + "tyapi-server/internal/infrastructure/external/shumai" ) +// shumaiMobileTransferResp 数脉 /v4/mobile-transfer/query 返回结构 +type shumaiMobileTransferResp struct { + OrderNo string `json:"order_no"` + Channel string `json:"channel"` // 移动/电信/联通 + Status int `json:"status"` // 0-在网 1-不在网 + Desc string `json:"desc"` // 不在网原因(status=1时有效) +} + // ProcessYYSY6D9ARequest YYSY6D9A API处理方法 - 全网手机号状态验证A func ProcessYYSY6D9ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { var paramsDto dto.YYSY6D9AReq @@ -21,29 +30,72 @@ func ProcessYYSY6D9ARequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) + reqFormData := map[string]interface{}{ + "mobile": paramsDto.MobileNo, } - reqData := map[string]interface{}{ - "phone": encryptedMobileNo, - } - - respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI030", reqData) + // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded + apiPath := "/v4/mobile-transfer/query" + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData) if err != nil { - if errors.Is(err, zhicha.ErrDatasource) { + if errors.Is(err, shumai.ErrDatasource) { return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + return nil, errors.Join(processors.ErrSystem, err) } else { return nil, errors.Join(processors.ErrSystem, err) } } - // 将响应数据转换为JSON字节 - respBytes, err := json.Marshal(respData) + mapped, err := mapShumaiToYYSY6D9A(respBytes) if err != nil { return nil, errors.Join(processors.ErrSystem, err) } - - return respBytes, nil + return json.Marshal(mapped) +} + +// mapShumaiToYYSY6D9A 将数脉 mobile-transfer 响应映射为最终 state/operators 格式 +// state: 1-正常 2-不在网(空号) 3-无短信能力 4-欠费 5-长时间关机 6-关机 7-通话中 -1-查询失败 +// operators: 1-移动 2-联通 3-电信 +func mapShumaiToYYSY6D9A(dataBytes []byte) (map[string]string, error) { + var r shumaiMobileTransferResp + if err := json.Unmarshal(dataBytes, &r); err != nil { + return map[string]string{"state": "-1", "operators": ""}, nil // 解析失败视为查询失败 + } + + operators := ispNameToCode(strings.TrimSpace(r.Channel)) + state := statusDescToState(r.Status, r.Desc) + + return map[string]string{ + "state": state, + "operators": operators, + }, nil +} + +// statusDescToState status: 0-在网 1-不在网;desc 为不在网原因 +func statusDescToState(status int, desc string) string { + if status == 0 { + return "1" // 正常 + } + // status == 1 不在网,根据 desc 推断 state + d := strings.TrimSpace(desc) + if strings.Contains(d, "销号") || strings.Contains(d, "空号") { + return "2" // 不在网(空号) + } + if strings.Contains(d, "无短信") || strings.Contains(d, "在网不可用") { + return "3" // 无短信能力 + } + if strings.Contains(d, "欠费") { + return "4" // 欠费 + } + if strings.Contains(d, "长时间关机") { + return "5" // 长时间关机 + } + if strings.Contains(d, "关机") { + return "6" // 关机 + } + if strings.Contains(d, "通话中") { + return "7" // 通话中 + } + return "2" // 不在网但未明确原因,默认空号 } diff --git a/internal/domains/api/services/processors/yysy/yysy7d3e_processor.go b/internal/domains/api/services/processors/yysy/yysy7d3e_processor.go index 215f33c..1553899 100644 --- a/internal/domains/api/services/processors/yysy/yysy7d3e_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy7d3e_processor.go @@ -4,12 +4,35 @@ import ( "context" "encoding/json" "errors" + "strings" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/xingwei" + "tyapi-server/internal/infrastructure/external/shumai" ) +// shumaiMobileTransferResp 数脉 /v4/mobile-transfer/query 携号转网返回结构 +type shumaiMobileTransfer7d3eResp struct { + OrderNo string `json:"order_no"` + Mobile string `json:"mobile"` + Area *string `json:"area"` + IspType string `json:"ispType"` // 转网前运营商 + NewIspType string `json:"newIspType"` // 转网后运营商 +} + +// yysy7d3eResp 携号转网查询对外响应结构 +type yysy7d3eResp struct { + BatchNo string `json:"batchNo"` + QueryResult []yysy7d3eQueryItem `json:"queryResult"` +} + +type yysy7d3eQueryItem struct { + Mobile string `json:"mobile"` + Result string `json:"result"` // 0:否 1:是 + After string `json:"after"` // 转网后:-1未知 1移动 2联通 3电信 4广电 + Before string `json:"before"` // 转网前:同上 +} + // ProcessYYSY7D3ERequest YYSY7D3E API处理方法 - 携号转网查询 func ProcessYYSY7D3ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { var paramsDto dto.YYSY7D3EReq @@ -21,25 +44,70 @@ func ProcessYYSY7D3ERequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } - // 构建请求数据,将项目规范的字段名转换为 XingweiService 需要的字段名 - reqData := map[string]interface{}{ - "phoneNumber": paramsDto.MobileNo, + reqFormData := map[string]interface{}{ + "mobile": paramsDto.MobileNo, } - // 调用行为数据API,使用指定的project_id - projectID := "CDJ-1100244706893164544" - respBytes, err := deps.XingweiService.CallAPI(ctx, projectID, reqData) + // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded + apiPath := "/v4/mobile-transfer/query" + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData) if err != nil { - if errors.Is(err, xingwei.ErrNotFound) { - return nil, errors.Join(processors.ErrNotFound, err) - } else if errors.Is(err, xingwei.ErrDatasource) { + if errors.Is(err, shumai.ErrDatasource) { return nil, errors.Join(processors.ErrDatasource, err) - } else if errors.Is(err, xingwei.ErrSystem) { + } else if errors.Is(err, shumai.ErrSystem) { return nil, errors.Join(processors.ErrSystem, err) } else { return nil, errors.Join(processors.ErrSystem, err) } } - return respBytes, nil + mapped, err := mapShumaiToYYSY7D3E(respBytes) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return json.Marshal(mapped) +} + +// mapShumaiToYYSY7D3E 将数脉携号转网响应映射为 batchNo + queryResult 格式 +func mapShumaiToYYSY7D3E(dataBytes []byte) (*yysy7d3eResp, error) { + var r shumaiMobileTransfer7d3eResp + if err := json.Unmarshal(dataBytes, &r); err != nil { + return nil, err + } + + before := ispNameToCodeTransfer(strings.TrimSpace(r.IspType)) + after := ispNameToCodeTransfer(strings.TrimSpace(r.NewIspType)) + result := "0" + if r.IspType != "" && r.NewIspType != "" && strings.TrimSpace(r.IspType) != strings.TrimSpace(r.NewIspType) { + result = "1" // 转网前与转网后不同即为携号转网 + } + + out := &yysy7d3eResp{ + BatchNo: r.OrderNo, + QueryResult: []yysy7d3eQueryItem{ + { + Mobile: r.Mobile, + Result: result, + After: after, + Before: before, + }, + }, + } + return out, nil +} + +// ispNameToCodeTransfer 运营商名称转编码:-1未知 1移动 2联通 3电信 4广电 +func ispNameToCodeTransfer(name string) string { + switch name { + case "移动": + return "1" + case "联通": + return "2" + case "电信": + return "3" + case "广电": + return "4" + default: + return "-1" + } } diff --git a/internal/domains/api/services/processors/yysy/yysy8b1c_processor.go b/internal/domains/api/services/processors/yysy/yysy8b1c_processor.go index c549a05..4ad4738 100644 --- a/internal/domains/api/services/processors/yysy/yysy8b1c_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy8b1c_processor.go @@ -7,7 +7,7 @@ import ( "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/zhicha" + "tyapi-server/internal/infrastructure/external/shumai" ) // ProcessYYSY8B1CRequest YYSY8B1C API处理方法 - 手机在网时长 @@ -20,30 +20,81 @@ func ProcessYYSY8B1CRequest(ctx context.Context, params []byte, deps *processors if err := deps.Validator.ValidateStruct(paramsDto); err != nil { return nil, errors.Join(processors.ErrInvalidParam, err) } - - encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) + reqFormData := map[string]interface{}{ + "mobile": paramsDto.MobileNo, } - reqData := map[string]interface{}{ - "phone": encryptedMobileNo, - } - - respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI003", reqData) + // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded + apiPath := "/v2/mobile_online/check" // 接口路径,根据数脉文档填写(如 v4/xxx) + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData) if err != nil { - if errors.Is(err, zhicha.ErrDatasource) { + if errors.Is(err, shumai.ErrDatasource) { + // 数据源错误 return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + // 系统错误 + return nil, errors.Join(processors.ErrSystem, err) } else { + // 其他未知错误 return nil, errors.Join(processors.ErrSystem, err) } } - // 将响应数据转换为JSON字节 - respBytes, err := json.Marshal(respData) - if err != nil { + var shumaiResp struct { + OrderNo string `json:"order_no"` + Channel string `json:"channel"` // cmcc/cucc/ctcc/gdcc + Time string `json:"time"` // [0,3),[3,6),[6,12),[12,24),[24,-1) + } + if err := json.Unmarshal(respBytes, &shumaiResp); err != nil { return nil, errors.Join(processors.ErrSystem, err) } - return respBytes, nil + // 映射 channel -> operators + operators := channelToOperators(shumaiResp.Channel) + // 映射 time 区间 -> inTime + inTime := timeIntervalToInTime(shumaiResp.Time) + + out := map[string]string{ + "inTime": inTime, + "operators": operators, + } + return json.Marshal(out) +} + +// channelToOperators 运营商编码转名称:cmcc-移动 cucc-联通 ctcc-电信 gdcc-广电 +func channelToOperators(channel string) string { + switch channel { + case "cmcc": + return "移动" + case "cucc": + return "联通" + case "ctcc": + return "电信" + case "gdcc": + return "广电" + default: + return "" + } +} + +// timeIntervalToInTime 在网时间区间转 inTime 值 +// [0,3)->0, [3,6)->3, [6,12)->6, [12,24)->12, [24,-1)->24 +// 空或异常->99, 查无记录->-1(此处按空/未知处理为99) +func timeIntervalToInTime(timeInterval string) string { + switch timeInterval { + case "[0,3)": + return "0" + case "[3,6)": + return "3" + case "[6,12)": + return "6" + case "[12,24)": + return "12" + case "[24,-1)": + return "24" + case "": + return "-1" + default: + return "99" + } } diff --git a/internal/domains/api/services/processors/yysy/yysy9f1b_processor.go b/internal/domains/api/services/processors/yysy/yysy9f1b_processor.go index c8e4222..2fd28d7 100644 --- a/internal/domains/api/services/processors/yysy/yysy9f1b_processor.go +++ b/internal/domains/api/services/processors/yysy/yysy9f1b_processor.go @@ -7,7 +7,7 @@ import ( "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" - "tyapi-server/internal/infrastructure/external/zhicha" + "tyapi-server/internal/infrastructure/external/shumai" ) // ProcessYYSY9F1BYequest YYSY9F1B API处理方法 - 手机二要素验证 @@ -21,37 +21,94 @@ func ProcessYYSY9F1BYequest(ctx context.Context, params []byte, deps *processors if err := deps.Validator.ValidateStruct(paramsDto); err != nil { return nil, errors.Join(processors.ErrInvalidParam, err) } - - encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name) - if err != nil { - return nil, errors.Join(processors.ErrSystem, err) + reqFormData := map[string]interface{}{ + "mobile": paramsDto.MobileNo, + "name": paramsDto.Name, } - encryptedMobileNo, err := deps.ZhichaService.Encrypt(paramsDto.MobileNo) + // 3m8s 运营商二要素:order_no, fee, result(0-一致 1-不一致)。失败则直接返回,不再调携号转网接口。 + apiPath := "/v4/mobile_two/check" + respBytes, err := deps.ShumaiService.CallAPIForm(ctx, apiPath, reqFormData) if err != nil { - return nil, errors.Join(processors.ErrSystem, err) - } - - reqData := map[string]interface{}{ - "name": encryptedName, - "phone": encryptedMobileNo, - "authorized": paramsDto.Authorized, - } - - respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI040", reqData) - if err != nil { - if errors.Is(err, zhicha.ErrDatasource) { + if errors.Is(err, shumai.ErrDatasource) { return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + return nil, errors.Join(processors.ErrSystem, err) } else { return nil, errors.Join(processors.ErrSystem, err) } } - // 将响应数据转换为JSON字节 - respBytes, err := json.Marshal(respData) + // s9w1 手机携号转网(仅在上方二要素成功后再调) + apiPath2 := "/v4/mobile-transfer/query" + respBytes2, err := deps.ShumaiService.CallAPIForm(ctx, apiPath2, reqFormData) if err != nil { + if errors.Is(err, shumai.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } else if errors.Is(err, shumai.ErrSystem) { + return nil, errors.Join(processors.ErrSystem, err) + } else { + return nil, errors.Join(processors.ErrSystem, err) + } + } + + var twoFactorResp struct { + OrderNo string `json:"order_no"` + Fee int `json:"fee"` + Result int `json:"result"` // 0-一致 1-不一致 + } + if err := json.Unmarshal(respBytes, &twoFactorResp); err != nil { return nil, errors.Join(processors.ErrSystem, err) } - return respBytes, nil + var transferResp struct { + OrderNo string `json:"order_no"` + Mobile string `json:"mobile"` + Area *string `json:"area"` + IspType string `json:"ispType"` // 转网前运营商 + NewIspType string `json:"newIspType"` // 转网后运营商 + } + if err := json.Unmarshal(respBytes2, &transferResp); err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + // state: 1-一致 2-不一致 3-异常 + state := "3" + switch twoFactorResp.Result { + case 0: + state = "1" + case 1: + state = "2" + } + + operator := ispNameToCode(transferResp.IspType) + operatorReal := ispNameToCode(transferResp.NewIspType) + + // is_xhzw: 0-否 1-是(转网前与转网后运营商不同即为携号转网) + isXhzw := "0" + if transferResp.IspType != "" && transferResp.NewIspType != "" && transferResp.IspType != transferResp.NewIspType { + isXhzw = "1" + } + + out := map[string]string{ + "operator_real": operatorReal, + "state": state, + "is_xhzw": isXhzw, + "operator": operator, + } + return json.Marshal(out) +} + +// ispNameToCode 运营商名称转编码:1-移动 2-联通 3-电信 +func ispNameToCode(name string) string { + switch name { + case "移动": + return "1" + case "联通": + return "2" + case "电信": + return "3" + default: + return "" + } } diff --git a/internal/domains/api/services/processors/yysy/yysyf2t7_processor.go b/internal/domains/api/services/processors/yysy/yysyf2t7_processor.go index 5baa065..f9b9b5c 100644 --- a/internal/domains/api/services/processors/yysy/yysyf2t7_processor.go +++ b/internal/domains/api/services/processors/yysy/yysyf2t7_processor.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "strings" "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" @@ -20,9 +21,12 @@ func ProcessYYSYF2T7Request(ctx context.Context, params []byte, deps *processors if err := deps.Validator.ValidateStruct(paramsDto); err != nil { return nil, errors.Join(processors.ErrInvalidParam, err) } + // 从入参 date_range(YYYYMMDD-YYYYMMDD)提取右区间作为 date + parts := strings.SplitN(paramsDto.DateRange, "-", 2) + dateEnd := strings.TrimSpace(parts[1]) // 校验已保证格式正确,取结束日期 reqFormData := map[string]interface{}{ "mobile": paramsDto.MobileNo, - "date": paramsDto.DateRange, + "date": dateEnd, } // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded diff --git a/internal/domains/api/services/processors/yysy/yysyf7db_processor.go b/internal/domains/api/services/processors/yysy/yysyf7db_processor.go index d2f3b13..bb27a51 100644 --- a/internal/domains/api/services/processors/yysy/yysyf7db_processor.go +++ b/internal/domains/api/services/processors/yysy/yysyf7db_processor.go @@ -48,11 +48,11 @@ func ProcessYYSYF7DBRequest(ctx context.Context, params []byte, deps *processors // 组装日期:开始日期 + 当前日期(YYYYMMDD-YYYYMMDD) today := time.Now().Format("20060102") - dateRange := paramsDto.StartDate + "-" + today + // dateRange := startDateYyyymmdd + "-" + today reqFormData := map[string]interface{}{ "mobile": paramsDto.MobileNo, - "date": dateRange, + "date": today, } // 以表单方式调用数脉 API;参数在 CallAPIForm 内转为 application/x-www-form-urlencoded diff --git a/internal/shared/validator/custom_validators.go b/internal/shared/validator/custom_validators.go index 80c8ea5..0f07416 100644 --- a/internal/shared/validator/custom_validators.go +++ b/internal/shared/validator/custom_validators.go @@ -78,10 +78,13 @@ func RegisterCustomValidators(validate *validator.Validate) { // 非空字符串验证器(不能为空字符串或只包含空格) validate.RegisterValidation("notEmpty", validateNotEmpty) - // 授权日期验证器 + // 授权日期验证器(格式 YYYYMMDD-YYYYMMDD,且范围必须包含今天) validate.RegisterValidation("auth_date", validateAuthDate) validate.RegisterValidation("validAuthDate", validateAuthDate) + // 日期范围验证器(格式 YYYYMMDD-YYYYMMDD,开始≤结束,不要求包含今天) + validate.RegisterValidation("validDateRange", validateDateRange) + // 授权书URL验证器 validate.RegisterValidation("authorization_url", validateAuthorizationURL) @@ -312,6 +315,32 @@ func validateAuthDate(fl validator.FieldLevel) bool { return !startDate.After(today) && !endDate.Before(today) } +// validateDateRange 日期范围验证器 +// 格式:YYYYMMDD-YYYYMMDD,开始日期不能晚于结束日期(不要求范围包含今天) +func validateDateRange(fl validator.FieldLevel) bool { + s := fl.Field().String() + if s == "" { + return true + } + parts := strings.Split(s, "-") + if len(parts) != 2 { + return false + } + startStr, endStr := parts[0], parts[1] + if len(startStr) != 8 || len(endStr) != 8 { + return false + } + startDate, err := parseYYYYMMDD(startStr) + if err != nil { + return false + } + endDate, err := parseYYYYMMDD(endStr) + if err != nil { + return false + } + return !startDate.After(endDate) +} + // parseYYYYMMDD 解析YYYYMMDD格式的日期字符串 func parseYYYYMMDD(dateStr string) (time.Time, error) { if len(dateStr) != 8 { diff --git a/internal/shared/validator/translations.go b/internal/shared/validator/translations.go index 60afac4..8ae2be5 100644 --- a/internal/shared/validator/translations.go +++ b/internal/shared/validator/translations.go @@ -9,7 +9,7 @@ import ( func RegisterCustomTranslations(validate *validator.Validate, trans ut.Translator) { // 注册标准字段翻译 registerStandardTranslations(validate, trans) - + // 注册自定义字段翻译 registerCustomFieldTranslations(validate, trans) } @@ -194,7 +194,7 @@ func registerCustomFieldTranslations(validate *validator.Validate, trans ut.Tran t, _ := ut.T("auth_date", getFieldDisplayName(fe.Field())) return t }) - + validate.RegisterTranslation("validAuthDate", trans, func(ut ut.Translator) error { return ut.Add("validAuthDate", "{0}格式不正确,必须是YYYYMMDD-YYYYMMDD格式,且日期范围必须包括今天", true) }, func(ut ut.Translator, fe validator.FieldError) string { @@ -202,6 +202,13 @@ func registerCustomFieldTranslations(validate *validator.Validate, trans ut.Tran return t }) + validate.RegisterTranslation("validDateRange", trans, func(ut ut.Translator) error { + return ut.Add("validDateRange", "{0}格式不正确,必须是YYYYMMDD-YYYYMMDD格式,且开始日期不能晚于结束日期", true) + }, func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("validDateRange", getFieldDisplayName(fe.Field())) + return t + }) + // 时间范围翻译 validate.RegisterTranslation("validTimeRange", trans, func(ut ut.Translator) error { return ut.Add("validTimeRange", "{0}格式不正确,必须是HH:MM-HH:MM格式", true) @@ -305,7 +312,7 @@ func registerCustomFieldTranslations(validate *validator.Validate, trans ut.Tran t, _ := ut.T("validEnterpriseName", getFieldDisplayName(fe.Field())) return t }) - + validate.RegisterTranslation("enterprise_name", trans, func(ut ut.Translator) error { return ut.Add("enterprise_name", "{0}格式不正确,必须包含至少一个汉字,长度2-100字符", true) }, func(ut ut.Translator, fe validator.FieldError) string { @@ -410,4 +417,4 @@ func getFieldDisplayName(field string) string { return displayName } return field -} \ No newline at end of file +}