# 实名制采集保存(smz.saveSmzcjxx)— Java 改写说明 > 对应原 C#:`CallClient/TaxTrueNameCaiji.cs` → `BaishuiModel.SmzSaveSmzcjxx` > 目标:前端传入手机号 / 邮箱 / 固话 / 比对序号,Java 后端再转发调用百税 `smz.saveSmzcjxx`。 --- ## 1. 原 C# 行为摘要 ```text 办税员弹窗 TaxerInfo → 点击「实名采集」(未采集时可见) → 弹出 TaxTrueNameCaiji → 用户填写:手机号(必填)、电子邮箱、固定电话 → 点击登记 → 调用百税 smz.saveSmzcjxx 入参:rxsfzBdxh、sjhm、dzyx、lxdh → result.code == "00" → 提示成功,关闭弹窗,隐藏采集按钮并回填手机号 ``` 比对序号 `rxsfzBdxh` 在原客户端来自办税员窗已加载的 `CompareCode`(取号时人脸比对结果),不在采集窗输入。 --- ## 2. 改写后的职责划分 | 角色 | 职责 | |------|------| | 前端 | 收集并校验:手机号、电子邮箱、固定电话、人像身份证比对序号;调用 Java API | | Java 后端 | 接收前端参数 → 校验 → 组装百税请求 → 调用 `smz.saveSmzcjxx` → 将结果返回前端 | | 百税网关 | 按原协议处理采集保存 | 前端不再直连百税;百税 URL、编码、表单协议只存在于 Java 侧。 --- ## 3. Java 对外 API(暴露给前端) ### 3.1 接口定义(建议) | 项 | 建议值 | |----|--------| | Method | `POST` | | Path | `/api/smz/save-cjxx`(可按现有工程规范调整) | | Content-Type | `application/json` | ### 3.2 请求 Body ```json { "rxsfzBdxh": "418206", "sjhm": "13800138000", "dzyx": "a@example.com", "lxdh": "0571-88888888" } ``` | 字段 | 类型 | 必填 | 含义 | 对应百税字段 | |------|------|------|------|--------------| | `rxsfzBdxh` | string | 是 | 人像身份证比对序号 | `rxsfzBdxh` | | `sjhm` | string | 是 | 手机号码 | `sjhm` | | `dzyx` | string | 否 | 电子邮箱 | `dzyx` | | `lxdh` | string | 否 | 固定电话 | `lxdh` | ### 3.3 前端校验(与 C# 对齐) | 规则 | 说明 | |------|------| | `sjhm` 不能为空 | C#:`所登记的手机号不能为空` | | 占位文案不作为有效值 | C# 会把「请输入手机号码」等占位当成空串 | | `rxsfzBdxh` 不能为空 | 原客户端从办税员窗带入;Java 版由前端显式传入,后端应二次校验 | 邮箱、固话格式是否严格校验:原 C# 未做正则,Java 侧可选增强,非必须。 ### 3.4 响应 Body(建议) 透传百税业务结果,便于前端按原逻辑提示: ```json { "success": true, "code": "00", "mess": "保存成功" } ``` | 字段 | 说明 | |------|------| | `success` | Java 层业务成功标志(建议:`code == "00"` 或 `"0"` 时为 true) | | `code` | 百税 `result.code` | | `mess` | 百税 `result.mess`;空时可回落「保存成功」 | 失败示例: ```json { "success": false, "code": "99", "mess": "无返回错误信息" } ``` 网关/网络异常时返回明确错误信息,HTTP 状态可用 `502` / `500`,或统一 `200` + `success=false`(与现有工程约定一致即可)。 --- ## 4. Java 调用百税 `smz.saveSmzcjxx` ### 4.1 百税协议(与 BaiShuiSDK 一致) | 项 | 值 | |----|-----| | Method | `POST` | | URL | 配置项,对应原 `REALNAME_CHECK_API` / `ApiUrl` | | Content-Type | `application/x-www-form-urlencoded` | | Body | `domain.ywId=smz.saveSmzcjxx&domain.parmJson={UrlEncode(JSON)}` | `parmJson` 序列化规则: 1. 整对象转 JSON(含 `ver`、`bid` 及业务字段) 2. **忽略 null 字段** 3. 中文保持原文,不做 `\uXXXX` 4. 再对整段 JSON 做 URL 编码;建议与 C# `HttpUtils.UrlEncode(..., Encoding.Default)` 对齐(中文环境多为 GBK,`%XX` 大写),避免 `2003 JSON解析出错` ### 4.2 组装给百税的 `parmJson` ```json { "ver": "1.0", "bid": "smz.saveSmzcjxx", "rxsfzBdxh": "418206", "sjhm": "13800138000", "dzyx": "a@example.com", "lxdh": "0571-88888888" } ``` 字段映射:前端入参原样写入同名字段;空的可选字段不传(null 忽略)。 ### 4.3 百税响应 HTTP 外层: ```json { "domain": { "ywId": "smz.saveSmzcjxx", "retJson": "{\"result\":{\"code\":\"00\",\"mess\":\"保存成功\"}}" } } ``` 解析 `domain.retJson` 后: ```json { "result": { "code": "00", "mess": "保存成功" } } ``` 无额外业务字段(`JhxtSaveSmzcjxxResult` 仅含 `code` / `mess`)。 C# SDK 中 `SmzSaveSmzcjxx` **不强制**校验 `code == "00"` 后抛异常,而是把 `result` 交回 UI 判断。Java 建议: - 能解析出 `result`:按 `code` 组装对外响应,不抛中断(除非工程统一要求失败抛异常) - 通信失败 / 无 `retJson`:记日志并返回失败给前端 --- ## 5. 后端处理流程 ```text ① 接收前端 JSON ② 校验 sjhm、rxsfzBdxh 非空(可选:手机号格式) ③ 组装 BsRequest: ver=1.0, bid=smz.saveSmzcjxx, rxsfzBdxh, sjhm, dzyx?, lxdh? ④ POST 百税网关(form-urlencoded + UrlEncode) ⑤ 解析 domain.retJson → result.code / result.mess ⑥ 返回给前端(success / code / mess) ``` 伪代码: ```java @PostMapping("/api/smz/save-cjxx") public ApiResult saveCjxx(@RequestBody SaveSmzcjxxReq req) { if (isBlank(req.getSjhm())) { return ApiResult.fail("所登记的手机号不能为空"); } if (isBlank(req.getRxsfzBdxh())) { return ApiResult.fail("人像身份证比对序号不能为空"); } Map parm = new LinkedHashMap<>(); parm.put("ver", "1.0"); parm.put("bid", "smz.saveSmzcjxx"); parm.put("rxsfzBdxh", req.getRxsfzBdxh()); parm.put("sjhm", req.getSjhm()); if (notBlank(req.getDzyx())) parm.put("dzyx", req.getDzyx()); if (notBlank(req.getLxdh())) parm.put("lxdh", req.getLxdh()); BsResult result = bsClient.execute("smz.saveSmzcjxx", parm); boolean ok = "00".equals(result.getCode()) || "0".equals(result.getCode()); String mess = blankToDefault(result.getMess(), ok ? "保存成功" : "无返回错误信息"); return new ApiResult(ok, result.getCode(), mess); } ``` --- ## 6. 配置项 | 配置键(建议) | 含义 | 对应原系统 | |----------------|------|------------| | `baishui.api-url` / `REALNAME_CHECK_API` | 百税网关完整 URL | `TaxTrueNameCaiji.ApiUrl` / `SystemControl.Get("REALNAME_CHECK_API")` | | 超时时间 | HTTP 超时 | C# `PostSync` 约 60s 量级 | --- ## 7. 前端对接要点(办税员窗复刻) 1. 打开采集弹窗前,确保已有 `rxsfzBdxh`(来自票的比对序号 / 办税员信息加载结果)。 2. 用户填写 `sjhm`(必填)、`dzyx`、`lxdh`。 3. 调用 Java `/api/smz/save-cjxx`。 4. `success == true`(或 `code == "00"`): - 提示成功 - 关闭弹窗 - 隐藏「实名采集」按钮 - 回填展示手机号(原 `SetTruceNameCaijiOk`) 5. 失败:展示 `mess`。 说明:查询是否已采集仍可走原逻辑对应的 `smz.getSmzcjxx`(若也改写到 Java,另文说明);**本文仅覆盖采集保存**。 --- ## 8. 实现清单 | 优先级 | 项 | 说明 | |--------|----|------| | P0 | 对外 REST API | 暴露四个参数,校验手机号与比对序号 | | P0 | 百税 Client | form-urlencoded + `domain.ywId` / `domain.parmJson` + UrlEncode | | P0 | 调用 `smz.saveSmzcjxx` | 字段映射与响应解析 | | P1 | 统一错误码 / 日志 | 记录入参(可脱敏手机号)与百税返回 | | P2 | 手机号格式校验 | 原 C# 未做,可选 | --- ## 9. 源码索引 | 说明 | 路径 | |------|------| | 采集弹窗 / 提交 | `CallClient/TaxTrueNameCaiji.cs` | | 办税员窗打开采集 | `CallClient/WPF/TaxerInfo.xaml.cs` → `Caiji_Click` | | 成功回调 | `TaxerInfo.SetTruceNameCaijiOk` | | 请求模型 | `BaiShuiSDK/Request/SmzJhxtSaveSmzcjxxRequest.cs`(`bid = smz.saveSmzcjxx`) | | 响应模型 | `BaiShuiSDK/Response/SmzJhxtSaveSmzcjxxResponse.cs` | | SDK 封装 | `BaiShuiSDK/Model/BaishuiModel.cs` → `SmzSaveSmzcjxx` | | HTTP 协议 | `BaiShuiSDK/Client/BsDefaultClient.cs` | | 协议参考 | `BaiShuiSDK/smz.rxsfzBd-接口说明.md`(协议相同,bid 不同) |