From 7fc072e6089c0a447b24ea0342f41ca9ff2d22ca Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 20 Nov 2025 20:16:18 +0800 Subject: [PATCH] add ivyz9k2l --- docs/IVYZ9K2L_WestDex_API文档.md | 261 ++++++++++++++++++ internal/domains/api/dto/api_request_dto.go | 2 +- .../api/services/form_config_service.go | 6 +- .../processors/ivyz/ivyz9k2l_processor.go | 52 +++- 4 files changed, 309 insertions(+), 12 deletions(-) create mode 100644 docs/IVYZ9K2L_WestDex_API文档.md diff --git a/docs/IVYZ9K2L_WestDex_API文档.md b/docs/IVYZ9K2L_WestDex_API文档.md new file mode 100644 index 0000000..f55cb36 --- /dev/null +++ b/docs/IVYZ9K2L_WestDex_API文档.md @@ -0,0 +1,261 @@ +# IVYZ9K2L - 身份认证三要素(人脸图像版) WestDex API 文档 + +## 接口信息 + +- **接口名称**: 身份认证三要素(人脸图像版) +- **接口代码**: IVYZ9K2L +- **WestDex API Code**: `idCardThreeElements` +- **请求方式**: POST +- **Content-Type**: application/json + +## 请求URL + +``` +https://apimaster.westdex.com.cn/api/invoke/{secret_id}/{api_code}?timestamp={timestamp} +``` + +### URL 参数说明 + +| 参数 | 说明 | 示例值 | +|------|------|--------| +| secret_id | 西部数据 SecretID(从配置获取) | `449159` | +| api_code | API代码 | `idCardThreeElements` | +| timestamp | 毫秒级时间戳(URL参数) | `1713421668375` | + +### 完整URL示例 + +``` +https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements?timestamp=1713421668375 +``` + +## 请求头 + +``` +Content-Type: application/json +``` + +## 请求体 + +### 请求体结构 + +```json +{ + "data": { + "timeStamp": "1713421668375", + "customNumber": "449159", + "xM": "fU4B3fR3Dw+UkHNkFsHIjA==", + "gMSFZHM": "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=" + }, + "photoData": "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..." +} +``` + +### 参数说明 + +#### data 对象(必填) + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +|--------|------|------|------|--------| +| timeStamp | string | 是 | 毫秒级时间戳,与URL参数中的timestamp一致 | `"1713421668375"` | +| customNumber | string | 是 | 自定义编号,使用配置中的 secret_id | `"449159"` | +| xM | string | 是 | 加密后的姓名(使用AES加密,密钥为配置中的key) | `"fU4B3fR3Dw+UkHNkFsHIjA=="` | +| gMSFZHM | string | 是 | 加密后的身份证号(使用AES加密,密钥为配置中的key) | `"qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg="` | + +#### photoData(必填) + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +|--------|------|------|------|--------| +| photoData | string | 是 | Base64编码的人脸图片数据,仅支持JPG、BMP、PNG格式 | `"Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..."` | + +## 加密说明 + +### 姓名和身份证号加密 + +使用 AES-ECB 模式加密,密钥为配置中的 `key`(示例:`121a1e41fc1690dd6b90afbcacd80cf4`) + +**加密步骤**: +1. 使用密钥生成 AES 密钥 +2. 使用 AES-ECB 模式加密原始数据 +3. 将加密结果进行 Base64 编码 + +**示例**: +- 原始姓名:`"张三"` +- 加密后:`"fU4B3fR3Dw+UkHNkFsHIjA=="` + +## 完整请求示例 + +### cURL 示例 + +```bash +curl -X POST "https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements?timestamp=1713421668375" \ + -H "Content-Type: application/json" \ + -d '{ + "data": { + "timeStamp": "1713421668375", + "customNumber": "449159", + "xM": "fU4B3fR3Dw+UkHNkFsHIjA==", + "gMSFZHM": "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=" + }, + "photoData": "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..." + }' +``` + +### JavaScript 示例 + +```javascript +const timestamp = Date.now().toString(); +const url = `https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements?timestamp=${timestamp}`; + +const requestBody = { + data: { + timeStamp: timestamp, + customNumber: "449159", + xM: "fU4B3fR3Dw+UkHNkFsHIjA==", // 加密后的姓名 + gMSFZHM: "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=" // 加密后的身份证号 + }, + photoData: "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..." // Base64图片数据 +}; + +fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(requestBody) +}) +.then(response => response.json()) +.then(data => console.log(data)) +.catch(error => console.error('Error:', error)); +``` + +## 响应格式 + +### 成功响应 + +```json +{ + "code": "00000", + "message": "成功", + "data": "加密后的响应数据(需要解密)", + "id": "响应ID", + "error_code": null, + "reason": "" +} +``` + +### 错误响应 + +```json +{ + "code": "错误码", + "message": "错误信息", + "data": "加密后的错误数据(需要解密)", + "id": "响应ID", + "error_code": 错误码, + "reason": "错误原因" +} +``` + +### 响应状态码说明 + +| 状态码 | 说明 | +|--------|------| +| `00000` | 成功 | +| `200` | 成功 | +| `0` | 成功 | +| 其他 | 失败 | + +## 响应数据解密 + +响应中的 `data` 字段是加密的,需要使用相同的密钥进行解密: + +**解密步骤**: +1. 使用配置中的 `key` 作为密钥 +2. 对 `data` 字段进行 Base64 解码 +3. 使用 AES-ECB 模式解密 +4. 得到原始 JSON 字符串 + +## Apifox 配置步骤 + +### 1. 创建新请求 + +- 方法:`POST` +- URL:`https://apimaster.westdex.com.cn/api/invoke/449159/idCardThreeElements` + +### 2. 设置URL参数 + +在"Params"标签页添加: +- `timestamp`: `{{$timestamp}}` (使用Apifox变量生成当前时间戳) + +### 3. 设置请求头 + +在"Headers"标签页添加: +- `Content-Type`: `application/json` + +### 4. 设置请求体 + +在"Body"标签页选择 `raw` 类型,格式选择 `JSON`,内容如下: + +```json +{ + "data": { + "timeStamp": "{{$timestamp}}", + "customNumber": "449159", + "xM": "fU4B3fR3Dw+UkHNkFsHIjA==", + "gMSFZHM": "qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=" + }, + "photoData": "Qk3OlwAAAAAAADYAAAAoAAAAZgAAAH4AAAABABgAAA..." +} +``` + +### 5. 配置环境变量(可选) + +在Apifox中创建环境变量: +- `westdex_secret_id`: `449159` +- `westdex_key`: `121a1e41fc1690dd6b90afbcacd80cf4` +- `westdex_url`: `https://apimaster.westdex.com.cn/api/invoke` + +然后在URL中使用:`{{westdex_url}}/{{westdex_secret_id}}/idCardThreeElements?timestamp={{$timestamp}}` + +### 6. 前置脚本(用于生成时间戳) + +在"前置脚本"中添加: + +```javascript +// 生成毫秒级时间戳 +pm.environment.set("timestamp", Date.now().toString()); +``` + +然后在URL参数和请求体中使用 `{{timestamp}}` + +## 注意事项 + +1. **时间戳同步**:URL参数中的 `timestamp` 和请求体 `data.timeStamp` 必须一致 +2. **加密密钥**:姓名和身份证号必须使用配置中的 `key` 进行加密 +3. **图片格式**:`photoData` 必须是纯Base64字符串(不包含 `data:image/xxx;base64,` 前缀) +4. **图片格式限制**:仅支持 JPG、BMP、PNG 三种格式 +5. **请求超时**:建议设置60秒超时时间 +6. **响应解密**:成功响应中的 `data` 字段需要解密后才能查看实际内容 + +## 配置信息 + +根据项目配置文件,当前使用的配置为: + +- **URL**: `https://apimaster.westdex.com.cn/api/invoke` +- **Key**: `121a1e41fc1690dd6b90afbcacd80cf4` +- **SecretID**: `449159` +- **SecretSecondID**: `296804` + +## 测试数据示例 + +### 原始数据 +- 姓名:`张三` +- 身份证号:`110101199001011234` +- 人脸图片:需要转换为Base64格式 + +### 加密后的数据(示例) +- 加密姓名:`fU4B3fR3Dw+UkHNkFsHIjA==` +- 加密身份证号:`qL3GFeI7JO8txKDT25hjuXe5IhnGJ00Jg8+YYbnQ6wg=` + +**注意**:实际加密结果会根据密钥和原始数据不同而变化,以上仅为示例格式。 + diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index 2b77f22..0d14b2c 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -302,7 +302,7 @@ type IVYZ3A7FReq struct { type IVYZ9K2LReq struct { Name string `json:"name" validate:"required,min=1,validName"` IDCard string `json:"id_card" validate:"required,validIDCard"` - PhotoData string `json:"photo_data" validate:"omitempty,validBase64Image"` + PhotoData string `json:"photo_data" validate:"required,validBase64Image"` } type IVYZ9D2EReq struct { diff --git a/internal/domains/api/services/form_config_service.go b/internal/domains/api/services/form_config_service.go index 1e5e85c..5ea4ee4 100644 --- a/internal/domains/api/services/form_config_service.go +++ b/internal/domains/api/services/form_config_service.go @@ -295,6 +295,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string frontendRules = append(frontendRules, "返回链接格式") case rule == "validAuthorizationURL": frontendRules = append(frontendRules, "授权链接格式") + case rule == "validBase64Image": + frontendRules = append(frontendRules, "Base64图片格式(JPG、BMP、PNG)") case strings.HasPrefix(rule, "oneof="): values := strings.TrimPrefix(rule, "oneof=") frontendRules = append(frontendRules, "可选值: "+values) @@ -324,6 +326,8 @@ func (s *FormConfigServiceImpl) getFieldType(fieldType reflect.Type, validation return "url" } else if strings.Contains(validation, "可选值") { return "select" + } else if strings.Contains(validation, "Base64图片") || strings.Contains(validation, "base64") { + return "textarea" } return "text" case reflect.Int64: @@ -515,7 +519,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s "plate_type": "号牌类型:01-小型汽车;02-大型汽车(可选)", "vin_code": "请输入17位车辆识别代号VIN码(Vehicle Identification Number)", "return_type": "返回类型:1-专业和学校名称数据返回编码形式(默认);2-专业和学校名称数据返回中文名称", - "photo_data": "人脸图片(选填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式", + "photo_data": "人脸图片(必填):base64编码的图片数据,仅支持JPG、BMP、PNG三种格式", } if desc, exists := descMap[jsonTag]; exists { diff --git a/internal/domains/api/services/processors/ivyz/ivyz9k2l_processor.go b/internal/domains/api/services/processors/ivyz/ivyz9k2l_processor.go index ca49324..9a2d07a 100644 --- a/internal/domains/api/services/processors/ivyz/ivyz9k2l_processor.go +++ b/internal/domains/api/services/processors/ivyz/ivyz9k2l_processor.go @@ -10,6 +10,8 @@ import ( "tyapi-server/internal/domains/api/dto" "tyapi-server/internal/domains/api/services/processors" "tyapi-server/internal/infrastructure/external/westdex" + + "github.com/tidwall/gjson" ) // ProcessIVYZ9K2LRequest IVYZ9K2L API处理方法 - 身份认证三要素(人脸图像版) @@ -44,15 +46,13 @@ func ProcessIVYZ9K2LRequest(ctx context.Context, params []byte, deps *processors // 构建请求数据 reqData := map[string]interface{}{ - "timeStamp": timestamp, - "customNumber": customNumber, - "xM": encryptedName, - "gMSFZHM": encryptedIDCard, - } - - // 如果提供了人脸图片,添加到请求数据中 - if paramsDto.PhotoData != "" { - reqData["photoData"] = paramsDto.PhotoData + "data": map[string]interface{}{ + "timeStamp": timestamp, + "customNumber": customNumber, + "xM": encryptedName, + "gMSFZHM": encryptedIDCard, + "photoData": paramsDto.PhotoData, + }, } respBytes, err := deps.WestDexService.CallAPI(ctx, "idCardThreeElements", reqData) @@ -67,5 +67,37 @@ func ProcessIVYZ9K2LRequest(ctx context.Context, params []byte, deps *processors } } - return respBytes, nil + // 使用gjson提取authResult字段 + // 尝试多个可能的路径 + var authResult string + paths := []string{ + "WEST00037.WEST00038.authResult", + "WEST00036.WEST00037.WEST00038.authResult", + "authResult", + } + + for _, path := range paths { + result := gjson.GetBytes(respBytes, path) + if result.Exists() { + authResult = result.String() + break + } + } + + // 如果找不到authResult,返回ErrDatasource + if authResult == "" { + return nil, errors.Join(processors.ErrDatasource, errors.New("响应中未找到authResult字段")) + } + + // 构建返回格式 {result: XXXX} + response := map[string]interface{}{ + "result": authResult, + } + + responseBytes, err := json.Marshal(response) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + return responseBytes, nil }