commit b450b512a259bb5fd768d02fdb81ca76f8635f6d Author: 杨柳杰 Date: Mon May 4 12:16:08 2026 +0800 init: mipu-open 对外开放项目统一管理仓库 - 添加 mipu-api 作为 git submodule (Claude Code Skill) - 迁移 API 文档源文件到 docs/ 目录统一维护 - 添加 Gitea Actions 工作流:tag推送自动打包docs并发布Release - Skill 运行时自动从 mipu-open Release 下载最新文档 Co-Authored-By: Claude Opus 4.6 diff --git a/.gitea/workflows/release-docs.yml b/.gitea/workflows/release-docs.yml new file mode 100644 index 0000000..9a5a057 --- /dev/null +++ b/.gitea/workflows/release-docs.yml @@ -0,0 +1,58 @@ +name: Release Docs + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Package docs + run: | + if [ ! -d "docs" ]; then + echo "Error: docs directory not found" + exit 1 + fi + TAG=${GITHUB_REF#refs/tags/} + echo "$TAG" > "docs/.version" + tar -czf /tmp/docs.tar.gz docs/ + + - name: Create Release + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + TAG=${GITHUB_REF#refs/tags/} + API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + + # Create release + curl -sf -X POST "${API}/releases" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"${TAG}\", + \"name\": \"API文档 ${TAG}\", + \"body\": \"米普云航司API文档更新 ${TAG}\" + }" + + # Get release ID + RELEASE_ID=$(curl -sf "${API}/releases/tags/${TAG}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + | jq -r '.id') + + if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then + echo "Error: failed to create release" + exit 1 + fi + + # Upload docs.tar.gz + curl -sf -X POST "${API}/releases/${RELEASE_ID}/assets?name=docs.tar.gz" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: multipart/form-data" \ + -F "attachment=@/tmp/docs.tar.gz" + + echo "Release ${TAG} created with docs.tar.gz" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c08f380 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mipu-api"] + path = mipu-api + url = https://git.addhh.com/willow/mipu-api.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..9dce823 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Mipu Open + +米普对外开放项目的统一管理仓库。 + +## 子项目 + +| 子项目 | 说明 | +|--------|------| +| [mipu-api](./mipu-api/) | Claude Code 开发 Skill,辅助外部开发者快速对接米普 API | +| docs/ | API 文档源文件(本仓库维护,通过 Release 发布 tar 包) | +| mipu-sdk | 开发 SDK(待添加) | + +## 文档发布流程 + +文档源文件在 `docs/` 目录维护。发布新版本: + +```bash +# 1. 修改 docs/ 下的文档 +# 2. 提交并打 tag +git add docs/ +git commit -m "更新API文档" +git tag v1.1.0 +git push && git push --tags + +# Gitea Actions 自动: +# - 打包 docs/ 为 docs.tar.gz +# - 创建 Release 并附加 docs.tar.gz +``` + +Skill 端会在每次调用时自动检查 Release 版本并下载更新。 + +## 快速开始 + +克隆主仓库及所有子模块: + +```bash +git clone --recurse-submodules +``` + +如果已经克隆了主仓库: + +```bash +git submodule update --init --recursive +``` + +## 更新子模块 + +拉取所有子模块的最新更新: + +```bash +git submodule update --remote +``` + +## 添加新子模块 + +```bash +git submodule add +``` diff --git a/docs/02-01_cache-search.md b/docs/02-01_cache-search.md new file mode 100644 index 0000000..e4d71d8 --- /dev/null +++ b/docs/02-01_cache-search.md @@ -0,0 +1,144 @@ + +# cache/search - 查询航司缓存数据 + +# 缓存票价搜索(cache/search)API 说明文档 + +## 用户使用场景 + +> 用户希望以低成本,高性能的模式获取航司的价格数据,该数据源为我司缓存库内容,非实时提取航司数据。 + +## 错误场景(不应该使用本接口的场景) + +> 本接口不会与航司产生任何交互,无法保障在响应时刻价格/库存与航司完全一致。 + +## 性能指标 + +> - 航司路线&旅行日期覆盖率:按航司提供,平均值为180天内>90%。 +> - 价格准确率(验价变价率):<85% + +## 请求说明 + +| **请求地址** | https://${endpoint}/cache/search | +| --- | --- | +| **请求方法** | POST | +| **注意事项** | Header必须带压缩请求。 | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### + +### 请求体 + +注灰色背景为高级特性,默认不不填写,根据需要进行开发。 + +```json +{ + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "u2web" +} +``` + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW:单程OneWayRT:往返RoundTrip | +| origin | String | 是 | "CJJ" | 出发地,为IATA 3字码兼容城市或者机场3字码 | +| originAirport | String | 否 | | | +| destination | String | 是 | "SHA" | 到达地,为IATA3字码兼容城市或者机场3字码 | +| destinationAirport | String | 否 | | | +| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501) | +| returnDate | String | 否 | 20240423(仅 RT 必填) | 返程日期,格式同 departureDate,仅当 journeyType=RT 时必传,OW 时可传空 | +| adults | Integer | 是 | 2 | 成人,乘机人数量 | +| children | Integer | 是 | 1 | 儿童,乘机人数量 | +| infants | Integer | 是 | 0 | 婴儿,乘机人数量 | +| agent | String | 是 | "F9-web" | 你自行定义的F9执行器编码(Agent Code) | +| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-USUS代表美国 | +| acceptCacheMinutes | Integer | 否 | 5 | 默认值:5**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存,用户可以指定缓存时长,不填写为5。 | + +## 返回参数 + +探索一个返回参数json,https://jsonhero.io/j/XmTLWK94Ae5X + +| 参数名称 | 类型 | 示例值 | 说明 | +| --- | --- | --- | --- | +| itineraries | Array | - | 航线组合列表,包含不同航班拼接的行程方案 | +| code | Integer | 0 | 系统状态码:0 = 成功,非 0 为失败 | +| msg | String | null | 系统消息:成功时为 null,失败时返回具体系统提示信息 | +| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID: 与航司通信的session值,可以用于加速后续动作,如继续获取包裹,选座,下单。 | + +# 常见问题 + +## origin/originAirport的区别是什么? + +origin:出发地,建议传入IATA规定的城市码,必填 + +originAirport:出发机场,非必填 + +由于各个数据源对于城市码/机场码的需求场景不一致,服务端做了兼容处理。 + +一般情况只需要传入Origin(城市码),我们会自动将Origin翻译为机场码,并对航司进行轮询获取所有数据。 + +部分特殊情况,如只需要搜索单个机场码,或者直接搜索城市码,不希望服务端转换为机场码,请将需要搜索的三字码直接放入OriginAirport。 + +**举例:** + +入参:origin: LON。 + +不传入originAirport, 服务端发送给航司originAirport: LCY, LGW, LHR, LTN, SEN, STN + +传入originAirport: LCY, 服务端实际发送给航司originAirport: LCY + +传入originAirport: LON, 服务端实际发送给航司 originAirport: LON + +**destination/destinationAirport逻辑同origin/originAirport** + +## + +# 业务案例 + +## 单人单程搜索 + +### 请求(Request) + +```objective-c + +``` + +### 请求(Response) + +--- + +## 转机行程搜索 + +### 请求(Request) + +```json +{ + "journeyType": "OW", + "origin": "BOM", + "destination": "CNX", + "departureDate": "20251118", + "adults": 1, + "children": 0, + "infants": 0 +} +``` + +### 响应(Response) + +在线查看:https://jsonhero.io/j/J0LMhczR0bJM/editor + diff --git a/docs/03-01_shopping-search.md b/docs/03-01_shopping-search.md new file mode 100644 index 0000000..106a308 --- /dev/null +++ b/docs/03-01_shopping-search.md @@ -0,0 +1,192 @@ + +# shopping/search - 运价报价 + +## 概述 + +航班搜索(Flight Search)API 用于驱动 Agent 去数据源(航司)获取对应数据。 + +### 用户使用场景 + +本接口一般用于: +- 查询单家航司报价 +- 建立航司数据本地缓存 + +### 错误场景(不建议使用) + +本接口不适合以下场景: + +- **秒级搜索响应**:本接口为实时调用接口,响应时间依赖数据源(航司),一般响应时间在10秒左右。如需秒级搜索响应,请参考缓存搜索接口(cache/search)。 +- **验价**:验价请使用 shopping/select(8秒内响应,成功率98%)。 + +### 性能指标 + +| 指标 | 说明 | +|------|------| +| 响应速度 | 依赖数据源(航司)响应,根据大数据统计90%的请求在10秒内 | +| 成功率 | >90% | + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/shopping/search` | +| **请求方法** | POST | +| **注意事项** | Header 必须带压缩请求 | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### 请求体参数 + +> 注:灰色背景为高级特性,默认不填写,根据需要进行开发。 + +```json +{ + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "u2web", + "proxy": "myproxy-us" +} +``` + +| 参数名称 | 类型 | 是否必选 | 示例值(默认值) | 说明 | +|----------|------|----------|-----------------|------| +| journeyType | String | 是 | "OW" | 行程类型:OW=单程(OneWay),RT=往返(RoundTrip) | +| origin | String | 是 | "CJJ" | 出发地,为IATA 3字码兼容城市或者机场3字码 | +| originAirport | String | 否 | - | 出发机场 | +| destination | String | 是 | "SHA" | 到达地,为IATA 3字码兼容城市或者机场3字码 | +| destinationAirport | String | 否 | - | 到达机场 | +| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如2024年5月1日为20240501) | +| returnDate | String | 否 | 20240423 | 返程日期,格式同 departureDate,仅当 journeyType=RT 时必传 | +| adults | Integer | 是 | 2 | 成人乘机人数量 | +| children | Integer | 是 | 1 | 儿童乘机人数量 | +| infants | Integer | 是 | 0 | 婴儿乘机人数量 | +| agent | String | 是 | "F9-web" | 你自行定义的执行器编码(Agent Code) | +| proxy | String | 是 | - | 本次获取航司数据采用的代理方式。如需指定代理国家请在代理用户后面加上国家二字码,如 myproxy-US 代表美国 | +| currency | String | 否 | - | 航司所支持的货币类型,如航司不支持对应货币,接口会进行报错 | +| acceptCacheMinutes | Integer | 否 | 5 | 默认值:5。**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存,用户可以指定缓存时长,不填写为5分钟 | +| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。
- **false**: 同步模式,等待完整结果返回
- **true**: 异步模式,立即返回 202,结果通过 Webhook 回调

**异步模式说明**:
1. 系统会同步检查是否有新鲜缓存(acceptCacheMinutes),如有则立即返回
2. 无缓存时返回 202 Accepted,包含 requestId 用于追踪
3. 航司数据返回后,通过预先配置的 Webhook 回调通知

**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook | +| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`search_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}`,最大 50 个字符 | + +## 返回参数 + +在线查看返回参数示例:https://jsonhero.io/j/XmTLWK94Ae5X + +| 参数名称 | 类型 | 示例值 | 说明 | +|----------|------|--------|------| +| itineraries | Array\ | - | 航线组合列表,包含不同航班拼接的行程方案。详见 [Itinerary 实体定义](08-01_itinerary.md) | +| code | Integer | 0 | 系统状态码:0=成功,非0为失败 | +| msg | String | null | 系统消息:成功时为 null,失败时返回具体系统提示信息 | +| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID:与航司通信的 session 值,可以用于加速后续动作,如继续获取包裹、选座、下单 | + +### 异步模式响应(async=true) + +当 `async=true` 且无可用缓存时,系统立即返回 202 Accepted: + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| code | Integer | 202(表示请求已接受,正在处理中) | +| msg | String | "Accepted. Please wait for the webhook callback" | +| requestId | String | 请求ID(UUID),用于追踪和关联 Webhook 回调 | +| status | String | "PROCESSING"(处理中) | + +完整结果将通过预先配置的 Webhook 回调返回。 + +## 常见问题 + +### origin/originAirport 的区别是什么? + +**origin**:出发地,建议传入 IATA 规定的城市码,必填 + +**originAirport**:出发机场,非必填 + +由于各个数据源对于城市码/机场码的需求场景不一致,服务端做了兼容处理。 + +一般情况只需要传入 Origin(城市码),我们会自动将 Origin 翻译为机场码,并对航司进行轮询获取所有数据。 + +部分特殊情况,如只需要搜索单个机场码,或者直接搜索城市码不希望服务端转换为机场码,请将需要搜索的三字码直接放入 originAirport。 + +**举例:** + +- 入参:`origin: LON` + - 不传入 originAirport,服务端发送给航司:`originAirport: LCY, LGW, LHR, LTN, SEN, STN` + - 传入 originAirport: LCY,服务端实际发送给航司:`originAirport: LCY` + - 传入 originAirport: LON,服务端实际发送给航司:`originAirport: LON` + +**destination/destinationAirport 逻辑同 origin/originAirport** + +### 这个接口会使用缓存吗? + +默认情况本接口会读取一个 5 分钟短效缓存,如果你在 5 分钟重复请求同一个路线,那么第二次会直接返回缓存数据。 + +你也可以通过 `acceptCacheMinutes` 自己控制缓存时效。 + +## 响应实体介绍 + +### 响应示例 + +```json +{ + "code": 0, + "msg": null, + "sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75", + "itineraries": [ + { + "outboundSegments": [ + { + "index": 1, + "carrier": "VJ", + "flightNumber": "VJ884", + "operatingCarrier": "", + "operatingFlightnumber": "", + "originAirport": "BOM", + "destinationAirport": "SGN", + "departureTime": "202510270040", + "arrivalTime": "202510270705", + "departureTerminal": "", + "arrivalTerminal": "", + "stopCities": "", + "duration": 295, + "aircraftCode": "330", + "codeShare": false + } + ], + "inboundSegments": [], + "fares": [ + { + "fareBasis": "", + "rtnFareBasis": "", + "fareFamily": "Lion Economy", + "rtnFareFamily": "", + "currency": "USD", + "adultFare": 100.00, + "adultTax": 50.00, + "bookingCode": "Y", + "availableSeats": 7, + "flightPolicy": { + "airlineCode": "SL", + "fareFamilyType": "Lion Economy", + "freeAncillaryList": [ + { "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 7, "size": "" }, + { "categoryCode": "StandardCheckedBaggage", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 20 } + ] + } + } + ] + } + ] +} +``` + +> **提示**:`itineraries` 数组中的每个元素是一个完整的行程方案,详见 [Itinerary 实体定义](08-01_itinerary.md) diff --git a/docs/03-02_shopping-select.md b/docs/03-02_shopping-select.md new file mode 100644 index 0000000..0f5f81b --- /dev/null +++ b/docs/03-02_shopping-select.md @@ -0,0 +1,206 @@ + +# shopping/select - 验价/确定航班 + +# 确定航班(shopping/select)API 说明文档 + +## 用户使用场景 + +> 本接口目的是: +> 验价:可满足8秒内响应,>95%成功率。 +> 进一步获取免费行李/退改规则信息(对于无法再shopping/search获取的航司场景) + +## 错误场景(不应该使用本接口的场景) + +> 如航司在点选行程后占位,本接口需谨慎使用,防止航司侧异常。 + +## 性能指标 + +> - 响应速度:95%的请求<8秒。 +> - 请求频率QPS:根据你的商务合同确定 +> - 接口成功率:>95% + +## 请求说明 + +| **请求地址** | https://${endpoint}/shopping/select | +| --- | --- | +| **请求方法** | POST | +| **注意事项** | Header必须带压缩请求。 | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### + +### 请求体 + +```json +{ + "sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75", + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "u2web", + "proxy": "myproxy-us", + "outboundFareFamily": "promo", + "inboundFareFamily": "", + "outboundSegments": [ + { + "carrier": "SL", + "originAirport": "PHS", + "destinationAirport": "DMK", + "flightNumber": "SL557", + "departureDate": "20251119" + } + ], + "inboundSegments": [ + ] +} +``` + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| sessionId | String | 否 | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | 目的是为了复用前一步的shopping/search的session,提升速度&成功率。如带入session,请务必保障:1. 出发到达/旅行日期/人数与前一步保持一致。 | +| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW:单程OneWayRT:往返RoundTrip | +| origin | String | 是 | "CJJ" | 出发地,为IATA 3字码兼容城市或者机场3字码 | +| destination | String | 是 | "SHA" | 到达地,为IATA3字码兼容城市或者机场3字码 | +| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501) | +| returnDate | String | 否 | 20240423(仅 RT 必填) | 返程日期,格式同 departureDate,仅当 journeyType=RT 时必传,OW 时可传空 | +| adults | Integer | 是 | 2 | 成人,乘机人数量 | +| children | Integer | 是 | 1 | 儿童,乘机人数量 | +| infants | Integer | 是 | 0 | 婴儿,乘机人数量 | +| outboundSegments | Array | 是 | | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| inboundSegments | Array | 否 | | 返程航段信息往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| outboundFareFamily | String | 是 | | 去程FareFamily的名字 | +| inboundFareFamily | String | 否 | | 回程FareFamily的名字仅往返程有值 | +| agent | String | 是 | "u2web" | 你自行定义的执行器编码(Agent Code) | +| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-USUS代表美国 | +| acceptCacheMinutes | Integer | 否 | 5 | 默认值:5**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存,用户可以指定缓存时长,不填写为5。 | +| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。
- **false**: 同步模式,等待完整结果返回
- **true**: 异步模式,立即返回 202,结果通过 Webhook 回调

**异步模式说明**:
1. 系统会同步检查是否有新鲜缓存(acceptCacheMinutes),如有则立即返回
2. 无缓存时返回 202 Accepted,包含 requestId 用于追踪
3. 航司数据返回后,通过预先配置的 Webhook 回调通知

**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook | +| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`select_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}_{flightNumber}`,最大 50 个字符 | + +## 返回参数 + +探索一个返回参数json(同shopping/search),https://jsonhero.io/j/XmTLWK94Ae5X + +| 参数名称 | 类型 | 示例值 | 说明 | +| --- | --- | --- | --- | +| itineraries | Array | - | 航线组合列表,包含不同航班拼接的行程方案 | +| baggageList | | | | +| | | | | +| code | Integer | 0 | 系统状态码:0 = 成功,非 0 为失败 | +| msg | String | null | 系统消息:成功时为 null,失败时返回具体系统提示信息 | +| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID: 与航司通信的session值,可以用于加速后续动作,如继续获取包裹,选座,下单。 | + +### 异步模式响应(async=true) + +当 `async=true` 且无可用缓存时,系统立即返回 202 Accepted: + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| code | Integer | 202(表示请求已接受,正在处理中) | +| msg | String | "Accepted. Please wait for the webhook callback" | +| requestId | String | 请求ID(UUID),用于追踪和关联 Webhook 回调 | +| status | String | "PROCESSING"(处理中) | + +完整结果将通过预先配置的 Webhook 回调返回。 + +## 响应实体介绍 + +### 响应示例 + +```json +{ + "code": 0, + "msg": null, + "sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75", + "itineraries": [ + { + "outboundSegments": [ + { + "index": 1, + "carrier": "SL", + "flightNumber": "SL557", + "operatingCarrier": "", + "operatingFlightnumber": "", + "originAirport": "PHS", + "destinationAirport": "DMK", + "departureTime": "202511190800", + "arrivalTime": "202511191130", + "departureTerminal": "", + "arrivalTerminal": "", + "stopCities": "", + "duration": 210, + "aircraftCode": "320", + "codeShare": false + } + ], + "inboundSegments": [], + "fares": [ + { + "fareBasis": "", + "rtnFareBasis": "", + "fareFamily": "promo", + "rtnFareFamily": "", + "currency": "USD", + "adultFare": 80.00, + "adultTax": 30.00, + "bookingCode": "Y", + "availableSeats": 9, + "flightPolicy": { + "airlineCode": "SL", + "fareFamilyType": "promo", + "freeAncillaryList": [ + { "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 7, "size": "" }, + { "categoryCode": "StandardCheckedBaggage", "segmentIndex": 1, "paxType": "ADT", "piece": 0, "weight": 0 } + ] + } + } + ] + } + ], + "baggageList": [] +} +``` + +> **提示**:`itineraries` 数组中的每个元素是一个完整的行程方案,详见 [Itinerary 实体定义](08-01_itinerary.md) + +# 常见问题 + +## 航司不稳定的时候,如何保障成功率>95% + +我们支持多个通过proxy入参实现并发请求。 + +**案例1:竖线分隔,随机挑选** + +proxy= myproxy-us|myproxy-uk + +行为:随机从myproxy-us中挑选一个 + +**案例2:加号分隔** + +proxy= myproxy-us+myproxy-uk + +行为:启动两个进程对数据源(航司)做并发访问,确保成功率。 + +**案例3:组合** + +proxy= myproxy1-us|myproxy2-au+myproxy1-uk|myproxy2-de + +行为:随机挑选myproxy1|myproxy2的代理,并启动两个进程对数据源做并发访问。 + +## 如果航司侧响应大于8秒,这个请求会被抛弃吗? + +不会,为了兼容异常场景,该接口我们最大等待航司响应的时长为15秒。 + diff --git a/docs/03-03_shopping-baggage.md b/docs/03-03_shopping-baggage.md new file mode 100644 index 0000000..c05e337 --- /dev/null +++ b/docs/03-03_shopping-baggage.md @@ -0,0 +1,264 @@ + +# shopping/baggage - 行李报价 + +## 用户使用场景 + +> 获取行程可支持的收费行李规格,以及各个规格的行李价格 + +## 性能指标 + +> - 通过sessionId访问,或者sessionId有效,响应时间<8秒。 + +## 请求说明 + +| **请求地址** | https://${endpoint}/shopping/baggage | +| --- | --- | +| **请求方法** | POST | +| **注意事项** | Header必须带压缩请求。 | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### + +### 请求体 + +```json +{ + "sessionId": "66d807b0-abbc-4b57-aeff-bbf4fc29fb75", + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "u2web", + "proxy": "myproxy-us", + "outboundFareFamily": "promo", + "inboundFareFamily": "", + "outboundSegments": [ + { + "carrier": "SL", + "originAirport": "PHS", + "destinationAirport": "DMK", + "flightNumber": "SL557", + "departureDate": "20251119" + } + ], + "inboundSegments": [ + ] +} +``` + +> **注意**:本接口的请求参数与 [shopping/select](03-02_shopping-select.md) 完全一致,详细的参数说明请参考 shopping/select 文档。 + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| sessionId | String | 否 | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | 目的是为了复用前一步的shopping/search的session,提升速度&成功率。如带入session,请务必保障:1. 出发到达/旅行日期/人数与前一步保持一致。 | +| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW:单程OneWayRT:往返RoundTrip | +| origin | String | 是 | "CJJ" | 出发地,为IATA 3字码兼容城市或者机场3字码 | +| destination | String | 是 | "SHA" | 到达地,为IATA3字码兼容城市或者机场3字码 | +| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501) | +| returnDate | String | 否 | 20240423(仅 RT 必填) | 返程日期,格式同 departureDate,仅当 journeyType=RT 时必传,OW 时可传空 | +| adults | Integer | 是 | 2 | 成人,乘机人数量 | +| children | Integer | 是 | 1 | 儿童,乘机人数量 | +| infants | Integer | 是 | 0 | 婴儿,乘机人数量 | +| outboundSegments | Array | 是 | | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| inboundSegments | Array | 否 | | 返程航段信息往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| outboundFareFamily | String | 是 | | 去程FareFamily的名字 | +| inboundFareFamily | String | 否 | | 回程FareFamily的名字仅往返程有值 | +| agent | String | 是 | "u2web" | 你自行定义的执行器编码(Agent Code) | +| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-USUS代表美国 | +| acceptCacheMinutes | Integer | 否 | 5 | 默认值:5**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存,用户可以指定缓存时长,不填写为5。 | +| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。
- **false**: 同步模式,等待完整结果返回
- **true**: 异步模式,立即返回 202,结果通过 Webhook 回调

**异步模式说明**:
1. 系统会同步检查是否有新鲜缓存(acceptCacheMinutes),如有则立即返回
2. 无缓存时返回 202 Accepted,包含 requestId 用于追踪
3. 航司数据返回后,通过预先配置的 Webhook 回调通知

**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook | +| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`baggage_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}_{flightNumber}`,最大 50 个字符 | + +> **参数一致性说明**:本接口的所有请求参数(Header 和 请求体)与 [shopping/select](03-02_shopping-select.md) 完全相同。如果您已经熟悉 shopping/select 接口,可以直接使用相同的参数格式调用本接口。 + +## 返回参数 + +| 参数名称 | 类型 | 示例值 | 说明 | +| --- | --- | --- | --- | +| code | Integer | 0 | 系统状态码:0 = 成功,非 0 为失败 | +| msg | String | null | 系统消息:成功时为 null,失败时返回具体系统提示信息 | +| sessionId | String | 66d807b0-abbc-4b57-aeff-bbf4fc29fb75 | UUID: 与航司通信的session值,可以用于加速后续动作,如继续获取包裹,选座,下单。 | +| itineraries | Array\ | - | 航线组合列表。由于在本接口已确定路线和FareFamily,数组长度 ≤ 1 | +| ancillaries | Array\<[AncillaryGroup](#ancillarygroup)\> | {json实体} | 辅营产品分组列表(新结构,三级嵌套) | +| ancillaryList | Array | {json实体} | *(兼容旧结构)* 当 `ancillaries` 为空时可能存在,建议优先使用 `ancillaries` | + +### 异步模式响应(async=true) + +当 `async=true` 且无可用缓存时,系统立即返回 202 Accepted: + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| code | Integer | 202(表示请求已接受,正在处理中) | +| msg | String | "Accepted. Please wait for the webhook callback" | +| requestId | String | 请求ID(UUID),用于追踪和关联 Webhook 回调 | +| status | String | "PROCESSING"(处理中) | + +完整结果将通过预先配置的 Webhook 回调返回。 + +--- + +## 响应实体介绍 + +响应采用三级嵌套结构:**AncillaryGroup** → **AncillaryCategory** → **BaggageOption** + +``` +ancillaries (Array) + └─ AncillaryGroup ← 按航程方向 + 运价家族分组 + └─ categoryList (Array) + └─ AncillaryCategory ← 按行李类别分组 + └─ categoryDetail (Array) + └─ BaggageOption ← 具体行李选项及价格 +``` + +### AncillaryGroup(最外层) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| currency | String | 售价币种(ISO 4217),如 `CAD`、`EUR`、`USD` | +| fareFamilyType | String | 运价家族类型,如 `BASIC`、`SMART`、`PROMO` | +| journeyDirection | String | 航程方向:`outbound`(去程)/ `inbound`(回程) | +| categoryList | Array\<[AncillaryCategory](#ancillarycategory)\> | 辅营类别列表 | + +### AncillaryCategory(中间层) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| categoryCode | String | 类别代码,见 [categoryCode 说明](#categorycode-说明) | +| maxPieces | String | 该类别最多可购买件数 | +| pricingMode | String | 定价模式,见 [pricingMode 说明](#pricingmode-说明) | +| categoryDetail | Array\<[BaggageOption](#baggageoption)\> | 选项列表 | + +### BaggageOption(最内层) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| pieceNo | Integer | 第 N 件。STEP 模式下为序号(1, 2, 3...),FLAT/TIERED 模式下为 1 | +| weight | BigDecimal | 行李额重量(KG) | +| size | String | 尺寸限制,如 `"55x40x23cm"` | +| price | BigDecimal | 该选项/该件价格 | +| maxPieces | Integer | 该选项可购买数量 | + +--- + +### 响应示例 + +#### 示例1:FLAT + STEP 混合 + +```json +{ + "code": 0, + "msg": null, + "sessionId": "NYW34327-f212fbacdd054dda8a509e846e300ebf", + "itineraries": [], + "ancillaries": [ + { + "currency": "CAD", + "fareFamilyType": "BASIC", + "journeyDirection": "outbound", + "categoryList": [ + { + "categoryCode": "CabinBaggageOverheadLocker", + "maxPieces": "1", + "pricingMode": "FLAT", + "categoryDetail": [ + { "pieceNo": 1, "weight": 10, "size": "55x40x23cm", "price": 39.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 23, "size": "55x40x23cm", "price": 64.00, "maxPieces": 1 } + ] + }, + { + "categoryCode": "StandardCheckedBaggage", + "maxPieces": "5", + "pricingMode": "STEP", + "categoryDetail": [ + { "pieceNo": 1, "weight": 10, "size": "", "price": 39.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 }, + { "pieceNo": 2, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 }, + { "pieceNo": 3, "weight": 23, "size": "", "price": 99.00, "maxPieces": 1 } + ] + } + ] + }, + { + "currency": "CAD", + "fareFamilyType": "BASIC", + "journeyDirection": "inbound", + "categoryList": [ + { + "categoryCode": "CabinBaggageOverheadLocker", + "maxPieces": "1", + "pricingMode": "FLAT", + "categoryDetail": [ + { "pieceNo": 1, "weight": 10, "size": "55x40x23cm", "price": 35.00, "maxPieces": 1 } + ] + } + ] + } + ] +} +``` + +#### 示例2:TIERED 模式(按重量档位) + +```json +{ + "code": 0, + "msg": null, + "sessionId": "...", + "ancillaries": [ + { + "currency": "CNY", + "fareFamilyType": "ECONOMY", + "journeyDirection": "outbound", + "categoryList": [ + { + "categoryCode": "StandardCheckedBaggage", + "maxPieces": "1", + "pricingMode": "TIERED", + "categoryDetail": [ + { "pieceNo": 1, "weight": 20, "size": "", "price": 300.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 25, "size": "", "price": 500.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 30, "size": "", "price": 800.00, "maxPieces": 1 } + ] + } + ] + } + ] +} +``` + +--- + +### categoryCode 说明 + +| categoryCode | 说明 | +|--------------|------| +| CabinBaggageOverheadLocker | 手提行李(头顶行李架) | +| CabinBaggageUnderSeat | 手提行李(座椅下) | +| StandardCheckedBaggage | 托运行李 | + +### pricingMode 说明 + +| pricingMode | 说明 | 渲染建议 | +|-------------|------|----------| +| FLAT | 固定单价,每件独立产品 | 卡片网格展示,每个选项一张卡片 | +| STEP | 逐件定价,第 N 件对应不同价格 | 表格展示,按 pieceNo 分组,同 pieceNo 行用背景色合并 | +| TIERED | 按总重量档位选择,不计件 | 单选列表展示,互斥选项 | + +> **提示**: +> - `ancillaries` 数组按 `journeyDirection`(outbound/inbound)和 `fareFamilyType` 分组 +> - 优先使用 `ancillaries`(新三级结构),`ancillaryList` 为兼容旧结构 +> - 同一 `pricingMode` 内,`categoryDetail` 按 `pieceNo` 升序排列 +> - STEP 模式下,相同 `pieceNo` 可能有多个重量/价格选项(如第 1 件可选 10kg 或 23kg) diff --git a/docs/03-04_shopping-seat.md b/docs/03-04_shopping-seat.md new file mode 100644 index 0000000..6e9b2bf --- /dev/null +++ b/docs/03-04_shopping-seat.md @@ -0,0 +1,354 @@ + +# shopping/seat - 选座报价 + +## 用户使用场景 + +> 获取航班座位图信息,包括每个座位的位置、价格、状态(可用/已占)、以及座位特性(靠窗、靠过道、紧急出口等) + +## 性能指标 + +> - 通过 sessionId 访问,或者 sessionId 有效,响应时间 < 10 秒 +> - 接口成功率:> 90% + +## 请求说明 + +| **请求地址** | https://${endpoint}/shopping/seat | +| --- | --- | +| **请求方法** | POST | +| **注意事项** | Header 必须带压缩请求 | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### 请求体 + +```json +{ + "sessionId": "743768c1-8bdc-46ed-a586-94fdbd0ce24a", + "journeyType": "OW", + "origin": "OTP", + "destination": "CAG", + "departureDate": "20260116", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "AXZ", + "proxy": "res_mipu_bookxWdyg-it", + "outboundFareFamily": "BASIC", + "inboundFareFamily": "", + "outboundSegments": [ + { + "carrier": "XZ", + "originAirport": "OTP", + "destinationAirport": "FCO", + "flightNumber": "XZ3115", + "departureDate": "20260116" + }, + { + "carrier": "XZ", + "originAirport": "FCO", + "destinationAirport": "CAG", + "flightNumber": "XZ2341", + "departureDate": "20260116" + } + ], + "inboundSegments": [] +} +``` + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| sessionId | String | 否 | 743768c1-8bdc-46ed-a586-94fdbd0ce24a | 目的是为了复用前一步的 shopping/search 或 shopping/select 的 session,提升速度 & 成功率。如带入 session,请务必保障:1. 出发到达/旅行日期/人数与前一步保持一致 | +| journeyType | String | 是 | "OW" | 行程为单程还是往返程
OW:单程 OneWay
RT:往返 RoundTrip | +| origin | String | 是 | "OTP" | 出发地,为 IATA 3 字码兼容城市或者机场 3 字码 | +| destination | String | 是 | "CAG" | 到达地,为 IATA 3 字码兼容城市或者机场 3 字码 | +| departureDate | String | 是 | 20260116 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501) | +| returnDate | String | 否 | 20240423(仅 RT 必填) | 返程日期,格式同 departureDate,仅当 journeyType=RT 时必传,OW 时可传空 | +| adults | Integer | 是 | 1 | 成人,乘机人数量 | +| children | Integer | 是 | 0 | 儿童,乘机人数量 | +| infants | Integer | 是 | 0 | 婴儿,乘机人数量 | +| agent | String | 是 | "AXZ" | Agent 代码 | +| proxy | String | 是 | "res_mipu_bookxWdyg-it" | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如 myproxy-US,US 代表美国 | +| outboundSegments | Array | 是 | 见示例 | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| inboundSegments | Array | 否 | | 返程航段信息,往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| outboundFareFamily | String | 是 | "BASIC" | 去程 FareFamily 的名字 | +| inboundFareFamily | String | 否 | | 回程 FareFamily 的名字,仅往返程有值 | +| acceptCacheMinutes | Integer | 否 | 5 | 默认值:5
**如无特殊情况,本参数不建议设置或调整。**为了避免频繁请求航司设置的缓存,用户可以指定缓存时长,不填写为 5 | +| async | Boolean | 是 | false | 是否启用 Webhook 异步模式。
- **false**: 同步模式,等待完整结果返回
- **true**: 异步模式,立即返回 202,结果通过 Webhook 回调

**异步模式说明**:
1. 系统会同步检查是否有新鲜缓存(acceptCacheMinutes),如有则立即返回
2. 无缓存时返回 202 Accepted,包含 requestId 用于追踪
3. 航司数据返回后,通过预先配置的 Webhook 回调通知

**注意**:使用异步模式需提前配置 `shopping_response` 类型的 Webhook | +| callbackId | String | 否 | - | 异步模式下的业务回调标识,用于客户自行匹配业务。建议格式:`seat_{origin}_{destination}_{departureDate}_{adults}-{children}-{infants}_{flightNumber}`,最大 50 个字符 | + +## 返回参数 + +### 响应示例 + +```json +{ + "cached": false, + "code": 0, + "itineraries": [], + "msg": "success", + "seatMapList": [ + { + "segmentIndex": 1, + "journeyDirection": "outbound", + "segment": { + "flightNumber": "DM200", + "dptAirport": "AUA", + "arrAirport": "CUR", + "departureTime": "202604261100", + "arrivalTime": "202604261150" + }, + "cabins": [ + { + "deck": "main", + "cabinType": "Business", + "cabinLayout": { + "columns": [ + { "designator": "A", "characteristics": "W" }, + { "designator": "C", "characteristics": "A" }, + { "designator": "D", "characteristics": "A" }, + { "designator": "G", "characteristics": "A" }, + { "designator": "J", "characteristics": "W" } + ], + "rows": { "first": 1, "last": 8 }, + "exitRowPositions": [] + }, + "rows": [ + { + "number": 1, + "seats": [ + { + "column": "A", + "currency": "EUR", + "price": 120.0, + "seatCharacteristics": ["W", "L"], + "seatStatus": "F" + } + ] + } + ] + }, + { + "deck": "main", + "cabinType": "Economy", + "cabinLayout": { + "columns": [ + { "designator": "A", "characteristics": "W" }, + { "designator": "B", "characteristics": "M" }, + { "designator": "C", "characteristics": "A" }, + { "designator": "D", "characteristics": "A" }, + { "designator": "E", "characteristics": "M" }, + { "designator": "F", "characteristics": "W" } + ], + "rows": { "first": 9, "last": 32 }, + "exitRowPositions": [ + { "first": 16, "last": 16 }, + { "first": 17, "last": 17 } + ] + }, + "rows": [ + { + "number": 9, + "seats": [ + { + "column": "A", + "currency": "EUR", + "price": 21.0, + "seatCharacteristics": ["W", "IE", "E", "L"], + "seatStatus": "F" + } + ] + } + ] + } + ] + } + ], + "sessionId": "shopping-seat-fa149f406bcd4df9a1a318d230afc422" +} +``` + +### 响应字段说明 + +| 参数名称 | 类型 | 说明 | +| --- | --- | --- | +| code | Integer | 系统状态码:0 = 成功,非 0 为失败 | +| msg | String | 系统消息:成功时为 null,失败时返回具体系统提示信息 | +| cached | Boolean | 是否使用缓存数据 | +| sessionId | String | UUID:与航司通信的 session 值,可以用于加速后续动作,如下单 | +| itineraries | Array | 航线组合列表(本接口通常为空数组) | +| seatMapList | Array | **座位图列表,按航段返回每个航段的座位信息** | + +### 异步模式响应(async=true) + +当 `async=true` 且无可用缓存时,系统立即返回 202 Accepted: + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| code | Integer | 202(表示请求已接受,正在处理中) | +| msg | String | "Accepted. Please wait for the webhook callback" | +| requestId | String | 请求ID(UUID),用于追踪和关联 Webhook 回调 | +| status | String | "PROCESSING"(处理中) | + +完整结果将通过预先配置的 Webhook 回调返回。 + +### SeatMap(座位图)对象说明 + +seatMapList 数组中的每个元素代表一个航段的座位图,结构如下: + +| 字段名 | 类型 | 说明 | +| --- | --- | --- | +| segmentIndex | Integer | 航段索引(从 1 开始),对应 outboundSegments 或 inboundSegments 中的位置 | +| journeyDirection | String | 行程方向:`outbound`(去程)/ `inbound`(返程) | +| segment | Object | 航段信息对象,包含航班号、出发/到达机场和时间 | +| cabins | Array\ | 客舱信息列表,按舱位等级排列(如商务舱在前、经济舱在后) | + +#### Segment(航段)对象 + +| 字段名 | 类型 | 说明 | +| --- | --- | --- | +| flightNumber | String | 航班号,如 "DM200" | +| dptAirport | String | 出发机场 IATA 三字码,如 "AUA" | +| arrAirport | String | 到达机场 IATA 三字码,如 "CUR" | +| departureTime | String | 出发时间,格式 `YYYYMMDDHHmm`,如 "202604261100" | +| arrivalTime | String | 到达时间,格式 `YYYYMMDDHHmm`,如 "202604261150" | + +#### Cabin(机舱)对象 + +| 字段名 | 类型 | 说明 | +| --- | --- | --- | +| deck | String | 甲板类型,如 "main" 表示主甲板 | +| cabinType | String | 客舱类型,可选值:`Economy`(经济舱)、`Business`(商务舱)、`FirstClass`(头等舱)、`PremiumEconomy`(高端经济舱) | +| cabinLayout | Object | 机舱布局信息 | +| rows | Array\ | 座位行列表 | + +#### CabinLayout(机舱布局)对象 + +| 字段名 | 类型 | 说明 | +| --- | --- | --- | +| columns | Array | 列信息数组,描述每列的座位特性 | +| rows | Object | 行范围,包含 `first`(起始行号)和 `last`(结束行号) | +| exitRowPositions | Array | 紧急出口行位置数组,每个对象包含 `first` 和 `last` 行号 | + +#### Column(列)对象 + +| 字段名 | 类型 | 说明 | +| --- | --- | --- | +| designator | String | 列标识符,如 "A"、"B"、"C" 等 | +| characteristics | String | 列特性代码(见座位特性说明) | + +#### Row(座位行)对象 + +| 字段名 | 类型 | 说明 | +| --- | --- | --- | +| number | Integer | 行号 | +| seats | Array | 该行的座位列表 | + +#### Seat(座位)对象 + +| 字段名 | 类型 | 示值 | 说明 | +| --- | --- | --- | --- | +| column | String | "A" | 列标识符(A、B、C 等) | +| seatStatus | String | "F" | 座位状态
**F**:Free(可用)
**O**:Occupied(已占) | +| seatCharacteristics | Array | ["W", "I"] | 座位特性数组(见下文座位特性说明) | +| price | BigDecimal | 15.0 | 座位价格(该座位选座费用) | +| currency | String | "EUR" | 价格币种 | + +### 座位特性说明 + +seatCharacteristics 数组中的特性代码: + +| 代码 | 英文 | 中文说明 | +| --- | --- | --- | +| W | Window | 靠窗座位 | +| A | Aisle | 靠过道座位 | +| M | Middle | 中间座位 | +| I | Standard | 标准座位 | +| IE | ExitRow | 紧急出口排 | +| E | ExtraLegroom | 额外腿部空间 | +| L | LimitedRecline | 有限后倾 | + +## 常见问题 + +### 如何理解 seatMapList 的结构? + +每个航段(Segment)都有一个独立的座位图,每个座位图可包含多个舱位(cabins)。例如: +- 单程直飞:seatMapList 长度为 1,该航段可能包含 1 个或多个 cabins(如商务舱 + 经济舱) +- 单程转机(2 个航段):seatMapList 长度为 2,每个航段各有独立的 cabins +- 往返程直飞:seatMapList 长度为 2(去程 + 回程),每个航段各有独立的 cabins + +segmentIndex 从 1 开始,与请求中的 outboundSegments/inboundSegments 数组索引对应。cabins 数组中每个 Cabin 对象代表一个舱位区域,通过 cabinType 区分舱位等级。 + +### 座位状态有哪些? + +- **F (Free)**:座位可选,未被占用 +- **O (Occupied)**:座位已被占用,不可选择 + +### 如何识别靠窗/靠过道座位? + +通过 seatCharacteristics 数组判断: +- 包含 "W":靠窗座位 +- 包含 "A":靠过道座位 +- 包含 "M":中间座位(通常在 3-3 或 3-4-3 布局的中间列) + +### 紧急出口座位如何识别? + +包含以下特性的座位通常是紧急出口排: +- **IE (ExitRow)**:紧急出口排 +- **E (ExtraLegroom)**:额外腿部空间 + +这些座位通常价格更高,腿部空间更大,但可能有年龄限制(如 14 岁以上)。 + +### 价格为 0 的座位是什么意思? + +价格为 0 表示该座位免费可选,不需要额外付费。这种情况常见于: +- 高端舱位(商务舱、头等舱) +- 航司促销活动 +- 某些会员权益 + +## 业务案例 + +### 单人单程选座查询 + +#### 请求(Request) + +```json +{ + "journeyType": "OW", + "origin": "OTP", + "destination": "CAG", + "departureDate": "20260116", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "AXZ", + "outboundFareFamily": "BASIC", + "outboundSegments": [ + { + "carrier": "XZ", + "originAirport": "OTP", + "destinationAirport": "CAG", + "flightNumber": "XZ3115", + "departureDate": "20260116" + } + ] +} +``` + +#### 响应(Response) + +响应包含完整的座位图,每个座位都有: +- 位置信息(行号 + 列号) +- 价格信息(price + currency) +- 状态信息(seatStatus:F/O) +- 特性信息(seatCharacteristics:靠窗/靠过道/紧急出口等) + +在线查看完整示例:https://jsonhero.io/j/XmTLWK94Ae5X diff --git a/docs/03-05_async-query.md b/docs/03-05_async-query.md new file mode 100644 index 0000000..a194881 --- /dev/null +++ b/docs/03-05_async-query.md @@ -0,0 +1,187 @@ + +# async/query - 异步查询 + +# 异步查询(async/query)API 说明文档 + +## 用户使用场景 + +> 本接口目的是: +> 当异步调用API返回code:202时,通过本接口查询异步请求的结果。 + +## 使用时机 + +> 当异步调用API返回 `code: 202` 时,表示请求已接收并正在后台处理。 +> 此时响应中会包含 `requestId`,使用本接口查询处理结果。 + +## 性能指标 + +> - 响应速度:毫秒级(直接从缓存读取) +> - 请求频率QPS:无限制 +> - 数据保留时间:24小时 + +## 请求说明 + +| **请求地址** | https://${endpoint}/async/query/{requestId} | +| --- | --- | +| **请求方法** | GET | +| **注意事项** | Header 必须带认证信息。 | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### 路径参数 + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| requestId | String | 是 | abc123-def456 | 异步请求返回的 requestId | + +## 响应说明 + +### 有结果(请求已处理完成) + +当异步请求已处理完成,**直接返回原始响应数据**(不包裹在统一响应结构中)。 + +**判断方式**:响应体中直接包含业务数据字段(如 `itineraries`、`bookingId` 等),无 `code` 字段包裹。 + +**示例 - 有结果(搜索)**: + +```json +{ + "itineraries": [ + { + "itineraryId": "xxx", + "outbound": { + "segments": [...] + }, + "price": { + "total": 1200.00 + } + } + ], + "sessionId": "session-xxx" +} +``` + +**示例 - 有结果(预订)**: + +```json +{ + "bookingId": "BK123456", + "status": "HOLD", + "expiryTime": "2025-01-20T12:00:00Z" +} +``` + +### 无结果(请求未找到或已过期) + +当异步请求不存在或已过期时,返回 404 错误。 + +**判断方式**:响应体中 `code: 404` + +**示例 - 无结果**: + +```json +{ + "code": 404, + "msg": "Not found" +} +``` + +**可能原因**: +1. `requestId` 不存在 +2. 请求已过期(超过 24 小时) +3. 请求属于其他租户(clientCode 不匹配) + +### 请求仍在处理中 + +当异步请求仍在处理中时,会返回处理中的状态。 + +**示例 - 处理中**: + +```json +{ + "code": 202, + "msg": "Processing", + "requestId": "abc123-def456" +} +``` + +## 完整示例 + +### 请求示例 + +```bash +curl -X GET "https://api.example.com/async/query/abc123-def456" \ + -H "client-key: your-client-key" \ + -H "client-secret: your-client-secret" +``` + +### 响应示例(有结果) + +```json +{ + "itineraries": [ + { + "itineraryId": "itinerary-001", + "outbound": { + "segments": [ + { + "carrier": "CA", + "flightNumber": "CA123", + "origin": "PEK", + "destination": "SHA", + "departureTime": "2025-01-20T08:00:00", + "arrivalTime": "2025-01-20T10:00:00" + } + ] + }, + "price": { + "currency": "CNY", + "total": 1200.00 + } + } + ], + "sessionId": "session-xxx" +} +``` + +### 响应示例(无结果) + +```json +{ + "code": 404, + "msg": "Not found" +} +``` + +## 常见问题 + +### Q: 如何获取 requestId? + +A: 当异步调用API返回 `code: 202` 时,响应中会包含 `requestId` 字段。 + +```json +{ + "code": 202, + "msg": "Async request accepted", + "requestId": "abc123-def456" +} +``` + +### Q: 结果会保留多久? + +A: 异步请求的结果会保留 24 小时,超时后自动删除。 + +### Q: 可以查询其他租户的请求吗? + +A: 不可以。接口会根据认证信息中的 clientCode 进行租户隔离,只能查询本租户的请求。 + +### Q: 建议的轮询策略是什么? + +A: 建议每隔 2-5 秒查询一次,最多查询 10 次。如果 10 次后仍未获取结果,可能是航司响应较慢,建议稍后重试。 diff --git a/docs/04-01_booking-hold.md b/docs/04-01_booking-hold.md new file mode 100644 index 0000000..0e6f3a1 --- /dev/null +++ b/docs/04-01_booking-hold.md @@ -0,0 +1,163 @@ + +# booking/hold - 下单 + +# 下单(booking/hold) API 说明文档 + +## 使用场景 + +> 1. 用户在航班验价通过后,确认预订意向,提交乘客与联系人信息以生成正式订单 + +## 错误场景(不应该使用本接口的场景) + +> 1. 未通过验价接口(checkRoute)获取有效 offerId 时(此场景需先调用验价接口,确保价格与航线有效性) +> 1. 验价接口返回的 offerId 已过期(通常与支付截止时间关联,过期后需重新验价获取新 offerId) +> 1. 乘客信息、证件信息未完整填写或格式错误时(应先校验信息合法性,避免接口调用失败) + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 15000ms(因需实时对接航司数据,响应速度明显慢于搜索接口) + +## 请求说明 + +| **请求地址** | https://${endpoint}/booking/hold | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### + +### 请求体 + +```json +{ + "orderId": "abbc4b57fb75", + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "adults": 1, + "children": 0, + "infants": 0, + "agent": "u2web", + "proxy": "myproxy-us", + "outboundFareFamily": "promo", + "inboundFareFamily": "", + "maximumFareThreshold": 15009.69, + "currency": "THB", + "outboundSegments": [ + { + "carrier": "SL", + "originAirport": "PHS", + "destinationAirport": "DMK", + "flightNumber": "SL557", + "departureDate": "20251119" + } + ], + "inboundSegments": [ + ], + "passengers": [ + { + "firstName": "first", + "lastName": "last", + "passengerType": "ADT", + "dateOfBirth": "19970616", + "gender": "M", + "documentNumber": "E12343214", + "documentType": "PP", + "documentIssuePlace": "CN", + "documentExpirationDate": "20260731", + "nationality": "CN", + "mobile": "0086-18923726222", + "frequentFlyerNumber": "", + "frequentFlyerCarrier": "" + } + ], + "contactInfo": { + "firstName": "san", + "lastName": "zhang", + "address": "dfdsaqqq", + "phoneCountryCode": "0086", + "phone": "18912345678", + "email": "san.zhang@gmail.com", + "postCode": "310006", + "city": "hangzhou", + "province": "zejiang", + "country": "CN" + } +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| orderId | String | 是 | abbc4b57fb75 | 你系统内的订单号,返回的有效报价唯一标识,用于关联待下单的航线与价格 | +| journeyType | String | 是 | "OW" | 行程为单程还是往返程OW:单程OneWayRT:往返RoundTrip | +| origin | String | 是 | "CJJ" | 出发地,为IATA 3字码兼容城市或者机场3字码 | +| destination | String | 是 | "SHA" | 到达地,为IATA3字码兼容城市或者机场3字码 | +| departureDate | String | 是 | 20240326 | 出发日期,格式为 `YYYYMMDD`(如 2024 年 5 月 1 日为 20240501) | +| returnDate | String | 否 | 20240423(仅 RT 必填) | 返程日期,格式同 departureDate,仅当 journeyType=RT 时必传,OW 时可传空 | +| adults | Integer | 是 | 2 | 成人,乘机人数量 | +| children | Integer | 是 | 1 | 儿童,乘机人数量 | +| infants | Integer | 是 | 0 | 婴儿,乘机人数量 | +| outboundSegments | Array | 是 | | 去程航段信息,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| inboundSegments | Array | 否 | | 返程航段信息往返行程为必须,详见 [FlightSegmentRequest](07-01_flight-segment-request.md) | +| outboundFareFamily | String | 是 | | 去程FareFamily的名字 | +| inboundFareFamily | String | 否 | | 回程FareFamily的名字仅往返程有值 | +| maximumFareThreshold | Number | **是** | 15009.69 | 最高票价阈值,超过此价格的报价将不会被接受(用于限制订单金额) | +| currency | String | **是** | "THB" | 出票币种代码,遵循ISO 4217标准,如:USD、CNY、THB、EUR等。
**注意**:必须为出票币种,系统不会进行任何汇率转换 | +| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-US,US代表美国 | +| passengers | Array | 是 | 请查看实体定义([Passenger](07-02_passenger.md)) | 乘客信息列表,支持 1-9 名乘客(具体数量受航司限制),每个数组元素为单个乘客详情 | +| contactInfo | Object | 是 | 请查看实体定义([ContactInfo](07-03_contact-info.md)) | 联系人信息,用于接收订单通知、行程单等 | + +## + +## 返回参数 + +探索一个响应实体:https://jsonhero.io/j/pPOhukgKMcln/editor + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| code | Integer | 0 | 系统状态码:0 = 成功,非 0 = 失败 | +| msg | String | null | 系统消息:成功时为 null,失败时返回具体错误提示(如 “证件有效期不足”“航司订单创建超时”) | +| pnr | String | 000000 | 仅同步模式会响应PNR信息注意对于不支持Hold的航司,我们也会实施与航司进行交互,并将动作完成在支付前一步,这种情况也可以返回000000 | +| pnrExpiryTime | String | YYYYMMDDHHMMSS20251225121212 | hold到的PNR有效期,此处时间为UTC 0时区格式。 | +| status | String | | * 异步模式,且进行中为进行中,失败,成功 * 同步模式,为完成,失败,成功 | +| orderId | String | | 入参给的orderId | +| feeItems | Array | 参考Feeitems实体 | 从航司处获取的报价细项拆分[feeItems 实体定义](08-06_fee-items.md) | + +## 实体定义说明 + +本接口涉及的实体对象定义如下: + +### 请求参数实体 + +| 实体名称 | 说明 | 链接 | +| --- | --- | --- | +| FlightSegmentRequest | 航段请求对象,用于描述单段航班的核心信息 | [查看详情](07-01_flight-segment-request.md) | +| Passenger | 乘客信息对象,包括身份信息、证件信息、常旅客信息等 | [查看详情](07-02_passenger.md) | +| ContactInfo | 联系人信息对象,用于接收订单通知、行程单等 | [查看详情](07-03_contact-info.md) | + +### 返回参数实体 + +| 实体名称 | 说明 | 链接 | +| --- | --- | --- | +| Itinerary | 行程组合信息,包含去程和返程航段列表 | [查看详情](08-01_itinerary.md) | +| FlightFare | 票价信息对象,描述不同乘客类型的费用构成 | [查看详情](08-02_flight-fare.md) | +| SegmentElement | 航段响应对象,描述单段航班的详细信息 | [查看详情](08-03_segment-element.md) | +| FreeBaggage (freeAncillaryList) | 免费行李额元素,描述每个航段每种乘客类型的免费行李配额 | [查看详情](08-04_flight-policy.md) | +| RefundRule (refundRules) | 退改规则对象,描述航班的退改签政策(预留字段,暂未提供) | [查看详情](08-04_flight-policy.md) | +| FlightPolicy | 航班政策对象,包含行李、退改等政策信息 | [查看详情](08-04_flight-policy.md) | +| AncillaryProduct | 附加产品对象,描述付费行李等附加服务 | [查看详情](08-05_ancillary-product.md) | +| feeItems | 费用明细对象,拆分航班预订的各类费用 | [查看详情](08-06_fee-items.md) | + diff --git a/docs/04-02_booking-payment.md b/docs/04-02_booking-payment.md new file mode 100644 index 0000000..0735dc4 --- /dev/null +++ b/docs/04-02_booking-payment.md @@ -0,0 +1,302 @@ + +# booking/payment - 支付 + +# 更新支付信息(booking/payment) API 说明文档 + +## 使用场景 + +> 1. 客户完成 booking/hold 下单后,需要更新订单的支付信息(如支付方式、信用卡信息等) +> 2. 用于在支付流程中记录或更新支付相关信息 +> 3. 使用 Apple Pay 支付时,可指定设备标签来选择执行任务的设备 + +## 错误场景(不应该使用本接口的场景) + +> 1. 未完成 booking/hold 下单,订单不存在时 +> 2. orderId 为空或未提供时 +> 3. 订单不属于当前客户(clientCode 不匹配)时 + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 2000ms +> - 接口成功率:> 99% + +## 请求说明 + +| **请求地址** | https://${endpoint}/booking/payment | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### + +### 请求体 + +```json +{ + "orderId": "abbc4b57fb75", + "proxy": "myproxy-us", + "paymentMethod": "CREDIT_CARD", + "retryCount": 0, + "creditCard": { + "number": "4111111111111111", + "CVV": "123", + "expiryMonth": "12", + "expiryYear": "25", + "lastName": "Doe", + "firstName": "John", + "country": "US" + } +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,必须是通过 booking/hold 创建的订单 | +| proxy | String | 是 | | 本次获取航司数据采用的代理方式。如果需要指定代理国家请在代理用户后面加上国家二字码。如myproxy-US,US代表美国 | +| paymentMethod | String | 否 | "CREDIT_CARD" | 支付方式代码:
**BALANCE**:余额支付
**CREDIT_CARD**:信用卡
**APPLE_PAY**:Apple Pay(支持设备标签) | +| retryCount | Integer | 否 | 0 | 支付重试次数,默认为 0(不重试) | +| creditCard | Object | 条件必填 | | 信用卡信息对象,当 paymentMethod 为 CREDIT_CARD 时必填 | +| ├─ number | String | **条件必填** | "4111111111111111" | 信用卡号(13-19 位数字) | +| ├─ CVV | String | **条件必填** | "123" | CVV 安全码(3-4 位数字) | +| ├─ expiryMonth | String | **条件必填** | "12" | 有效期月份(1-2 位数字) | +| ├─ expiryYear | String | **条件必填** | "25" | 有效期年份(2 或 4 位数字) | +| ├─ lastName | String | **条件必填** | "Doe" | 持卡人姓 | +| ├─ firstName | String | **条件必填** | "John" | 持卡人名 | +| ├─ country | String | **条件必填** | "US" | 持卡人国家(二字码) | +| ├─ province | String | 否 | | 持卡人省份/州 | +| ├─ city | String | 否 | | 持卡人城市 | +| ├─ postCode | String | 否 | | 持卡人邮编 | +| ├─ address | String | 否 | | 持卡人地址 | +| ├─ phone | String | 否 | | 持卡人电话(实体卡时使用) | +| ├─ email | String | 否 | | 持卡人邮箱(实体卡时使用) | +| ├─ maximumPaymentAmount | BigDecimal | 否 | | 最大支付金额 | +| └─ reusable | Boolean | 否 | false | 是否为多次卡(默认单次卡) | +| deviceTag | String | 否 | "EUR" | 设备标签,仅当 paymentMethod 为 APPLE_PAY 时可选,用于指定执行任务的设备 | + +### 设备标签说明 (deviceTag) + +- **适用场景**:仅当 `paymentMethod` 为 `APPLE_PAY` 时有效 +- **作用**:指定执行任务的设备标签,系统会优先分配有对应标签的设备 +- **可选性**:留空则自动分配设备 +- **示例**: + ```json + { + "orderId": "abbc4b57fb75", + "paymentMethod": "APPLE_PAY", + "deviceTag": "EUR" + } + ``` + +## 返回参数 + +### 成功响应 + +```json +{ + "code": 0, + "msg": "Payment info updated successfully", + "orderId": "abbc4b57fb75" +} +``` + +### 失败响应 + +#### 缺少 orderId +```json +{ + "code": 400, + "msg": "orderId is required" +} +``` + +#### 订单不存在 +```json +{ + "code": 404, + "msg": "Booking not found", + "orderId": "abbc4b57fb75" +} +``` + +#### 更新失败 +```json +{ + "code": 500, + "msg": "Failed to update payment info", + "orderId": "abbc4b57fb75" +} +``` + +### 响应字段说明 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| code | Integer | 0 | 系统状态码
**0**:成功
**400**:缺少必填参数(orderId)
**404**:订单不存在或不属于当前客户
**500**:更新支付信息失败 | +| msg | String | "Payment info updated successfully" | 系统消息:成功时返回成功提示,失败时返回具体错误信息 | +| orderId | String | "abbc4b57fb75" | 客户的订单号,与请求中的 orderId 一致 | + +## 业务流程 + +### 支付信息更新流程 + +``` +1. 客户完成航班搜索(shopping/search) + ↓ +2. 客户选择航班并验价(shopping/select) + ↓ +3. 客户提交订单(booking/hold)→ 获取 orderId + ↓ +4. 客户选择支付方式,调用支付接口(booking/payment) + ↓ +5. 支付完成 +``` + +## 常见问题 + +### 为什么 orderId 是必填的? + +orderId 是订单的唯一标识符,系统需要通过它来: +1. 定位到具体的订单 +2. 验证订单是否属于当前客户(通过 clientCode) +3. 确保只有合法的订单才能更新支付信息 + +### paymentMethod 支持哪些支付方式? + +系统支持三种支付方式:**BALANCE**(余额支付)、**CREDIT_CARD**(信用卡)、**APPLE_PAY**(Apple Pay,支持设备标签)。详见上方参数表格中的 paymentMethod 说明。 + +### Apple Pay 中的 deviceTag 是什么? + +`deviceTag` 是设备标签,用于指定执行 Apple Pay 支付任务的设备: +- 仅在 `paymentMethod` 为 `APPLE_PAY` 时有效 +- 可以指定有特定标签的设备执行任务 +- 留空则系统自动分配可用设备 +- 示例:`"deviceTag": "EUR"` 表示优先使用欧洲地区的设备 + +### 信用卡信息安全吗? + +本接口仅用于记录支付信息到系统中。实际的支付处理通常由第三方支付网关完成,建议: +1. 在前端不要存储真实的完整信用卡号 +2. 使用 PCI DSS 合规的支付网关处理实际支付 +3. 仅存储支付网关返回的支付令牌(token) + +### 如果订单不存在会怎样? + +接口会返回 `code: 404` 和 `msg: "Booking not found"`。这种情况通常由以下原因造成: +1. orderId 错误或不存在 +2. 订单属于其他客户(clientCode 不匹配) +3. 订单已过期或被删除 + +## 业务案例 + +### 更新信用卡支付信息 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "paymentMethod": "CREDIT_CARD", + "creditCard": { + "number": "4111111111111111", + "CVV": "123", + "expiryMonth": "12", + "expiryYear": "25", + "lastName": "Doe", + "firstName": "John", + "country": "US" + } +} +``` + +#### 响应(Response) + +```json +{ + "code": 0, + "msg": "Payment info updated successfully", + "orderId": "abbc4b57fb75" +} +``` + +### 使用 Apple Pay 并指定设备标签 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "paymentMethod": "APPLE_PAY", + "deviceTag": "EUR" +} +``` + +#### 响应(Response) + +```json +{ + "code": 0, + "msg": "Payment info updated successfully", + "orderId": "abbc4b57fb75" +} +``` + +### 缺少 orderId 的错误请求 + +#### 请求(Request) + +```json +{ + "paymentMethod": "CREDIT_CARD" +} +``` + +#### 响应(Response) + +```json +{ + "code": 400, + "msg": "orderId is required" +} +``` + +### 订单不存在的错误请求 + +#### 请求(Request) + +```json +{ + "orderId": "nonexistent-order-id", + "paymentMethod": "CREDIT_CARD" +} +``` + +#### 响应(Response) + +```json +{ + "code": 404, + "msg": "Booking not found", + "orderId": "nonexistent-order-id" +} +``` + +## 错误码说明 + +| 错误码 | 说明 | 处理建议 | +| --- | --- | --- | +| 0 | 成功 | 支付信息已成功更新 | +| 400 | 请求参数错误 | 检查是否提供了必填的 orderId | +| 404 | 订单不存在 | 确认订单 ID 是否正确,且订单属于当前客户 | +| 500 | 服务器内部错误 | 系统处理异常,请稍后重试或联系技术支持 | diff --git a/docs/04-03_booking-query.md b/docs/04-03_booking-query.md new file mode 100644 index 0000000..2833dde --- /dev/null +++ b/docs/04-03_booking-query.md @@ -0,0 +1,424 @@ + +# booking/query - 订单查询 + +# 订单查询(booking/query) API 说明文档 + +## 使用场景 + +> 1. 查询订单的详细信息和当前状态 +> 2. 在支付完成后查询订单确认状态 +> 3. 在客服系统中查询订单详情 +> 4. 获取订单的完整行程、乘客和费用信息 + +## 错误场景(不应该使用本接口的场景) + +> 1. 查询不存在的订单 ID +> 2. 查询不属于当前客户的订单(clientCode 不匹配) + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 500ms +> - 接口成功率:> 99.5% + +## 请求说明 + +| **请求地址** | https://${endpoint}/booking/query | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### + +### 请求体 + +```json +{ + "orderId": "abbc4b57fb75" +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,用于查询订单详情 | + +## 返回参数 + +### 成功响应 + +```json +{ + "orderId": "abbc4b57fb75", + "requestId": "req-12345", + "agentCode": "u2web", + "status": "CONFIRMED", + "payStatus": "PAID", + "origin": "MIL", + "destination": "CAG", + "journeyType": "OW", + "pnr": "ABC123", + "pnrExpiryTime": "2025-12-25T12:00:00Z", + "feeItems": { + "baseFare": 100.00, + "taxes": 50.50, + "total": 150.50, + "currency": "EUR" + }, + "passengers": [ + { + "lastName": "Rossi", + "firstName": "Mario", + "passengerType": "ADT", + "adult": true, + "child": false, + "infant": false, + "dateOfBirth": "1980-01-15", + "gender": "M", + "documentType": "PASSPORT", + "documentNumber": "AA1234567", + "ancillaries": [] + } + ], + "segments": [ + { + "airline": "AZ", + "flightNo": "AZ1234", + "dptAirport": "MIL", + "arrAirport": "CAG", + "dptTime": "2025-11-18T10:30:00Z", + "arrTime": "2025-11-18T12:15:00Z", + "equipment": "A320" + } + ], + "credential": null, + "promoCode": "SAVE10", + "createTime": "2024-11-15T10:00:00Z", + "updateTime": "2024-11-15T10:30:00Z", + "retryCount": 3, + "retriedCount": 0 +} +``` + +## 错误码说明 + +本接口可能返回以下错误码: + +| **错误码** | **错误名称** | **说明** | **处理建议** | +| --- | --- | --- | --- | +| 42005 | BOOKING_NOT_FOUND | 订单不存在 | 请检查 orderId 是否正确,或订单是否属于当前客户 | +| 41003 | PRICE_CHANGED | 票价高于预期 | 航司侧价格已变动,请检查响应中的 feeItems 获取最新价格,引导用户确认是否继续 | + +### 失败响应 + +#### 订单不存在 (42005) + +```json +{ + "code": 42005, + "msg": "Booking not found" +} +``` + +#### 票价变动 (41003) + +```json +{ + "code": 41003, + "msg": "Price changed. Please check the latest price in feeItems.", + "data": { + "orderId": "abbc4b57fb75", + "feeItems": { + "baseFare": 110.00, + "taxes": 52.50, + "total": 162.50, + "currency": "EUR" + } + } +} +``` + +### 响应字段说明 + +#### 基本字段 + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| orderId | String | 客户的订单号 | +| requestId | String | 请求 ID | +| agentCode | String | 代理商代码 | +| status | String | 订单状态(见下方状态说明) | +| payStatus | String | 支付状态(见下方支付状态说明) | +| origin | String | 出发地 IATA 3 字码 | +| destination | String | 到达地 IATA 3 字码 | +| journeyType | String | 行程类型
**OW**:单程
**RT**:往返 | + +#### PNR 相关 + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| pnr | String | 航司 PNR 码(订座记录编号) | +| pnrExpiryTime | String | PNR 有效期,ISO 8601 格式(UTC 0 时区) | + +#### 费用信息 + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| feeItems | Map | 费用明细(原始 JSON 对象)
详见:[feeItems 实体定义](/api-doc?doc=entity-fee-items) | + +#### 乘客和航班 + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| passengers | Array\ | 乘客信息列表(见 BookingPassenger 定义) | +| segments | Array\ | 航班段列表(见 BookingSegment 定义) | + +#### 其他信息 + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| credential | Object | 订单凭据信息 | +| promoCode | String | 促销码 | +| createTime | DateTime | 订单创建时间,ISO 8601 格式 | +| updateTime | DateTime | 订单最后更新时间,ISO 8601 格式 | +| retryCount | Integer | 可重试的次数 | +| retriedCount | Integer | 已经重试的次数 | + +## 状态说明 + +### 订单状态 (status) + +| **状态** | **说明** | +| --- | --- | +| PENDING | 订单处理中,等待航司确认 | +| CONFIRMED | 订单已确认,PNR 已生成 | +| CANCELLED | 订单已取消 | +| FAILED | 订单失败 | + +### 支付状态 (payStatus) + +| **状态** | **说明** | +| --- | --- | +| UNPAID | 未支付 | +| PENDING | 支付处理中 | +| PAID | 已支付 | +| REFUNDED | 已退款 | +| FAILED | 支付失败 | + +## 数据来源说明 + +本接口返回的数据优先从 `responseJson` 字段解析,如果 `responseJson` 为空或解析失败(code 不是 0 且 feeItems/passengers/segments 均为空),则从 `holdResponse` 字段回退获取。 + +## 业务流程 + +### 订单查询场景 + +``` +1. 客户下单(booking/hold)→ 获取 orderId + ↓ +2. 客户选择支付方式并支付(booking/payment) + ↓ +3. 查询订单状态(booking/query)→ 确认订单状态 + ↓ +4. 如需要,可以取消订单(booking/cancel) +``` + +## 常见问题 + +### 如果订单不存在会返回什么? + +接口会返回错误响应,code 为 40401,msg 为 "Booking not found"。这种情况通常由以下原因造成: +1. orderId 错误或不存在 +2. 订单属于其他客户(clientCode 不匹配) +3. 订单已过期或被删除 + +### status 和 payStatus 有什么区别? + +- **status**:订单的整体状态(PENDING/CONFIRMED/CANCELLED/FAILED) +- **payStatus**:订单的支付状态(UNPAID/PENDING/PAID/REFUNDED/FAILED) + +### feeItems 返回什么结构? + +`feeItems` 是一个原始的 Map 对象,直接从航司响应的 JSON 中解析。 + +详细的字段说明请参考:[feeItems 实体定义](/api-doc?doc=entity-fee-items) + +### passengers 和 segments 的详细定义是什么? + +#### BookingPassenger(乘客信息) + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| lastName | String | 乘客姓 | +| firstName | String | 乘客名 | +| passengerType | String | 乘客类型(ADT/CHD/INF) | +| adult | Boolean | 是否为成人 | +| child | Boolean | 是否为儿童 | +| infant | Boolean | 是否为婴儿 | +| dateOfBirth | String | 出生日期 | +| gender | String | 性别(M/F) | +| documentType | String | 证件类型(如 PASSPORT) | +| documentNumber | String | 证件号码 | +| ancillaries | Array | 附加服务列表 | + +#### BookingSegment(航班段信息) + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| airline | String | 航司代码(IATA 2 字码) | +| flightNo | String | 航班号(如 AZ1234) | +| dptAirport | String | 出发机场 IATA 3 字码 | +| arrAirport | String | 到达机场 IATA 3 字码 | +| dptTime | String | 出发时间,ISO 8601 格式 | +| arrTime | String | 到达时间,ISO 8601 格式 | +| equipment | String | 机型(如 A320、B737) | + +### 什么时候会从 holdResponse 回退数据? + +当 `responseJson` 满足以下任一条件时,会从 `holdResponse` 回退 feeItems/passengers/segments: +1. `responseJson` 为空 +2. `responseJson` 解析失败 +3. `code` 不是 0 且 feeItems/passengers/segments 均为空 + +这确保了即使订单最终状态失败,也能获取到 hold 阶段的数据。 + +### 遇到票价变动错误 (PRICE_CHANGED) 怎么办? + +当接口返回错误码 41003 (PRICE_CHANGED) 时,表示航司侧票价高于预期。这种情况通常发生在: + +1. 下单后到支付前,航司调整了票价 +2. 库存紧张导致票价上涨 +3. 促销活动价格已过期 + +**处理流程:** + +``` +1. 接收到 41003 错误响应 + ↓ +2. 从响应的 data.feeItems 中获取最新价格 + ↓ +3. 向用户展示价格变动信息 + ↓ +4. 询问用户是否继续 + ↓ +5a. 用户同意 → 重新调用 booking/hold 使用新价格 + ↓ +5b. 用户拒绝 → 取消订单流程 +``` + +**关键点:** + +- 错误响应中的 `feeItems` 包含最新的价格明细 +- 需要用户明确确认后才能继续交易 + +## 业务案例 + +### 查询已确认的订单 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75" +} +``` + +#### 响应(Response) + +```json +{ + "orderId": "abbc4b57fb75", + "agentCode": "u2web", + "status": "CONFIRMED", + "payStatus": "PAID", + "origin": "MIL", + "destination": "CAG", + "journeyType": "OW", + "pnr": "ABC123", + "passengers": [ + { + "lastName": "Rossi", + "firstName": "Mario", + "passengerType": "ADT" + } + ], + "segments": [ + { + "airline": "AZ", + "flightNo": "AZ1234", + "dptAirport": "MIL", + "arrAirport": "CAG", + "dptTime": "2025-11-18T10:30:00Z", + "arrTime": "2025-11-18T12:15:00Z" + } + ], + "createTime": "2024-11-15T10:00:00Z", + "updateTime": "2024-11-15T10:30:00Z" +} +``` + +### 查询往返程订单 + +#### 请求(Request) + +```json +{ + "orderId": "rt-order-12345" +} +``` + +#### 响应(Response) + +```json +{ + "orderId": "rt-order-12345", + "journeyType": "RT", + "origin": "MIL", + "destination": "CAG", + "status": "CONFIRMED", + "payStatus": "PAID", + "pnr": "XYZ789", + "passengers": [ + { + "lastName": "Rossi", + "firstName": "Mario", + "passengerType": "ADT" + }, + { + "lastName": "Bianchi", + "firstName": "Luca", + "passengerType": "ADT" + } + ], + "segments": [ + { + "airline": "AZ", + "flightNo": "AZ1234", + "dptAirport": "MIL", + "arrAirport": "CAG", + "dptTime": "2025-11-18T10:30:00Z", + "arrTime": "2025-11-18T12:15:00Z" + }, + { + "airline": "AZ", + "flightNo": "AZ5678", + "dptAirport": "CAG", + "arrAirport": "MIL", + "dptTime": "2025-11-25T14:00:00Z", + "arrTime": "2025-11-25T15:45:00Z" + } + ], + "createTime": "2024-11-15T10:00:00Z", + "updateTime": "2024-11-15T10:30:00Z" +} +``` diff --git a/docs/04-04_booking-cancel.md b/docs/04-04_booking-cancel.md new file mode 100644 index 0000000..c4d71bb --- /dev/null +++ b/docs/04-04_booking-cancel.md @@ -0,0 +1,343 @@ + +# booking/cancel - 取消订单 + +# 取消订单(booking/cancel) API 说明文档 + +## 使用场景 + +> 1. 客户主动取消未支付的订单 +> 2. 客户因行程变更取消已确认的订单(需符合退改规则) +> 3. 系统自动取消过期未支付的订单 +> 4. 客服代客取消订单 + +## 错误场景(不应该使用本接口的场景) + +> 1. 订单已不可取消(如已起飞航班) +> 1. 订单不属于当前客户(clientCode 不匹配) +> 1. 重复取消已取消的订单 + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 2000ms +> - 接口成功率:> 95% + +## 请求说明 + +| **请求地址** | https://${endpoint}/booking/cancel | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### + +### 请求体 + +```json +{ + "orderId": "abbc4b57fb75", + "cancelReason": "Customer request - flight change" +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,用于取消订单 | +| cancelReason | String | **是** | "Customer request" | 取消原因,用于记录和审计
**客户主动取消**:Customer request
**行程变更**:Flight change
**重复订单**:Duplicate booking
**支付超时**:Payment timeout
**其他**:Other reason | + +## 返回参数 + +### 成功响应 + +```json +{ + "code": 0, + "msg": "Booking cancelled successfully", + "historyId": 789 +} +``` + +### 失败响应 + +#### 订单不存在 +```json +{ + "code": 1, + "msg": "Failed to cancel booking" +} +``` + +### 响应字段说明 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| code | Integer | 0 | 系统状态码
**0**:取消成功
**1**:取消失败 | +| msg | String | "Booking cancelled successfully" | 系统消息:成功或失败的详细信息 | +| historyId | Long | 789 | 历史记录 ID,用于追踪取消操作记录 | + +## 业务流程 + +### 订单取消流程 + +``` +1. 客户下单(booking/hold)→ 获取 orderId + ↓ +2. 订单状态变化 + ├─ 未支付超时 → 系统自动取消 + ├─ 客户主动取消 → 调用取消接口 + └─ 行程变更取消 → 调用取消接口 + ↓ +3. 调用取消接口(booking/cancel)→ 提供取消原因 + ↓ +4a. 成功:code=0,订单状态更新为 CANCELLED + ↓ +4b. 失败:code=1,返回错误信息 +``` + +### 取消后状态变化 + +``` +取消前: +- 状态:PENDING / CONFIRMED +- PNR:已生成(如已确认) + +取消后: +- 状态:CANCELLED +- PNR:被航司释放 +- 历史记录:保存取消操作记录 +``` + +## 常见问题 + +### 取消订单后可以恢复吗? + +通常**不可以**。订单一旦取消: +1. PNR 被释放,座位被释放回库存 +2. 如果需要恢复,需要重新下单 +3. 某些航司可能支持恢复,但需要联系客服处理 + +### 哪些订单可以取消? + +可以取消的订单: +1. **状态为 PENDING**:处理中的订单 +2. **状态为 CONFIRMED**:已确认但未起飞的订单 +3. **符合退改规则**:在退改规则允许的时间范围内 + +不可取消的订单: +1. **已起飞**:航班已经起飞 +2. **已取消**:订单已经被取消过 +3. **已使用**:乘客已办理登机 + +### cancelReason 有什么用? + +cancelReason 用于: +1. **审计追踪**:记录取消原因,便于后续分析 +2. **统计分析**:统计取消原因分布,优化产品 +3. **客服支持**:客服可以查看取消原因 +4. **退款处理**:根据取消原因判断是否退款及退款金额 + +### 取消成功后如何退款? + +取消成功后: +1. 系统会自动触发退款流程 +2. 退款金额根据**退改规则**和**取消时间**决定 +3. 退款通常需要 3-7 个工作日到账 +4. 可以通过 **booking/query** 查询退款状态 + +### 重复取消会怎样? + +如果订单已经是 CANCELLED 状态: +1. 再次调用取消接口会返回失败(code=1) +2. 不会重复处理退款 +3. 建议取消前先通过 **booking/query** 查询订单状态 + +## 业务案例 + +### 客户主动取消订单 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "cancelReason": "Customer request - flight change" +} +``` + +#### 响应(Response) + +```json +{ + "code": 0, + "msg": "Booking cancelled successfully", + "historyId": 789 +} +``` + +### 行程变更取消订单 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "cancelReason": "Itinerary change - customer requested new dates" +} +``` + +#### 响应(Response) + +```json +{ + "code": 0, + "msg": "Booking cancelled successfully", + "historyId": 790 +} +``` + +### 重复订单取消 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "cancelReason": "Duplicate booking - same itinerary booked twice" +} +``` + +#### 响应(Response) + +```json +{ + "code": 0, + "msg": "Booking cancelled successfully", + "historyId": 791 +} +``` + +### 取消不存在的订单 + +#### 请求(Request) + +```json +{ + "orderId": "nonexistent-order-id", + "cancelReason": "Test cancellation" +} +``` + +#### 响应(Response) + +```json +{ + "code": 1, + "msg": "Failed to cancel booking" +} +``` + +## 取消原因建议 + +### 标准取消原因 + +| 场景 | 建议的 cancelReason | 说明 | +| --- | --- | --- | +| 客户主动取消 | "Customer request" | 客户主动要求取消 | +| 行程变更 | "Flight change" | 客户需要更改行程 | +| 重复预订 | "Duplicate booking" | 同一航班重复预订 | +| 支付超时 | "Payment timeout" | 支付时间超过限制 | +| 价格问题 | "Price issue" | 客户认为价格不合理 | +| 个人原因 | "Personal reason" | 客户个人原因 | +| 系统错误 | "System error" | 系统导致的错误 | +| 其他原因 | "Other reason" | 其他未列出的原因 | + +### 详细取消原因 + +建议在标准原因后添加详细说明: + +```json +{ + "orderId": "abbc4b57fb75", + "cancelReason": "Customer request - found better price elsewhere" +} +``` + +## 取消最佳实践 + +### 1. 取消前先查询状态 + +```javascript +// 1. 先查询订单状态 +const order = await bookingQuery({ orderId: "xxx" }); + +// 2. 确认订单可以取消后再取消 +if (order.status === "CONFIRMED" || order.status === "PENDING") { + const result = await bookingCancel({ + orderId: "xxx", + cancelReason: "Customer request" + }); + + if (result.code === 0) { + console.log("取消成功"); + } +} +``` + +### 2. 记录取消原因 + +始终提供清晰、准确的取消原因: +```json +{ + "cancelReason": "Customer request - passenger name change needed" +} +``` + +而不是: +```json +{ + "cancelReason": "Cancel" +} +``` + +### 3. 处理取消失败 + +```javascript +const result = await bookingCancel({ + orderId: "xxx", + cancelReason: "Customer request" +}); + +if (result.code !== 0) { + // 记录错误日志 + console.error("取消失败:", result.msg); + + // 联系客服处理 + await contactSupport({ + orderId: "xxx", + issue: "Cancel failed", + message: result.msg + }); +} +``` + +## 错误处理 + +### 常见错误及处理方式 + +| 错误场景 | code | msg | 处理建议 | +| --- | --- | --- | --- | +| 订单不存在 | 1 | "Failed to cancel booking" | 确认 orderId 是否正确 | +| 订单已取消 | 1 | "Failed to cancel booking" | 先查询订单状态 | +| 订单不可取消 | 1 | "Failed to cancel booking" | 检查退改规则和航班状态 | +| 系统错误 | 1 | "Failed to cancel booking" | 稍后重试或联系技术支持 | diff --git a/docs/04-05_booking-retry.md b/docs/04-05_booking-retry.md new file mode 100644 index 0000000..e934164 --- /dev/null +++ b/docs/04-05_booking-retry.md @@ -0,0 +1,342 @@ + +# booking/retry - 订单重试 + +# 订单重试(booking/retry) API 说明文档 + +## 使用场景 + +> 1. 当订单创建失败时,可以通过本接口重试订单 +> 2. 用于自动或手动恢复失败的订单 +> 3. 支持指定重试次数,避免无限重试 + +## 错误场景(不应该使用本接口的场景) + +> 1. 订单已成功时(status=CONFIRMED)不需要重试 +> 1. 订单已取消时不能重试 +> 1. 重试次数已达上限时继续重试 + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 3000ms(包含重试处理时间) +> - 接口成功率:> 95% + +## 请求说明 + +| **请求地址** | https://${endpoint}/booking/retry | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝。 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### + +### 请求体 + +```json +{ + "orderId": "abbc4b57fb75", + "retryCount": 1 +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值(默认值)** | **说明** | +| --- | --- | --- | --- | --- | +| orderId | String | **是** | abbc4b57fb75 | 客户的订单号,用于重试失败的订单 | +| retryCount | Integer | 否 | 1 | 重试次数,用于追踪当前是第几次重试
**不提供**:系统将自动重试
**提供**:使用指定的重试次数 | + +## 返回参数 + +### 成功响应 + +```json +{ + "id": 12345, + "orderId": "abbc4b57fb75", + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "returnDate": null, + "adults": 1, + "children": 0, + "infants": 0, + "status": "PENDING", + "pnr": null, + "pnrExpiryTime": null, + "agent": "u2web", + "outboundFareFamily": "promo", + "inboundFareFamily": null, + "totalPrice": 150.50, + "currency": "EUR", + "createTime": "20241115100000", + "updateTime": "20241115101500", + "passengers": [ + { + "firstName": "first", + "lastName": "last", + "passengerType": "ADT" + } + ], + "contactInfo": { + "firstName": "san", + "lastName": "zhang", + "email": "san.zhang@gmail.com", + "phone": "0086-18912345678" + } +} +``` + +### 失败响应 + +#### 订单不存在 +```json +null +``` + +### 响应字段说明 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| id | Long | 12345 | 系统内部订单 ID(仅用于调试) | +| orderId | String | "abbc4b57fb75" | 客户的订单号 | +| journeyType | String | "OW" | 行程类型
**OW**:单程
**RT**:往返 | +| origin | String | "MIL" | 出发地 IATA 3 字码 | +| destination | String | "CAG" | 到达地 IATA 3 字码 | +| departureDate | String | "20251118" | 出发日期,格式 YYYYMMDD | +| returnDate | String/null | null | 返程日期,格式 YYYYMMDD(仅往返程有值) | +| adults | Integer | 1 | 成人数量 | +| children | Integer | 0 | 儿童数量 | +| infants | Integer | 0 | 婴儿数量 | +| status | String | "PENDING" | 订单状态
**PENDING**:处理中(重试中)
**CONFIRMED**:已确认
**FAILED**:失败 | +| pnr | String/null | null | 航司 PNR 码(订座记录编号),重试中可能为空 | +| pnrExpiryTime | String/null | null | PNR 有效期,格式 YYYYMMDDHHmmss(UTC 0 时区) | +| agent | String | "u2web" | Agent 代码 | +| outboundFareFamily | String | "promo" | 去程票价族 | +| inboundFareFamily | String/null | null | 回程票价族(仅往返程有值) | +| totalPrice | BigDecimal | 150.50 | 订单总价 | +| currency | String | "EUR" | 币种代码 | +| createTime | String | "20241115100000" | 订单创建时间,格式 YYYYMMDDHHmmss | +| updateTime | String | "20241115101500" | 订单最后更新时间,格式 YYYYMMDDHHmmss | +| passengers | Array | | 乘客信息列表 | +| contactInfo | Object | | 联系人信息 | + +## 业务流程 + +### 订单重试流程 + +``` +1. 客户下单(booking/hold)→ 订单创建失败 + ↓ +2. 系统或客户检测到失败状态 + ↓ +3. 调用重试接口(booking/retry)→ 指定 retryCount + ↓ +4. 系统重新处理订单 + ↓ +5a. 成功:status=CONFIRMED,返回订单详情 + ↓ +5b. 失败:status=FAILED,可以再次重试 +``` + +### 自动重试策略 + +建议的自动重试策略: + +``` +第 1 次重试(retryCount=1):立即重试 +第 2 次重试(retryCount=2):等待 30 秒 +第 3 次重试(retryCount=3):等待 60 秒 +第 4 次重试(retryCount=4):等待 120 秒 +超过 4 次:停止重试,联系客服 +``` + +## 常见问题 + +### 什么时候应该使用重试接口? + +当订单状态为 **FAILED** 时,可以考虑使用重试接口: +1. 航司系统临时不可用 +2. 网络超时 +3. 航司返回临时错误 + +### retryCount 的作用是什么? + +retryCount 用于: +1. **追踪重试次数**:记录当前是第几次重试 +2. **防止无限重试**:可以设置最大重试次数上限 +3. **系统监控**:用于监控重试频率和成功率 + +### 如果订单不存在会返回什么? + +接口会返回 `null`。这种情况通常由以下原因造成: +1. orderId 错误或不存在 +2. 订单属于其他客户(clientCode 不匹配) + +### 重试后订单状态仍然是 FAILED 怎么办? + +如果多次重试后仍然失败: +1. 检查错误信息和日志 +2. 确认订单参数是否正确 +3. 检查航司系统是否正常 +4. 联系技术支持 + +### 可以重试已确认的订单吗? + +不建议。如果订单状态已经是 **CONFIRMED**,重试没有意义。建议先调用 **booking/query** 查询订单状态,确认订单确实失败后再重试。 + +## 业务案例 + +### 第 1 次重试失败的订单 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "retryCount": 1 +} +``` + +#### 响应(Response) + +```json +{ + "id": 12345, + "orderId": "abbc4b57fb75", + "journeyType": "OW", + "origin": "MIL", + "destination": "CAG", + "departureDate": "20251118", + "adults": 1, + "status": "PENDING", + "pnr": null, + "totalPrice": 150.50, + "currency": "EUR", + "createTime": "20241115100000", + "updateTime": "20241115101500" +} +``` + +### 第 2 次重试(等待后) + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75", + "retryCount": 2 +} +``` + +#### 响应(Response) + +```json +{ + "id": 12345, + "orderId": "abbc4b57fb75", + "status": "CONFIRMED", + "pnr": "ABC123", + "pnrExpiryTime": "20251225120000", + "updateTime": "20241115102000" +} +``` + +### 重试不存在的订单 + +#### 请求(Request) + +```json +{ + "orderId": "nonexistent-order-id", + "retryCount": 1 +} +``` + +#### 响应(Response) + +```json +null +``` + +### 不指定 retryCount 的重试 + +#### 请求(Request) + +```json +{ + "orderId": "abbc4b57fb75" +} +``` + +#### 响应(Response) + +```json +{ + "orderId": "abbc4b57fb75", + "status": "PENDING", + "updateTime": "20241115103000" +} +``` + +**说明**:不提供 retryCount 时,系统将自动管理重试次数。 + +## 重试最佳实践 + +### 1. 指数退避策略 + +```json +{ + "retryCount": 1, + "waitTime": 0 +} +``` +→ 失败后立即重试 + +```json +{ + "retryCount": 2, + "waitTime": 30 +} +``` +→ 失败后等待 30 秒 + +```json +{ + "retryCount": 3, + "waitTime": 60 +} +``` +→ 失败后等待 60 秒 + +### 2. 最大重试次数限制 + +建议设置最大重试次数为 **3-5 次**,超过后: +- 停止自动重试 +- 通知客服介入 +- 记录错误日志 + +### 3. 重试前先查询状态 + +```javascript +// 1. 先查询订单状态 +const order = await bookingQuery({ orderId: "xxx" }); + +// 2. 确认订单失败后再重试 +if (order.status === "FAILED") { + await bookingRetry({ + orderId: "xxx", + retryCount: 1 + }); +} +``` diff --git a/docs/07-01_flight-segment-request.md b/docs/07-01_flight-segment-request.md new file mode 100644 index 0000000..2640742 --- /dev/null +++ b/docs/07-01_flight-segment-request.md @@ -0,0 +1,23 @@ + +# FlightSegmentRequest + +# FlightSegmentRequest对象定义 + +Segment 对象用于描述单段航班的核心信息(告知我们定位到具体的行程),定义如下: + +| **字段名** | **数据类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| carrier | String | "VJ" | 承运航司 IATA 二字码(如 VJ = 越南航空),**与 flightNumber 前缀的航司码一致**(示例中 flightNumber="VJ884",carrier="VJ" 符合规则) | +| flightNumber | String | "VJ884" | 航班号,格式为 "航司二字码 + 数字": | +| originAirport | String | "BOM" / "SGN" | 出发机场 IATA 三字码(如 BOM = 孟买贾特拉帕蒂・希瓦吉国际机场,SGN = 胡志明市新山一国际机场) | +| destinationAirport | String | "SGN" / "SYD" | 到达机场 IATA 三字码(如 SYD = 悉尼金斯福德・史密斯机场) | +| stopCities | String | "CDG/MAD" | 经停地城市三字码,多个城市用 `/` 分隔(可选字段,用于支持包含经停点的航班查询) | +| departureDate | String | "20251027" | 起飞日期日期,固定格式 `YYYYMMDD`(20251027表示 2025 年 10 月 27 日) | + +# 备注 + +1. **请求体内的旅行时间是departureDate,不同于响应体的departureTime**:原因是航司会出现出发时间的调整,为了保障成功率,请求只需要告知日期即可。 +1. 本实体兼容FlightSegmentResponse, 你可以把shopping/search中获得实体直接引用。 +1. 如入参为departureTime,我们会抛弃时间,只采用日期。 +1. **stopCities 为可选字段**,仅在航班包含经停点时需要填写。格式为多个城市三字码用 `/` 分隔,例如 "CDG/MAD" 表示经停巴黎和马德里。 + diff --git a/docs/07-02_passenger.md b/docs/07-02_passenger.md new file mode 100644 index 0000000..8bfe2de --- /dev/null +++ b/docs/07-02_passenger.md @@ -0,0 +1,24 @@ + +# Passenger + +# Passenger 实体定义 + +Passenger 实体用于描述航班预订中单个乘客的核心信息,包括身份信息、证件信息、常旅客信息及附加服务选择,是订单创建、值机办理等流程的关键数据依据。 + +| **字段名** | **数据类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| firstName | String | San | 乘客名,需与证件名完全一致,国际航司不可填入中文 | +| lastName | String | Zhang | 乘客姓,需与证件姓完全一致,国际航司不可填入中文 | +| passengerType | String | "ADT"、"CHD"、"INF" | 乘客类型,固定枚举值,用于区分票价规则与服务权益:- "ADT" = 成人(Adult,通常指 12 周岁及以上乘客)- "CHD" = 儿童(Child,通常指 2-11 周岁乘客,需单独购票,部分航司可享折扣)- "INF" = 婴儿(Infant,通常指 2 周岁以下乘客,需与成人乘客绑定预订,票价规则特殊) | +| dateOfBirth | String | "20251017" | 乘客出生日期,固定格式为 **YYYYMMDD**(如 "20180520" 表示 2018 年 5 月 20 日) | +| gender | String | "M"、"F" | 乘客性别,固定枚举值:- "M" = 男(Male)- "F" = 女(Female) | +| documentNumber | String | "E12345678" | 证件号码,需与证件类型(documentType)匹配 | +| documentType | String | "PP"、"ID" | 证件类型,固定枚举值,需与航司支持的证件类型一致:- "PP" = 护照(Passport),国际航班必填- "ID" = 身份证(Identity Card) | +| documentIssuePlace | String | "CN"、"US" | 证件签发国家 / 地区代码,遵循 **ISO 3166-1 alpha-2** 标准(2 位字母编码):- "CN" = 中国内地- "US" = 美国 | +| documentExpirationDate | String | "20281030" | 证件有效期截止日期,固定格式为 **YYYYMMDD**(如 "20301231" 表示 2030 年 12 月 31 日),需满足 “有效期截止日期> 航班返程日期”(单程航班需满足 “> 出发日期”),否则会导致预订失败 | +| nationality | String | "CN"、"US" | 乘客国籍代码,遵循 **ISO 3166-1 alpha-2** 标准,与乘客护照 / 身份证上的国籍信息一致,用于航司核验出入境资格,不可为空 | +| mobile | String | 0086-13200112233 | 乘客手机号码注意格式采用xxxx-xxxxxxxxxxx前面4位为国家码。 | +| frequentFlyerCarrier | String | "CA"、"MU" | 常旅客所属航司代码,遵循 IATA 二字码标准,需与 ffpCardNo 对应的航司一致:- "CA" = 中国国际航空- "MU" = 中国东方航空无会员卡时填写 null,非必选 | +| frequentFlyerNumber | String | "CA1234567890" | 常旅客卡号(Frequent Flyer Program Card Number),即乘客在某航司的会员卡编号(如国航 “凤凰知音” 卡号 "CA1234567890"),无会员卡时填写 null,非必选 | +| ancillaries | Array | Ancillary | 复用shopping/baggage中的行李响应字段,详见 [Ancillary 实体定义](07-04_ancillary.md) | + diff --git a/docs/07-03_contact-info.md b/docs/07-03_contact-info.md new file mode 100644 index 0000000..9175a62 --- /dev/null +++ b/docs/07-03_contact-info.md @@ -0,0 +1,37 @@ + +# ContanctInfo + +# ContactInfo 实体定义 + +Contact 实体用于描述航班订单的联系人信息,是航司与用户沟通的核心渠道,用于发送订单确认通知、电子行程单、航班变动提醒等关键信息。 + +```objective-c + "contactInfo": { + "firstName": "san", + "lastName": "zhang", + "address": "dfdsaqqq", + "phoneCountryCode": "0086", + "phone": "18912345678", + "email": "san.zhang@gmail.com", + "postCode": "310006", + "city": "hangzhou", + "province": "zejiang", + "country": "CN" + +``` + + + +| **字段名** | **数据类型** | 是否必选 | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| firstName | String | 是 | San | 乘客名,需与证件名完全一致,国际航司不可填入中文 | +| lastName | String | 是 | Zhang | 乘客姓,需与证件姓完全一致,国际航司不可填入中文 | +| address | String | 否 | No. 88 Jianguo Road, Chaoyang District, Beijing | 联系人详细地址,用英语(字母)填写。是否必选取决于航司是否需要这个信息。 | +| phoneCountryCode | String | 是 | 0086 | 国家码 | +| phone | String | 是 | 18912345678 | 联系电话号码 | +| postcode | String | 否 | "100022" | 联系人地址对应的邮编(邮政编码)是否必选取决于航司是否需要这个信息。 | +| email | String | 是 | "name@emaildomain.com" | 联系人邮箱地址,需符合标准邮箱格式(如 "用户名 @域名"),用于接收电子行程单(ETicket)、订单确认邮件、航班变动通知,是订单关键凭证的主要发送渠道,不可为空且需确保邮箱可正常接收邮件 | +| city | String | 否 | hangzhou | 联系地址城市 | +| province | String | 否 | zhejiang | 联系地址省/州 | +| country | String | 否 | CN | 联系地址国家码 | + diff --git a/docs/07-04_ancillary.md b/docs/07-04_ancillary.md new file mode 100644 index 0000000..c13513e --- /dev/null +++ b/docs/07-04_ancillary.md @@ -0,0 +1,174 @@ + +# Ancillary + +# Ancillary 实体定义 + +## 重要约定 + +| **字段名** | **数据类型** | **示例值** | **说明** | +|------------------| --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| journeyDirection | String | outbound | **行程方向**:- outbound: 去程- inbound: 回程
**用途**:区分往返行程的去程和回程方向 | +| categoryCode | String | StandardCheckedBaggage | 值举例:
- 托运行李:StandardCheckedBaggage
- 随身行李:CabinBaggageOverheadLocker
- 选座产品:Seat | +| productCode | String | SCI_BAG_20KG | 值举例:
- 托运行李:SCI_BAG_20KG
- 随身行李:CBOL_BAG_15KG
- 选座产品:SEAT_16A | +| price | Number | 21.22 | 预计采购价格,选填 | +| currency | String | USD | 预计采购价对应的货币,选填 | +| baggage | Object | {"piece":0,"weight":20,"size":"","allWeight":true} | 行李附加信息:仅当行李类产品时非空,描述行李的重量、件数等属性 | +| ├─ piece | Integer | 1 | 行李最多件数:1:最多限一件2:最多限两件 | +| ├─ weight | Integer | 20 | 行李总重量限制:单位为公斤(KG),允许的行李总总量。20表示为:20KG | +| ├─ size | String | "" | 行李尺寸限制:格式为 "长*宽*高"(如 "56*36***23cm"),空字符串表示无特殊尺寸限制(需遵循航司通用规则) | +| seat | Object | {"segmentIndex":1,"row":"12","column":"A"} | 座位附加信息:仅当`categoryCode=Seat`(座位类产品)时非空,描述座位的位置信息 | +| ├─ segmentIndex | Integer | 1 | **航段序号**:从 1 开始,在整个行程中连续编号
• 单程(OW):去程从 1 开始
• 往返(RT):去程从 1 开始,**回程 = 去程航段数 + 1**
**示例**:
- 单程直飞:segmentIndex=1
- 单程2段转机:segmentIndex=1, 2
- 往返直飞:去程 segmentIndex=1,回程 segmentIndex=2
- 往返2段转机:去程 segmentIndex=1,2,回程 segmentIndex=3,4 | +| ├─ row | String | "12" | 座位行号:标识座位所在的行(如 "12" 表示第12排) | +| ├─ column | String | "A" | 座位列号:标识座位所在的列(如 "A"、"B"、"C" 等) | + +# 二、行李与选座的行程处理规则 + +## 1. segmentIndex 的计算规则 + +**重要**:seat 对象中的 `segmentIndex` 在整个行程中**连续编号**,不是每个方向重新从 1 开始。 + +**计算规则:** +- **单程(OW)**:去程从 1 开始连续编号 + - 直飞:segmentIndex = 1 + - 2段转机:segmentIndex = 1, 2 + - 3段转机:segmentIndex = 1, 2, 3 + +- **往返(RT)**:去程从 1 开始,回程紧接去程的编号 + - 往返直飞:去程 segmentIndex = 1,回程 segmentIndex = 2 + - 去程2段+回程1段:去程 segmentIndex = 1, 2,回程 segmentIndex = 3 + - 去程2段+回程2段:去程 segmentIndex = 1, 2,回程 segmentIndex = 3, 4 + +**计算公式:** +- 去程第 N 段:segmentIndex = N +- 回程第 M 段:segmentIndex = 去程航段数 + M + +## 2. 往返行程的区分 + +往返行程通过 `journeyDirection` 字段来区分: +- **outbound**:去程 +- **inbound**:回程 + +对于选座产品(`categoryCode=Seat`),每个座位通过 `segmentIndex` 标识其对应的航段。 + +## 3. 行李产品的特殊规则 + +### 转机行程的行李规格要求 + +在单向的转机行程中(即同一个 `journeyDirection` 下有多个航段),行李规格(重量、件数)**必须保持一致**。 + +### 规则说明 + +- 如果去程有3个航段(BOM→SGN→SYD→MEL),整个去程购买的行李规格必须相同 +- 例如:不能第1段购买20KG,第2段购买25KG +- 必须所有航段都购买20KG,或都购买25KG + +### 为什么需要相同规格? + +- 转机时行李会自动转运到下一航段,不需要重新托运 +- 航司系统要求整个行程使用相同的行李规格,以确保行李正常转运 + +### 往返行程的行李 + +- 去程和回程(不同的 `journeyDirection`)可以购买不同规格的行李 +- 例如:去程购买20KG,回程购买25KG是允许的 + +## 4. 选座产品的特殊规则 + +### 每个航段独立选座 + +与行李不同,选座是**每个航段独立进行的**。 + +### 规则说明 + +- 每个航段都需要单独选择座位(通过 seat 对象中的 segmentIndex 区分) +- 即使是转机行程,不同航段可以选择不同的座位 +- 例如:第1段选 12A,第2段选 15F + +### 为什么需要独立选座? + +- 每个航段的航班座位图不同 +- 机型、座位布局可能不同 +- 乘客可能希望在不同航段选择不同位置(如去程靠窗,回程靠过道) + +### 选座对象中的字段 + +- `segmentIndex`:标识选座对应的航段(seat 专用字段) +- `row`:座位行号(如 "12") +- `column`:座位列号(如 "A"、"F") + +**示例 - 转机行程选座:** + +往返直飞(去程1段+回程1段): +```json +// 去程座位 +{ + "categoryCode": "Seat", + "productCode": "SEAT_12A", + "journeyDirection": "outbound", + "seat": { + "segmentIndex": 1, + "row": "12", + "column": "A" + } +} +// 回程座位 +{ + "categoryCode": "Seat", + "productCode": "SEAT_8F", + "journeyDirection": "inbound", + "seat": { + "segmentIndex": 2, + "row": "8", + "column": "F" + } +} +``` + +往返转机(去程2段+回程2段): +```json +// 去程第1段座位 +{ + "categoryCode": "Seat", + "productCode": "SEAT_12A", + "journeyDirection": "outbound", + "seat": { + "segmentIndex": 1, + "row": "12", + "column": "A" + } +} +// 去程第2段座位 +{ + "categoryCode": "Seat", + "productCode": "SEAT_15F", + "journeyDirection": "outbound", + "seat": { + "segmentIndex": 2, + "row": "15", + "column": "F" + } +} +// 回程第1段座位(segmentIndex = 去程航段数 + 1 = 3) +{ + "categoryCode": "Seat", + "productCode": "SEAT_8A", + "journeyDirection": "inbound", + "seat": { + "segmentIndex": 3, + "row": "8", + "column": "A" + } +} +// 回程第2段座位(segmentIndex = 去程航段数 + 2 = 4) +{ + "categoryCode": "Seat", + "productCode": "SEAT_10F", + "journeyDirection": "inbound", + "seat": { + "segmentIndex": 4, + "row": "10", + "column": "F" + } +} +``` + diff --git a/docs/08-01_itinerary.md b/docs/08-01_itinerary.md new file mode 100644 index 0000000..9a26839 --- /dev/null +++ b/docs/08-01_itinerary.md @@ -0,0 +1,76 @@ + +# Itinerary - 行程实体对象 + +Itinerary 实体(行程组合信息,与实际响应字段一致) + +| **参数名称** | **类型** | **说明** | +| --- | --- | --- | +| outboundSegments | Array<[SegmentElement](08-03_segment-element.md)> | 去程航段列表:单程(OW)和往返(RT)场景均返回,按行程顺序排列 | +| inboundSegments | Array<[SegmentElement](08-03_segment-element.md)> | 返程航段列表:仅往返(RT)场景返回,字段含义同 outboundSegments;单程(OW)场景为空数组 | +| fares | Array<[FlightFare](08-02_flight-fare.md)> | 该行程下的可选票价方案列表,每个方案包含去程/回程的票价、税费、座位数、舱位类型及航班政策信息 | + +## 实体轮廓 + +```json +{ + "outboundSegments": [ + { + "index": 1, + "carrier": "VJ", + "flightNumber": "VJ884", + "operatingCarrier": "", + "operatingFlightnumber": "", + "originAirport": "BOM", + "destinationAirport": "SGN", + "departureTime": "202510270040", + "arrivalTime": "202510270705", + "departureTerminal": "", + "arrivalTerminal": "", + "stopCities": "", + "duration": 295, + "aircraftCode": "330", + "codeShare": false + } + ], + "inboundSegments": [ + { + // 同 outboundSegments 结构 + } + ], + "fares": [ + { + "fareBasis": "", + "rtnFareBasis": "", + "fareFamily": "Lion Economy", + "rtnFareFamily": "Lion Promo", + "currency": "USD", + "bookingCode": "Y,Y", + "rtnBookingCode": "Y,Y", + "availableSeats": 7, + "rtnAvailableSeats": 2, + "flightPolicy": { + "airlineCode": "SL", + "fareFamilyType": "Lion Economy", + "description": "", + "freeAncillaryList": [ + { + "categoryCode": "CabinBaggageOverheadLocker", + "segmentIndex": 1, + "paxType": "ADT", + "piece": 1, + "weight": 7, + "size": "" + } + ], + "refundRules": [], // 预留字段,暂未提供 + "changeRules": [] // 预留字段,暂未提供 + }, + "rtnFlightPolicy": { + // 同 flightPolicy 结构 + } + } + ] +} +``` + +> `RuleElement` 结构详见 [FlightPolicy](08-04_flight-policy.md) diff --git a/docs/08-02_flight-fare.md b/docs/08-02_flight-fare.md new file mode 100644 index 0000000..f7c456d --- /dev/null +++ b/docs/08-02_flight-fare.md @@ -0,0 +1,32 @@ + +# FlightFare + +FlightFare 实体用于描述不同乘客类型(成人 / 儿童 / 婴儿)的费用构成,拆分基础票价与税费等明细项,同时包含去程和返程的价格、政策信息,字段与实际响应完全对应。 + +> **说明**:带 `rtn` 前缀的字段为回程字段,仅往返程有值。 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| fareBasis | String | | 去 fareBasis 信息(如有) | +| rtnFareBasis | String | | 回程 fareBasis 信息(如有) | +| fareFamily | String | "Lion Economy" | 去程票价舱/产品类型名称 | +| rtnFareFamily | String | "Lion Promo" | 回程票价舱/产品类型名称 | +| currency | String | "USD" | 币种编码,遵循 ISO 4217 标准(如 USD = 美元,CNY = 人民币) | +| adultFare | BigDecimal | 6115.00 | 去程成人票价(不含税) | +| rtnAdultFare | BigDecimal | 6100.00 | 回程成人票价(不含税) | +| adultTax | BigDecimal | 13714.00 | 去程成人税费 | +| rtnAdultTax | BigDecimal | 8540.00 | 回程成人税费 | +| childFare | BigDecimal | 0.00 | 去程儿童票价(不含税),儿童定义为 2-12 周岁 | +| rtnChildFare | BigDecimal | 0.00 | 回程儿童票价(不含税) | +| childTax | BigDecimal | 0.00 | 去程儿童税费 | +| rtnChildTax | BigDecimal | 0.00 | 回程儿童税费 | +| infantFare | BigDecimal | 0.00 | 去程婴儿票价(不含税),婴儿定义为 0-2 周岁(无独立座位) | +| rtnInfantFare | BigDecimal | 0.00 | 回程婴儿票价(不含税) | +| infantTax | BigDecimal | 0.00 | 去程婴儿税费 | +| rtnInfantTax | BigDecimal | 0.00 | 回程婴儿税费 | +| bookingCode | String | "Y,Y" | 去程舱位代码(如 Y、B、M、W 等),廉航领域不一定存在,可通过系统设置-参数设置-默认舱位编码指定默认值。
**格式说明**:
- 单程直飞:单个代码,如 `"Y"`
- 单程转机:逗号分隔多段,如 `"B,Y"` 表示第一段 B 舱,第二段 Y 舱 | +| rtnBookingCode | String | "Y,Y" | 回程舱位代码(如 Y、B、M、W 等),廉航领域不一定存在。
**格式说明**:同 bookingCode,单段为单个代码,多段用逗号分隔 | +| availableSeats | int | 7 | 去程剩余座位数 | +| rtnAvailableSeats | int | 2 | 回程剩余座位数 | +| flightPolicy | [FlightPolicy](08-04_flight-policy.md) | | 去程航班政策(包含去程免费行李额、退改规则等) | +| rtnFlightPolicy | [FlightPolicy](08-04_flight-policy.md) | | 回程航班政策(包含回程免费行李额、退改规则等) | diff --git a/docs/08-03_segment-element.md b/docs/08-03_segment-element.md new file mode 100644 index 0000000..c6b67e4 --- /dev/null +++ b/docs/08-03_segment-element.md @@ -0,0 +1,30 @@ + +# FlightSegmentResponse + +# FlightSegment 对象定义 + +Segment 对象用于描述单段航班的核心信息(如在搜索结果中的去程航段 `outboundSegments`、返程航段 `inboundSegments`),具体定义如下: + +| **字段名** | **数据类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| index | Integer | 1 / 2 | 航段序号,按行程顺序递增(一般用于转机行程,第一段去程为 1,第二段去程为 2),非空且唯一 | +| carrier | String | "VJ" | 承运航司 IATA 二字码(如 VJ = 越南航空),**与 flightNumber 前缀的航司码一致**(示例中 flightNumber="VJ884",carrier="VJ" 符合规则) | +| flightNumber | String | "VJ884" | 航班号,格式为 “航司二字码 + 数字”: | +| operatingFlightnumber | String | "" | 实际承运航班号:- 代码共享航班时,填写实际承运航司的航班号- 非代码共享航班时,空字符串或与 flightNumber 一致(示例中为非代码共享,故为空) | +| operatingCarrier | String | "" | 实际承运航司 IATA 二字码:- 代码共享航班(codeShare=true)时,需填写实际承运航司(如 carrier="CA",operatingCarrier="MU")- 非代码共享航班(codeShare=false)时,空字符串或与 carrier 一致(示例中为非代码共享,故为空) | +| originAirport | String | "BOM" / "SGN" | 出发机场 IATA 三字码(如 BOM = 孟买贾特拉帕蒂・希瓦吉国际机场,SGN = 胡志明市新山一国际机场) | +| departureTime | String | "202510270040" | 起飞日期时间,固定格式 `YYYYMMDDHHMM`(202510270040 表示 2025 年 10 月 27 日 00:40) | +| destinationAirport | String | "SGN" / "SYD" | 到达机场 IATA 三字码(如 SYD = 悉尼金斯福德・史密斯机场) | +| arrivalTime | String | "202510270705" | 到达日期时间,格式同 depTime(202510280805 表示 2025 年 10 月 28 日 08:05) | +| stopCities | String | "" | 经停地,空字符串表示直飞 | +| duration | int | 295 / 515 | 飞行时长,单位为分钟(295 分钟 = 4 小时 55 分钟,515 分钟 = 8 小时 35 分钟) | +| codeShare | boolean | false | 代码共享标识:- true = 代码共享航班(由其他航司实际承运)- false = 非代码共享航班(由 carrier 航司直接承运) | +| cabin | String | "" | 舱位等级代码(如 "Y"= 经济舱、"C"= 公务舱),空字符串表示未明确具体舱位代码 | +| aircraftCode | String | "330" | 机型编码(如 "330"= 空客 A330,"738"= 波音 737-800),用于标识执飞客机型号 | +| departureTerminal | String | "T1" | 起飞机场航站楼(如 "T2"=2 号航站楼),空字符串表示未明确航站楼信息(需以机场实时通知为准) | +| arrivalTerminal | String | "T1" | 到达机场航站楼,规则同 departureTerminal | + +# 特殊业务规则备注 + +1. **航班号前 0 规则**:响应体内的航班号会按照航司接口中获取数据原样返回,请注意处理是否带0的情况。 + diff --git a/docs/08-04_flight-policy.md b/docs/08-04_flight-policy.md new file mode 100644 index 0000000..1b354ae --- /dev/null +++ b/docs/08-04_flight-policy.md @@ -0,0 +1,43 @@ + +# FlightPolicy + +# FlightPolicy 实体定义(基于实际响应) + +该实体用于描述航班的附加产品信息(如免费行李额、退改规则等),字段与最新响应数据完全对齐。 + +> **注意**:去程和回程的免费行李额分别存储在 `FlightFare.flightPolicy` 和 `FlightFare.rtnFlightPolicy` 中,因此 `freeBaggageList` 不再包含 `journeyDirection` 字段来区分方向。 + +| **字段名** | **数据类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| airlineCode | String | "SL" | 航司代码 | +| fareFamilyType | String | "Lion Economy" | 舱位/子舱位类型名称 | +| description | String | | 政策描述 | +| freeAncillaryList | List\ | | 免费行李额列表(详见下方说明) | +| refundRules | List\ | | 退票规则列表(预留字段,暂未提供) | +| changeRules | List\ | | 改签规则列表(预留字段,暂未提供) | + +## freeAncillaryList(FreeAncillary) + +免费行李额元素,描述每个航段、每种乘客类型的免费行李配额。 + +| **字段名** | **数据类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| categoryCode | String | "StandardCheckedBaggage" | 辅营类型代码:`StandardCheckedBaggage` = 托运行李,`CabinBaggageUnderSeat` = 客舱行李(座椅下),`CabinBaggageOverheadLocker` = 客舱行李(行李架) | +| segmentIndex | Integer | 1 | 航段序号,从 1 开始 | +| paxType | String | "ADT" | 乘客类型:`ADT` = 成人,`CHD` = 儿童,`INF` = 婴儿 | +| piece | Integer | 1 | 行李件数(PC) | +| weight | Integer | 20 | 行李重量(KG) | +| size | String | | 行李尺寸 | + +**示例:** + +```json +{ + "freeAncillaryList": [ + { "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 7 }, + { "categoryCode": "StandardCheckedBaggage", "segmentIndex": 1, "paxType": "ADT", "piece": 1, "weight": 20 }, + { "categoryCode": "CabinBaggageOverheadLocker", "segmentIndex": 2, "paxType": "ADT", "piece": 1, "weight": 7 }, + { "categoryCode": "StandardCheckedBaggage", "segmentIndex": 2, "paxType": "ADT", "piece": 1, "weight": 20 } + ] +} +``` \ No newline at end of file diff --git a/docs/08-05_ancillary-product.md b/docs/08-05_ancillary-product.md new file mode 100644 index 0000000..7ad66ac --- /dev/null +++ b/docs/08-05_ancillary-product.md @@ -0,0 +1,121 @@ + +# AncillaryProduct(辅营产品) + +## 数据结构总览 + +辅营产品采用三级嵌套结构: + +``` +AncillaryGroup(分组层) + └─ AncillaryCategory(类别层) + └─ BaggageOption(选项层) +``` + +--- + +## AncillaryGroup(分组层) + +按 **航程方向 + 运价家族** 分组的最外层容器。 + +| 字段名 | 类型 | 示例值 | 说明 | +|--------|------|--------|------| +| currency | String | "CAD" | 售价币种(ISO 4217) | +| fareFamilyType | String | "BASIC" | 运价家族类型,如 `BASIC`、`SMART`、`PROMO` | +| journeyDirection | String | "outbound" | 航程方向:`outbound`(去程)/ `inbound`(回程) | +| categoryList | Array\ | - | 辅营类别列表 | + +--- + +## AncillaryCategory(类别层) + +按行李类别分组,携带定价模式和购买限制。 + +| 字段名 | 类型 | 示例值 | 说明 | +|--------|------|--------|------| +| categoryCode | String | "StandardCheckedBaggage" | 类别代码,见下方 categoryCode 说明 | +| maxPieces | String | "5" | 该类别最多可购买件数 | +| pricingMode | String | "STEP" | 定价模式:`FLAT` / `STEP` / `TIERED`,见下方说明 | +| categoryDetail | Array\ | - | 该类别下的选项列表 | + +### categoryCode 说明 + +| categoryCode | 说明 | +|--------------|------| +| CabinBaggageOverheadLocker | 手提行李(头顶行李架) | +| CabinBaggageUnderSeat | 手提行李(座椅下) | +| StandardCheckedBaggage | 托运行李 | + +### pricingMode 说明 + +| pricingMode | 说明 | +|-------------|------| +| FLAT | 固定单价,每件为独立产品,pieceNo 固定为 1 | +| STEP | 逐件定价,pieceNo 表示第 N 件,同 pieceNo 可有多个重量选项 | +| TIERED | 按总重量档位选择,不计件,pieceNo 固定为 1 | + +--- + +## BaggageOption(选项层) + +具体的行李规格和价格。 + +| 字段名 | 类型 | 示例值 | 说明 | +|--------|------|--------|------| +| pieceNo | Integer | 1 | 第 N 件。STEP 模式下为序号(1, 2, 3...),FLAT/TIERED 模式下为 1 | +| weight | BigDecimal | 23 | 行李额重量(KG) | +| size | String | "55x40x23cm" | 尺寸限制,空字符串表示无特殊限制 | +| price | BigDecimal | 64.00 | 该选项/该件价格 | +| maxPieces | Integer | 1 | 该选项可购买数量 | + +--- + +## 响应示例 + +### STEP 模式示例 + +```json +{ + "currency": "CAD", + "fareFamilyType": "BASIC", + "journeyDirection": "outbound", + "categoryList": [ + { + "categoryCode": "StandardCheckedBaggage", + "maxPieces": "5", + "pricingMode": "STEP", + "categoryDetail": [ + { "pieceNo": 1, "weight": 10, "size": "", "price": 39.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 }, + { "pieceNo": 2, "weight": 23, "size": "", "price": 64.00, "maxPieces": 1 }, + { "pieceNo": 3, "weight": 23, "size": "", "price": 99.00, "maxPieces": 1 } + ] + } + ] +} +``` + +> **STEP 模式解读**:第 1 件可选 10kg($39)或 23kg($64),第 2 件 23kg($64),第 3 件 23kg($99)。 + +### TIERED 模式示例 + +```json +{ + "currency": "CNY", + "fareFamilyType": "ECONOMY", + "journeyDirection": "outbound", + "categoryList": [ + { + "categoryCode": "StandardCheckedBaggage", + "maxPieces": "1", + "pricingMode": "TIERED", + "categoryDetail": [ + { "pieceNo": 1, "weight": 20, "size": "", "price": 300.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 25, "size": "", "price": 500.00, "maxPieces": 1 }, + { "pieceNo": 1, "weight": 30, "size": "", "price": 800.00, "maxPieces": 1 } + ] + } + ] +} +``` + +> **TIERED 模式解读**:按重量档位互斥选择,20kg(¥300)、25kg(¥500)或 30kg(¥800),只能选一个。 diff --git a/docs/08-06_fee-items.md b/docs/08-06_fee-items.md new file mode 100644 index 0000000..479ab77 --- /dev/null +++ b/docs/08-06_fee-items.md @@ -0,0 +1,230 @@ + +# feeItems 实体定义 + +该实体用于拆分航班预订费用,字段与最新响应数据完全对齐,同时补充业务含义说明: + +### **字段结构** + +```plaintext +feeItems +├── amount (总金额) +├── currency (货币代码) +├── fare (票价) +│ ├── amount (票价总金额) +│ ├── name (费用名称) +│ ├── remark (费用备注) +│ └── passengers (按乘机人拆分) +│ └── 每个乘客的票价明细 +├── tax (税费) +│ ├── amount (税费总金额) +│ ├── name (费用名称) +│ ├── remark (费用备注) +│ ├── passengers (按乘机人拆分) +│ │ └── 每个乘客的税费明细 +│ │ └── fees (税费细项) +│ │ └── 每个税费的明细 +│ └── fees (税费总细项) +│ └── 每个税费的明细 +├── cabinBaggage (手提行李) +│ ├── amount (手提行李总金额) +│ ├── name (费用名称) +│ ├── remark (费用备注) +│ └── passengers (按乘机人拆分) +│ └── 每个乘客的手提行李费用明细 +├── checkedBaggage (托运行李) +│ ├── amount (托运行李总金额) +│ ├── name (费用名称) +│ ├── remark (费用备注) +│ └── passengers (按乘机人拆分) +│ └── 每个乘客的托运行李费用明细 +├── seat (座位) +│ ├── amount (座位总金额) +│ ├── name (费用名称) +│ ├── remark (费用备注) +│ └── passengers (按乘机人拆分) +│ └── 每个乘客的座位费用明细 +├── payment (支付手续费) +│ ├── amount (支付手续费总金额) +│ ├── name (费用名称) +│ └── remark (费用备注) +└── other (其他额外费用) + ├── amount (其他额外费用总金额) + ├── name (费用名称) + +``` + +| 字段名 | 数据类型 | 示例值 | 说明 | +| --- | --- | --- | --- | +| totalPrice | Decimal | "850.00" | 总金额:航班预订的全部费用合计,单位与 currency 一致 | +| currency | String | "CNY" | 货币代码:标识费用的货币类型(如 CNY 为人民币、USD 为美元) | +| ticketPrice | Object | {"amount":"200.00","name":"票价",...} | 票价对象:包含票价总金额、费用名称、备注及可选的乘机人拆分明细(PassengerFee 数组) | +| tax | Object | {"amount":"250.00","name":"税",...} | 税费对象:包含税费总金额、费用名称、备注,可选乘机人拆分明细(PassengerFee 数组)和税费细项(FeeDetail 数组) | +| cabinBaggage | Object | {"amount":"50.00","name":"手提行李",...} | 手提行李费用对象:包含总金额、费用名称、备注及可选的乘机人拆分明细(PassengerFee 数组),可选字段 | +| checkedBaggage | Object | {"amount":"150.00","name":"托运行李",...} | 托运行李费用对象:包含总金额、费用名称、备注及可选的乘机人拆分明细(PassengerFee 数组),可选字段 | +| seat | Object | {"amount":"100.00","name":"座位",...} | 座位费用对象:包含总金额、费用名称、备注及可选的乘机人拆分明细(PassengerFee 数组),可选字段 | +| payment | Object | {"amount":"50.00","name":"支付手续费",...} | 支付手续费对象:包含总金额、费用名称、备注,无乘机人拆分,可选字段 | +| other | Object | {"amount":"0.00","name":"其他费用",...} | 其他额外费用对象:包含总金额、费用名称、备注,无乘机人拆分,可选字段 | + +## 嵌套实体定义 + +### PassengerFee(乘机人费用明细) + +| 字段名 | 数据类型 | 示例值 | 说明 | +| --- | --- | --- | --- | +| amount | Decimal | "100.00" | 乘机人对应费用金额:单个乘客在该费用类型下的应付金额 | +| name | String | "willow/yang" | 乘机人姓名:对应乘客的姓名标识 | +| remark | String | "成人票价" | 备注:说明该乘客费用的相关信息(如乘客类型、费用用途) | +| fees | Array[FeeDetail] | [{"amount":"100.00","name":"机场税",...}] | 税费细项数组:仅税费对象的乘机人明细中可选,包含该乘客的具体税费拆分,可选字段 | + +### FeeDetail(费用细项明细) + +| 字段名 | 数据类型 | 示例值 | 说明 | +| --- | --- | --- | --- | +| amount | Decimal | "170.00" | 细项金额:单个费用细项的总金额或乘机人对应金额 | +| name | String | "机场税" | 细项名称:描述费用细项的类型(如机场税、燃油附加费) | +| remark | String | "机场税" | 备注:补充说明费用细项的相关信息,与名称语义一致 | +| code | String | - | 费用细项编码:唯一标识该费用细项类型,可选字段 | + +### **响应示例** + +```json +{ + "success": true, + "feeItems": { + "totalPrice": "850.00", + "currency": "CNY", + "ticketPrice": { + "amount": "200.00", + "name": "票价", + "remark": "票价", + "passengers": [ + { + "amount": "100.00", + "name": "willow/yang", + "remark": "成人票价" + }, + { + "amount": "100.00", + "name": "jasen/yang", + "remark": "成人票价" + } + ] + }, + "tax": { + "amount": "250.00", + "name": "税", + "remark": "税费", + "passengers": [ + { + "amount": "150.00", + "name": "willow/yang", + "remark": "成人税价", + "fees": [ + { + "amount": "100.00", + "name": "机场税", + "remark": "机场税" + }, + { + "amount": "50.00", + "name": "燃油附加费", + "remark": "燃油附加费" + } + ] + }, + { + "amount": "100.00", + "name": "jasen/yang", + "remark": "成人税价", + "fees": [ + { + "amount": "70.00", + "name": "机场税", + "remark": "机场税" + }, + { + "amount": "30.00", + "name": "燃油附加费", + "remark": "燃油附加费" + } + ] + } + ], + "fees": [ + { + "amount": "170.00", + "name": "机场税", + "remark": "机场税" + }, + { + "amount": "80.00", + "name": "燃油附加费", + "remark": "燃油附加费" + } + ] + }, + "cabinBaggage": { + "amount": "50.00", + "name": "手提行李", + "remark": "手提行李费", + "passengers": [ + { + "amount": "25.00", + "name": "willow/yang", + "remark": "手提行李费" + }, + { + "amount": "25.00", + "name": "jasen/yang", + "remark": "手提行李费" + } + ] + }, + "checkedBaggage": { + "amount": "150.00", + "name": "托运行李", + "remark": "托运行李费", + "passengers": [ + { + "amount": "75.00", + "name": "willow/yang", + "remark": "托运行李费" + }, + { + "amount": "75.00", + "name": "jasen/yang", + "remark": "托运行李费" + } + ] + }, + "seat": { + "amount": "100.00", + "name": "座位", + "remark": "选座费", + "passengers": [ + { + "amount": "50.00", + "name": "willow/yang", + "remark": "选座费" + }, + { + "amount": "50.00", + "name": "jasen/yang", + "remark": "选座费" + } + ] + }, + "payment": { + "amount": "50.00", + "name": "支付手续费", + "remark": "支付手续费" + }, + "other": { + "amount": "0.00", + "name": "其他费用", + "remark": "其他费用" + } + } +} +``` + diff --git a/docs/09-01_device-list.md b/docs/09-01_device-list.md new file mode 100644 index 0000000..e10f707 --- /dev/null +++ b/docs/09-01_device-list.md @@ -0,0 +1,141 @@ + +# device/list - 设备列表 + +## 概述 + +设备列表API用于查询当前客户的所有设备信息,包括设备状态、绑定的VCC卡、任务状态等。 + +### 用户使用场景 + +本接口用于: +- 查看所有可用设备及其在线状态 +- 检查设备的VCC卡绑定情况 +- 查看设备的任务处理状态 + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/device/list` | +| **请求方法** | GET | +| **注意事项** | 无需请求体,从Header中获取clientCode | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +## 返回参数 + +成功时返回设备信息列表: + +```json +[ + { + "deviceId": "device-001", + "status": "ONLINE", + "vccCardNumber": "424242******4242", + "bindingVccCardNumber": "555555******5555", + "lastHeartbeatTime": 1704140800000, + "taskStatus": "PROCESSING", + "processingTask": { + "taskId": "task-123", + "taskType": "BIND_VCC", + "status": "PROCESSING", + "createTime": "2025-01-15T10:30:00Z", + "updateTime": "2025-01-15T10:35:00Z", + "deviceId": "device-001" + }, + "pendingTasks": [] + }, + { + "deviceId": "device-002", + "status": "OFFLINE", + "vccCardNumber": "378222******2222", + "bindingVccCardNumber": null, + "lastHeartbeatTime": 1704130000000, + "taskStatus": "IDLE", + "processingTask": null, + "pendingTasks": [ + { + "taskId": "task-124", + "taskType": "UNBIND_VCC", + "status": "PENDING", + "createTime": "2025-01-15T11:00:00Z", + "updateTime": "2025-01-15T11:00:00Z", + "deviceId": "device-002" + } + ] + } +] +``` + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| deviceId | String | 设备编号 | +| status | String | 设备状态:ONLINE=在线,OFFLINE=离线 | +| vccCardNumber | String | 当前绑定的VCC卡号(脱敏显示) | +| bindingVccCardNumber | String | 正在绑定的VCC卡号(脱敏显示,绑定任务进行中时存在) | +| lastHeartbeatTime | Long | 最后心跳时间(时间戳毫秒) | +| taskStatus | String | 任务状态:IDLE=空闲,PROCESSING=处理中,PENDING=有等待任务 | +| processingTask | Object | 正在执行的任务信息 | +| pendingTasks | Array | 等待队列中的任务列表 | + +### TaskInfo 任务信息 + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| taskId | String | 任务ID | +| taskType | String | 任务类型:BIND_VCC=绑定VCC卡,UNBIND_VCC=解绑VCC卡,PAYMENT=支付 | +| status | String | 任务状态:PENDING=等待中,PROCESSING=处理中,COMPLETED=已完成,FAILED=失败 | +| createTime | String | 创建时间(ISO 8601格式) | +| updateTime | String | 更新时间(ISO 8601格式) | +| deviceId | String | 设备ID | + +## 业务案例 + +### 查询所有设备状态 + +### 请求 + +```bash +curl -X GET "https://${endpoint}/device/list" \ + -H "client-key: your_key" \ + -H "client-secret: your_secret" +``` + +### 响应 + +```json +[ + { + "deviceId": "device-001", + "status": "ONLINE", + "vccCardNumber": "424242******4242", + "bindingVccCardNumber": null, + "lastHeartbeatTime": 1704140800000, + "taskStatus": "IDLE", + "processingTask": null, + "pendingTasks": [] + } +] +``` + +## 常见问题 + +### 设备状态ONLINE和OFFLINE的区别是什么? + +- **ONLINE**:设备在线,可以接收和处理任务 +- **OFFLINE**:设备离线,无法处理任务 + +### taskStatus有几种状态? + +- **IDLE**:设备空闲,没有正在处理的任务 +- **PROCESSING**:设备正在执行任务 +- **PENDING**:设备有待处理的任务队列 + +### VCC卡号为什么要脱敏显示? + +为了保护支付安全,VCC卡号只显示前6位和后4位,中间用*替换。 diff --git a/docs/09-02_device-bind.md b/docs/09-02_device-bind.md new file mode 100644 index 0000000..4b97346 --- /dev/null +++ b/docs/09-02_device-bind.md @@ -0,0 +1,143 @@ + +# device/bind - VCC卡绑定 + +## 概述 + +VCC卡绑定API用于向设备提交VCC卡绑定请求。绑定过程是异步的,提交后会返回任务ID,可以通过任务查询接口查询绑定进度。 + +### 用户使用场景 + +本接口用于: +- 为在线设备绑定新的VCC卡 +- 替换设备当前绑定的VCC卡 + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/device/bind` | +| **请求方法** | POST | +| **注意事项** | 绑定过程是异步的,需要轮询任务状态 | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### 请求体参数 + +```json +{ + "deviceId": "device-001", + "creditCard": { + "cardNumber": "4242424242424242", + "cvv": "123", + "expiryDate": "12/25" + } +} +``` + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| deviceId | String | 是 | "device-001" | 设备编号 | +| creditCard | Object | 是 | - | 信用卡信息 | +| creditCard.cardNumber | String | 是 | "4242424242424242" | 卡号 | +| creditCard.cvv | String | 是 | "123" | CVV码 | +| creditCard.expiryDate | String | 是 | "12/25" | 有效期,格式:MM/YY | + +## 返回参数 + +成功时返回任务信息: + +```json +{ + "success": true, + "message": "VCC卡绑定任务已提交", + "taskId": "task-bind-123456" +} +``` + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| success | Boolean | 是否成功提交 | +| message | String | 结果消息 | +| taskId | String | 任务ID,用于查询任务状态 | + +失败时返回: + +```json +{ + "success": false, + "message": "设备离线,无法执行绑定任务", + "taskId": null +} +``` + +## 业务案例 + +### 绑定VCC卡 + +### 请求 + +```bash +curl -X POST "https://${endpoint}/device/bind" \ + -H "Content-Type: application/json" \ + -H "client-key: your_key" \ + -H "client-secret: your_secret" \ + -d '{ + "deviceId": "device-001", + "creditCard": { + "cardNumber": "4242424242424242", + "cvv": "123", + "expiryDate": "12/25" + } + }' +``` + +### 响应 + +```json +{ + "success": true, + "message": "VCC卡绑定任务已提交", + "taskId": "task-bind-123456" +} +``` + +### 查询绑定任务进度 + +使用返回的taskId查询任务状态: + +```bash +curl -X GET "https://${endpoint}/device/task/task-bind-123456" \ + -H "client-key: your_key" \ + -H "client-secret: your_secret" +``` + +## 常见问题 + +### 绑定是同步还是异步的? + +绑定是**异步操作**。提交绑定请求后会立即返回任务ID,需要通过任务查询接口轮询任务状态。 + +### 绑定失败的可能原因? + +- 设备离线(status=OFFLINE) +- 设备正在执行其他任务(taskStatus=PROCESSING) +- 信用卡信息格式错误 +- 信用卡已过期 + +### 如何判断绑定是否完成? + +通过任务查询接口检查任务状态: +- **PENDING**:任务在队列中等待 +- **PROCESSING**:正在绑定中 +- **COMPLETED**:绑定完成 +- **FAILED**:绑定失败 + +### 绑定完成后如何验证? + +再次调用设备列表接口,查看设备的 `vccCardNumber` 字段是否已更新为新卡号。 diff --git a/docs/09-03_device-unbind.md b/docs/09-03_device-unbind.md new file mode 100644 index 0000000..775c587 --- /dev/null +++ b/docs/09-03_device-unbind.md @@ -0,0 +1,128 @@ + +# device/unbind - VCC卡解绑 + +## 概述 + +VCC卡解绑API用于向设备提交VCC卡解绑请求。解绑过程是异步的,提交后会返回任务ID,可以通过任务查询接口查询解绑进度。 + +### 用户使用场景 + +本接口用于: +- 移除设备当前绑定的VCC卡 +- 更换VCC卡前先解绑旧卡 + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/device/unbind` | +| **请求方法** | POST | +| **注意事项** | 解绑过程是异步的,需要轮询任务状态 | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### 请求体参数 + +```json +{ + "deviceId": "device-001" +} +``` + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| deviceId | String | 是 | "device-001" | 设备编号 | + +## 返回参数 + +成功时返回任务信息: + +```json +{ + "success": true, + "message": "VCC卡解绑任务已提交", + "taskId": "task-unbind-123456" +} +``` + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| success | Boolean | 是否成功提交 | +| message | String | 结果消息 | +| taskId | String | 任务ID,用于查询任务状态 | + +失败时返回: + +```json +{ + "success": false, + "message": "设备没有绑定的VCC卡", + "taskId": null +} +``` + +## 业务案例 + +### 解绑VCC卡 + +### 请求 + +```bash +curl -X POST "https://${endpoint}/device/unbind" \ + -H "Content-Type: application/json" \ + -H "client-key: your_key" \ + -H "client-secret: your_secret" \ + -d '{ + "deviceId": "device-001" + }' +``` + +### 响应 + +```json +{ + "success": true, + "message": "VCC卡解绑任务已提交", + "taskId": "task-unbind-123456" +} +``` + +### 查询解绑任务进度 + +使用返回的taskId查询任务状态: + +```bash +curl -X GET "https://${endpoint}/device/task/task-unbind-123456" \ + -H "client-key: your_key" \ + -H "client-secret: your_secret" +``` + +## 常见问题 + +### 解绑是同步还是异步的? + +解绑是**异步操作**。提交解绑请求后会立即返回任务ID,需要通过任务查询接口轮询任务状态。 + +### 解绑失败的可能原因? + +- 设备离线(status=OFFLINE) +- 设备正在执行其他任务(taskStatus=PROCESSING) +- 设备没有绑定的VCC卡(vccCardNumber为空) + +### 如何判断解绑是否完成? + +通过任务查询接口检查任务状态: +- **PENDING**:任务在队列中等待 +- **PROCESSING**:正在解绑中 +- **COMPLETED**:解绑完成 +- **FAILED**:解绑失败 + +### 解绑完成后如何验证? + +再次调用设备列表接口,查看设备的 `vccCardNumber` 字段是否已为空。 diff --git a/docs/09-04_device-task.md b/docs/09-04_device-task.md new file mode 100644 index 0000000..9b3e821 --- /dev/null +++ b/docs/09-04_device-task.md @@ -0,0 +1,165 @@ + +# device/task/{taskId} - 查询任务 + +## 概述 + +任务查询API用于查询设备任务的执行状态和详情。通过绑定/解绑接口获取的任务ID,可以使用本接口查询任务进度。 + +### 用户使用场景 + +本接口用于: +- 查询VCC卡绑定任务的状态 +- 查询VCC卡解绑任务的状态 +- 查询支付任务的状态 +- 获取任务的详细信息和设备状态 + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/device/task/{taskId}` | +| **请求方法** | GET | +| **注意事项** | taskId从绑定/解绑接口返回中获取 | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### URL 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| taskId | String | 是 | "task-bind-123456" | 任务ID,从绑定/解绑接口返回 | + +## 返回参数 + +成功时返回任务详情: + +```json +{ + "deviceId": "device-001", + "deviceStatus": "ONLINE", + "task": { + "taskId": "task-bind-123456", + "taskType": "BIND_VCC", + "status": "PROCESSING", + "createTime": "2025-01-15T10:30:00Z", + "updateTime": "2025-01-15T10:35:00Z", + "deviceId": "device-001", + "orderId": null, + "creditCard": { + "cardNumber": "424242******4242", + "cvv": "***", + "expiryDate": "12/25" + }, + "orderDetail": null + } +} +``` + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| deviceId | String | 执行任务的设备ID | +| deviceStatus | String | 设备状态:ONLINE=在线,OFFLINE=离线 | +| task | Object | 任务详细信息 | + +### Task 任务信息 + +| 参数名称 | 类型 | 说明 | +|----------|------|------| +| taskId | String | 任务ID | +| taskType | String | 任务类型:BIND_VCC=绑定VCC卡,UNBIND_VCC=解绑VCC卡,PAYMENT=支付 | +| status | String | 任务状态:PENDING=等待中,PROCESSING=处理中,COMPLETED=已完成,FAILED=失败 | +| createTime | String | 创建时间(ISO 8601格式) | +| updateTime | String | 更新时间(ISO 8601格式) | +| deviceId | String | 设备ID | +| orderId | String | 订单ID(仅PAYMENT类型任务) | +| creditCard | Object | 信用卡信息(仅BIND_VCC类型任务,脱敏显示) | +| orderDetail | String | 订单详情(仅PAYMENT类型任务) | + +## 业务案例 + +### 查询绑定任务状态 + +### 请求 + +```bash +curl -X GET "https://${endpoint}/device/task/task-bind-123456" \ + -H "client-key: your_key" \ + -H "client-secret: your_secret" +``` + +### 响应 - 任务处理中 + +```json +{ + "deviceId": "device-001", + "deviceStatus": "ONLINE", + "task": { + "taskId": "task-bind-123456", + "taskType": "BIND_VCC", + "status": "PROCESSING", + "createTime": "2025-01-15T10:30:00Z", + "updateTime": "2025-01-15T10:35:00Z", + "deviceId": "device-001", + "creditCard": { + "cardNumber": "424242******4242", + "cvv": "***", + "expiryDate": "12/25" + } + } +} +``` + +### 响应 - 任务完成 + +```json +{ + "deviceId": "device-001", + "deviceStatus": "ONLINE", + "task": { + "taskId": "task-bind-123456", + "taskType": "BIND_VCC", + "status": "COMPLETED", + "createTime": "2025-01-15T10:30:00Z", + "updateTime": "2025-01-15T10:36:00Z", + "deviceId": "device-001" + } +} +``` + +### 响应 - 任务不存在 + +```json +{ + "deviceId": null, + "deviceStatus": null, + "task": null +} +``` + +## 常见问题 + +### 任务状态有哪几种? + +- **PENDING**:任务在队列中等待执行 +- **PROCESSING**:任务正在执行中 +- **COMPLETED**:任务执行完成 +- **FAILED**:任务执行失败 + +### 任务查询不到是什么原因? + +- 任务ID错误 +- 任务已完成且已被清理(根据系统配置,任务可能会在完成后一段时间被清理) +- 任务ID来自其他客户 + +### 为什么要轮询查询任务状态? + +因为绑定/解绑操作是异步的,设备需要时间完成操作。建议间隔3-5秒轮询一次,直到任务状态变为 COMPLETED 或 FAILED。 + +### 信用卡信息为什么要脱敏? + +为了保护支付安全,任务详情中的信用卡信息只显示脱敏后的卡号和掩码CVV。 diff --git a/docs/10_error-codes.md b/docs/10_error-codes.md new file mode 100644 index 0000000..9b62819 --- /dev/null +++ b/docs/10_error-codes.md @@ -0,0 +1,62 @@ + +# 错误码说明 + +## 完整错误码列表 + +| 错误码 | 枚举名称 | 消息 | 可重试 | 说明 | +|--------|----------|------|--------|------| +| 100 | - | 鉴权失败 | false | 请检查 client-key, client-secret 信息是否正确 | +| 102 | - | 超过限流 | true | 请求频率超过限制,请稍后重试 | +| 103 | - | Proxy error | true | 代理服务器失败,请检查代理服务器 URL 是否正确,或者代理 IP 用量是否足够。如果使用平台代理且刚刚修改过命名,请等待 5 分钟平台缓存同步再试 | +| 104 | - | Agent not exist or agent is disabled | false | 检查 agent code 是否正确,agent 是否被停用。如果刚刚创建运行器(agent),请等待 5 分钟平台缓存同步再试 | +| 40013 | AGENT_MISSED_CREDENTIAL | Agent credential not configured | false | Agent 凭证未配置,请检查 Agent 的认证信息是否正确配置 | +| 42010 | PRICE_CHANGED | Fare price has changed, please check latest price from feeitem | false | 价格变动,请检查响应中的 feeItems 获取最新价格 | +| 41007 | FLIGHT_NOT_AVAILABLE | Flight not available | true | 航班不存在/已售罄 | +| 42011 | INSUFFICIENT_SEATS | Insufficient seats | true | 剩余座位不足 | +| 42000 | ORDER_ID_REQUIRED | Order ID is required | false | 缺少订单 ID | +| 42001 | ORDER_ID_DUPLICATE | Order ID already exists | false | 订单 ID 重复 | +| 42002 | PASSENGER_MISMATCH | Passenger information mismatch | false | 乘客信息不匹配 | +| 42003 | BOOKING_FAILED | Failed to create booking | true | 预订失败 | +| 42004 | HOLD_EXECUTION_FAILED | Hold execution failed | true | Hold 执行失败 | +| 42005 | BOOKING_NOT_FOUND | Booking not found | false | 预订未找到 | +| 42006 | DUPLICATE_PASSENGER_NAME | Duplicate passenger name in booking | false | 乘客姓名重复 | +| 42007 | DUPLICATE_BOOKING | Duplicate booking detected | false | 重复预订 | +| 42008 | INVALID_PASSENGER_INFO | Invalid passenger information | false | 乘客信息错误 | +| 42009 | DOCUMENT_ISSUE | Document validation failed | false | 证件问题 | +| 42012 | SEAT_NOT_AVAILABLE | Selected seat is not available | false | 所选座位不可用 | +| 49000 | SYSTEM_ERROR | Internal system error | true | 系统内部错误 | +| 49003 | QUOTA_EXCEEDED | Daily quota exceeded. Please upgrade your plan or try again tomorrow. | false | 日配额已超限,请升级套餐或明天再试 | + +## PRICE_CHANGED (42010) 详细说明 + +**触发场景:** + +当航司侧票价高于预期时,接口会返回错误码 42010。 + +**响应结构:** + +```json +{ + "code": 42010, + "msg": "Fare price has changed, please check latest price from feeitem", + "timestamp": "2024-11-15T10:30:00Z", + "data": { + "orderId": "abbc4b57fb75", + "feeItems": { + "baseFare": 110.00, + "taxes": 52.50, + "total": 162.50, + "currency": "EUR" + } + } +} +``` + +**处理流程:** + +1. 接收到 42010 错误响应 +2. 从响应的 `data.feeItems` 中获取最新价格 +3. 向用户展示价格变动信息 +4. 询问用户是否接受新价格 +5. 如用户同意,重新调用接口 +6. 如用户拒绝,取消订单流程 diff --git a/docs/11-01_mailbox-domain-list.md b/docs/11-01_mailbox-domain-list.md new file mode 100644 index 0000000..ef1309a --- /dev/null +++ b/docs/11-01_mailbox-domain-list.md @@ -0,0 +1,90 @@ + +# mailbox/domain/list - 域名列表 + +## 概述 + +查询当前客户已绑定的所有邮箱域名列表。 + +### 使用场景 + +- 查看当前客户已绑定的所有邮箱域名列表 +- 管理员需要了解企业邮箱的域名配置情况 + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/mailbox/domain/list` | +| **请求方法** | POST | +| **注意事项** | 无需请求体,系统自动从请求上下文中提取 clientCode | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +## 返回参数 + +返回 `MailboxDomain` 对象数组: + +| 参数名称 | 类型 | 示例值 | 说明 | +|----------|------|--------|------| +| domainId | Long | 123 | 域名配置主键 | +| domainName | String | example.com | 域名全称 | +| status | Integer | 1 | 状态:0-待审核,1-已启用,2-已禁用,3-已删除 | +| statusText | String | 已启用 | 状态文本描述 | +| verifyStatus | Integer | 2 | 验证状态:0-未验证,1-验证中,2-验证通过,3-验证失败 | +| verifyStatusText | String | 验证通过 | 验证状态文本 | +| verifyToken | String | a1b2c3d4 | DNS验证令牌 | +| verifyHost | String | _mailbox-verify-a1b2c3d4 | DNS验证主机名 | +| verifyTime | String | 2025-01-01T10:00:00Z | 验证通过时间(ISO 8601格式) | +| createTime | String | 2025-01-01T10:00:00Z | 创建时间(ISO 8601格式) | +| updateTime | String | 2025-01-02T10:00:00Z | 更新时间(ISO 8601格式) | +| remark | String | 备注信息 | 备注 | + +## 返回示例 + +```json +[ + { + "domainId": 123, + "domainName": "example.com", + "status": 1, + "statusText": "已启用", + "verifyStatus": 2, + "verifyStatusText": "验证通过", + "verifyToken": "a1b2c3d4", + "verifyHost": "_mailbox-verify-a1b2c3d4", + "verifyTime": "2025-01-01T10:00:00Z", + "createTime": "2025-01-01T10:00:00Z", + "updateTime": "2025-01-02T10:00:00Z", + "remark": "主域名" + }, + { + "domainId": 124, + "domainName": "company.com", + "status": 0, + "statusText": "待审核", + "verifyStatus": 0, + "verifyStatusText": "未验证", + "verifyToken": "e5f6g7h8", + "verifyHost": "_mailbox-verify-e5f6g7h8", + "verifyTime": null, + "createTime": "2025-01-03T10:00:00Z", + "updateTime": "2025-01-03T10:00:00Z", + "remark": "备用域名" + } +] +``` + +## 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 401 | 认证失败,client-key 或 client-secret 无效 | +| 403 | 权限不足,无权访问该客户的域名列表 | +| 500 | 服务器内部错误 | diff --git a/docs/11-02_mailbox-domain-apply.md b/docs/11-02_mailbox-domain-apply.md new file mode 100644 index 0000000..3281d91 --- /dev/null +++ b/docs/11-02_mailbox-domain-apply.md @@ -0,0 +1,90 @@ + +# mailbox/domain/apply - 申请绑定域名 + +# 申请绑定域名(mailbox/domain/apply) API 说明文档 + +## 使用场景 + +> 1. 企业客户需要为自己的邮箱系统绑定自定义域名 +> 1. 配置企业邮箱后,员工可以使用 @company.com 格式的邮箱地址 + +## 错误场景(不应该使用本接口的场景) + +> 1. 域名已被其他客户绑定 +> 1. 域名格式不正确(如包含非法字符) +> 1. 客户已达到域名绑定数量上限 + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 1000ms + +## 请求说明 + +| **请求地址** | https://${endpoint}/mailbox/domain/apply | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### 请求体 + +```json +{ + "domainName": "example.com" +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| domainName | String | 是 | example.com | 要绑定的域名名称,必须是有效的域名格式 | + +## 返回参数 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| domainId | Long | 123 | 域名唯一标识 | +| domainName | String | example.com | 域名名称 | +| status | String | PENDING | 域名状态:PENDING=待DNS验证 | +| dnsVerified | Boolean | false | DNS验证状态,新申请的域名需要验证 | +| createTime | String | 2025-01-02T10:00:00Z | 域名创建时间(ISO 8601格式) | + +## 返回示例 + +```json +{ + "domainId": 123, + "domainName": "example.com", + "status": "PENDING", + "dnsVerified": false, + "createTime": "2025-01-02T10:00:00Z" +} +``` + +## 后续步骤 + +申请域名绑定后,需要完成DNS验证才能激活邮箱服务: + +1. 在域名DNS管理中添加相应的MX记录 +2. 调用 `mailbox/domain/verify` 接口验证DNS配置 +3. 验证通过后,域名状态将变为 ACTIVE + +## 错误码说明 + +| **错误码** | **说明** | +| --- | --- | +| 400 | 请求参数错误,域名格式不正确 | +| 401 | 认证失败,client-key 或 client-secret 无效 | +| 409 | 域名已被其他客户绑定 | +| 429 | 已达到域名绑定数量上限 | +| 500 | 服务器内部错误 | diff --git a/docs/11-03_mailbox-domain-detail.md b/docs/11-03_mailbox-domain-detail.md new file mode 100644 index 0000000..ac1feff --- /dev/null +++ b/docs/11-03_mailbox-domain-detail.md @@ -0,0 +1,87 @@ + +# mailbox/domain/detail - 域名详情 + +# 查询域名详情(mailbox/domain/detail) API 说明文档 + +## 使用场景 + +> 1. 查看指定域名的详细配置信息 +> 1. 管理员需要了解域名的DNS配置和验证状态 + +## 错误场景(不应该使用本接口的场景) + +> 1. 查询的域名ID不存在 +> 1. 查询的域名不属于当前客户(权限不足) + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 500ms + +## 请求说明 + +| **请求地址** | https://${endpoint}/mailbox/domain/detail | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### 请求体 + +```json +{ + "domainId": 123 +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| domainId | Long | 是 | 123 | 域名ID | + +## 返回参数 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| domainId | Long | 123 | 域名唯一标识 | +| domainName | String | example.com | 域名名称 | +| status | String | ACTIVE | 域名状态 | +| dnsVerified | Boolean | true | DNS验证状态 | +| createTime | String | 2025-01-01T10:00:00Z | 域名创建时间 | +| updateTime | String | 2025-01-02T10:00:00Z | 域名更新时间 | +| mxRecord | String | mail.example.com | MX记录(如已配置) | +| txtRecord | String | v=spf1 include:... | SPF记录(如已配置) | + +## 返回示例 + +```json +{ + "domainId": 123, + "domainName": "example.com", + "status": "ACTIVE", + "dnsVerified": true, + "createTime": "2025-01-01T10:00:00Z", + "updateTime": "2025-01-02T10:00:00Z", + "mxRecord": "mail.example.com", + "txtRecord": "v=spf1 include:_spf.example.com ~all" +} +``` + +## 错误码说明 + +| **错误码** | **说明** | +| --- | --- | +| 400 | 请求参数错误,domainId 格式不正确 | +| 401 | 认证失败,client-key 或 client-secret 无效 | +| 403 | 权限不足,无权访问该域名信息 | +| 404 | 域名不存在 | +| 500 | 服务器内部错误 | diff --git a/docs/11-04_mailbox-domain-delete.md b/docs/11-04_mailbox-domain-delete.md new file mode 100644 index 0000000..90acea3 --- /dev/null +++ b/docs/11-04_mailbox-domain-delete.md @@ -0,0 +1,95 @@ + +# mailbox/domain/delete - 删除域名 + +# 删除域名(mailbox/domain/delete) API 说明文档 + +## 使用场景 + +> 1. 企业不再需要使用某个域名作为邮箱域名 +> 1. 域名配置错误,需要删除后重新申请 + +## 错误场景(不应该使用本接口的场景) + +> 1. 域名下仍有活跃的邮箱账户(需要先删除邮箱账户) +> 1. 域名不属于当前客户(权限不足) +> 1. 域名ID不存在 + +## 性能指标 + +> - 响应速度:98% 的请求响应速度 < 1000ms + +## 请求说明 + +| **请求地址** | https://${endpoint}/mailbox/domain/delete | +| --- | --- | +| **请求方法** | POST | + +## 请求参数 + +### Header + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境secret | + +### 请求体 + +```json +{ + "domainId": 123 +} +``` + +#### 参数详情 + +| **参数名称** | **类型** | **是否必选** | **示例值** | **说明** | +| --- | --- | --- | --- | --- | +| domainId | Long | 是 | 123 | 要删除的域名ID | + +## 返回参数 + +| **参数名称** | **类型** | **示例值** | **说明** | +| --- | --- | --- | --- | +| code | Integer | 0 | 状态码:0=成功,1=失败 | +| msg | String | Domain deleted successfully | 操作结果消息 | +| domainId | Long | 123 | 被删除的域名ID | + +## 返回示例 + +成功响应: +```json +{ + "code": 0, + "msg": "Domain deleted successfully", + "domainId": 123 +} +``` + +失败响应: +```json +{ + "code": 1, + "msg": "Failed to delete domain", + "domainId": 123 +} +``` + +## 注意事项 + +> - 删除域名是不可逆操作,请谨慎操作 +> - 删除前请确保该域名下没有关联的邮箱账户 +> - 删除后,该域名的所有邮箱服务将停止 + +## 错误码说明 + +| **错误码** | **说明** | +| --- | --- | +| 400 | 请求参数错误,domainId 格式不正确 | +| 401 | 认证失败,client-key 或 client-secret 无效 | +| 403 | 权限不足,无权删除该域名 | +| 404 | 域名不存在 | +| 409 | 域名下仍有活跃的邮箱账户,无法删除 | +| 500 | 服务器内部错误 | diff --git a/docs/11-06_mailbox-mail-search.md b/docs/11-06_mailbox-mail-search.md new file mode 100644 index 0000000..e125903 --- /dev/null +++ b/docs/11-06_mailbox-mail-search.md @@ -0,0 +1,400 @@ + +# mailbox/mail/search - 搜索邮件 + +## 概述 + +根据关键词、邮件类型等条件搜索邮件,返回分页结果。 + +### 使用场景 + +- 用户需要在邮箱中查找特定邮件 +- 根据关键词、邮件类型等条件筛选邮件 +- 分页浏览邮件列表 + +## 请求说明 + +| 项目 | 值 | +|------|-----| +| **请求地址** | `https://${endpoint}/mailbox/mail/search` | +| **请求方法** | POST | + +### Header 参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| Content-Type | String | 是 | application/json | 固定值,指定请求体格式为 JSON | +| Accept-Encoding | String | 是 | gzip, deflate, br | 必须要填写,否则服务器会拒绝 | +| client-key | String | 是 | xxxxx | 联系我们获取生产环境 key | +| client-secret | String | 是 | xxxxx | 联系我们获取生产环境 secret | + +### 请求体参数 + +| 参数名称 | 类型 | 是否必选 | 示例值 | 说明 | +|----------|------|----------|--------|------| +| keyword | String | 否 | 机票 | 搜索关键词,支持邮件主题、发件人等 | +| mailType | Integer | 否 | 1 | 邮件类型:1-收件,2-发件 | +| domainName | String | 否 | example.com | 域名过滤 | +| hasAttachment | Boolean | 否 | true | 是否有附件 | +| readStatus | Integer | 否 | 0 | 阅读状态:0-未读,1-已读 | +| startTime | String | 否 | 2025-01-01T00:00:00Z | 起始时间(ISO 8601格式,**必须使用UTC时区**,以`Z`结尾) | +| endTime | String | 否 | 2025-01-31T23:59:59Z | 结束时间(ISO 8601格式,**必须使用UTC时区**,以`Z`结尾) | +| airlineCode | String | 否 | CA | 航空代码(如:CA-国航、MU-东航、CZ-南航等) | +| pageIndex | Integer | 是 | 1 | 页码,从1开始 | +| pageSize | Integer | 是 | 20 | 每页数量,建议10-50,最大100 | +| orderBy | String | 否 | sendTime | 排序字段,支持:`sendTime`(发送时间)、`receiveTime`(接收时间)、`subject`(主题) | +| orderDirection | String | 否 | DESC | 排序方向:`ASC`=升序,`DESC`=降序 | + +#### 请求体示例 + +```json +{ + "keyword": "机票", + "mailType": 1, + "airlineCode": "CA", + "pageIndex": 1, + "pageSize": 20, + "orderBy": "sendTime", + "orderDirection": "DESC" +} +``` + +## 返回参数 + +返回分页结果 `PageResult`: + +| 参数名称 | 类型 | 示例值 | 说明 | +|----------|------|--------|------| +| pageIndex | Integer | 1 | 当前页码 | +| pageSize | Integer | 20 | 每页数量 | +| total | Long | 150 | 总记录数 | +| records | Array\ | - | 邮件列表 | + +> **注意**: +> - 请求和响应统一使用 `pageIndex` +> - 总页数 `totalPages` 字段已移除,前端需自行计算:`totalPages = Math.ceil(total / pageSize)` + +### MailInfo 对象字段 + +| 参数名称 | 类型 | 示例值 | 说明 | +|----------|------|--------|------| +| mailId | String | MSG_001 | 邮件ID | +| subject | String | 机票预订确认 | 邮件主题 | +| fromAddress | String | airline@example.com | 发件人地址 | +| toAddresses | String\[\] | ["user@example.com"] | 收件人列表 | +| ccAddresses | String\[\] | ["cc@example.com"] | 抄送列表 | +| mailType | Integer | 1 | 邮件类型:1-收件,2-发件 | +| mailTypeText | String | 收件 | 类型文本 | +| hasAttachment | Boolean | true | 是否有附件 | +| attachmentCount | Integer | 2 | 附件数量 | +| mailSize | Long | 1024000 | 邮件大小(字节) | +| mailSizeText | String | 1.0 MB | 人性化大小 | +| readStatus | Integer | 0 | 阅读状态:0-未读,1-已读 | +| receiveTime | String | 2025-01-02T10:00:00Z | 接收时间 | +| sendTime | String | 2025-01-02T10:00:00Z | 发送时间 | +| emlDownloadUrl | String | https://... | 原始EML文件下载地址(临时链接,1小时有效期) | + +## 返回示例 + +```json +{ + "pageIndex": 1, + "pageSize": 20, + "total": 150, + "records": [ + { + "mailId": "MSG_001", + "subject": "机票预订确认", + "fromAddress": "airline@example.com", + "toAddresses": ["user@example.com"], + "ccAddresses": ["cc@example.com"], + "mailType": 1, + "mailTypeText": "收件", + "hasAttachment": true, + "attachmentCount": 2, + "mailSize": 1024000, + "mailSizeText": "1.0 MB", + "readStatus": 0, + "receiveTime": "2025-01-02T10:00:00Z", + "sendTime": "2025-01-02T10:00:00Z", + "emlDownloadUrl": "https://cdn.example.com/download/eml/MSG_001?token=xxx" + }, + { + "mailId": "MSG_002", + "subject": "航班变更通知", + "fromAddress": "airline@example.com", + "toAddresses": ["user@example.com"], + "ccAddresses": [], + "mailType": 1, + "mailTypeText": "收件", + "hasAttachment": false, + "attachmentCount": 0, + "mailSize": 51200, + "mailSizeText": "50.0 KB", + "readStatus": 1, + "receiveTime": "2025-01-02T09:00:00Z", + "sendTime": "2025-01-02T09:00:00Z", + "emlDownloadUrl": "https://cdn.example.com/download/eml/MSG_002?token=yyy" + } + ] +} +``` + +## 时区说明 + +### UTC 时区要求 + +本接口要求所有时间参数**必须使用 UTC 时区**(协调世界时)。 + +**什么是 UTC?** +- UTC(Coordinated Universal Time)是世界标准时间,不受夏令时影响 +- UTC 是全球统一的时间基准,与地理位置无关 +- 北京时间 = UTC + 8小时,纽约时间 = UTC - 5小时(东部标准时间) + +**为什么使用 UTC?** +1. **避免时区混淆**:不同地区的服务器和客户端使用不同时区会导致时间解释错误 +2. **跨时区一致**:确保全球用户查询结果一致 +3. **简化逻辑**:无需处理复杂的时区转换和夏令时问题 + +**时间格式要求:** +``` +ISO 8601 格式:2025-01-15T00:00:00Z + ^^^^^^^^^^^ ^^^^^^^ ^ ^ + 日期 时间 时区标识(UTC) +``` + +### 时间转换示例 + +| 本地时间 | UTC 时间(需要发送) | 说明 | +|----------|---------------------|------| +| 2025-01-15 00:00:00 (北京) | 2025-01-14T16:00:00Z | 北京时间 UTC+8,减8小时 | +| 2025-01-15 00:00:00 (纽约) | 2025-01-15T05:00:00Z | 纽约时间 UTC-5,加5小时 | +| 2025-01-15 00:00:00 (伦敦) | 2025-01-15T00:00:00Z | 伦敦时间 UTC+0(冬季) | +| 2025-01-15 00:00:00 (东京) | 2025-01-14T15:00:00Z | 东京时间 UTC+9,减9小时 | + +> **重要**:发送时间前,请务必将本地时间转换为 UTC 时间! + +### 代码示例 + +#### Java 示例 + +```java +import java.time.*; +import java.time.format.DateTimeFormatter; +import okhttp3.*; +import com.google.gson.Gson; + +public class MailSearchExample { + + private static final String API_ENDPOINT = "https://api.example.com/mailbox/mail/search"; + private static final String CLIENT_KEY = "your-client-key"; + private static final String CLIENT_SECRET = "your-client-secret"; + + public static void main(String[] args) { + // 方式1: 使用当前时间(自动转换为UTC) + ZonedDateTime nowUtc = ZonedDateTime.now(ZoneOffset.UTC); + ZonedDateTime oneDayAgoUtc = nowUtc.minusDays(1); + + // 方式2: 从本地时间转换到UTC + ZonedDateTime localTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); + ZonedDateTime utcTime = localTime.withZoneSameInstant(ZoneOffset.UTC); + + // 方式3: 指定特定时间(例如:北京时间2025-01-15 00:00:00) + ZonedDateTime beijingTime = ZonedDateTime.of( + 2025, 1, 15, 0, 0, 0, 0, + ZoneId.of("Asia/Shanghai") + ); + ZonedDateTime startTimeUtc = beijingTime.withZoneSameInstant(ZoneOffset.UTC); + ZonedDateTime endTimeUtc = beijingTime.plusDays(1).minusSeconds(1) + .withZoneSameInstant(ZoneOffset.UTC); + + // 格式化为ISO 8601格式(带Z后缀) + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + String startTime = startTimeUtc.format(formatter); // 2025-01-14T16:00:00Z + String endTime = endTimeUtc.format(formatter); // 2025-01-15T15:59:59Z + + // 构建请求体 + MailSearchRequest request = new MailSearchRequest(); + request.setKeyword("机票"); + request.setMailType(1); + request.setStartTime(startTime); + request.setEndTime(endTime); + request.setPageIndex(1); + request.setPageSize(20); + + // 设置排序(按发送时间降序) + request.setOrderBy("sendTime"); + request.setOrderDirection("DESC"); + + // 发送请求 + sendRequest(request); + } + + static class MailSearchRequest { + private String keyword; + private Integer mailType; + private String startTime; + private String endTime; + private Integer pageIndex; + private Integer pageSize; + private String orderBy; + private String orderDirection; + + // getters and setters... + } + + private static void sendRequest(MailSearchRequest request) throws Exception { + Gson gson = new Gson(); + String json = gson.toJson(request); + + OkHttpClient client = new OkHttpClient(); + RequestBody body = RequestBody.create( + json, + MediaType.parse("application/json; charset=utf-8") + ); + + Request httpRequest = new Request.Builder() + .url(API_ENDPOINT) + .post(body) + .addHeader("Content-Type", "application/json") + .addHeader("Accept-Encoding", "gzip, deflate, br") + .addHeader("client-key", CLIENT_KEY) + .addHeader("client-secret", CLIENT_SECRET) + .build(); + + try (Response response = client.newCall(httpRequest).execute()) { + System.out.println(response.body().string()); + } + } +} +``` + +#### Python 示例 + +```python +import requests +from datetime import datetime, timezone, timedelta +from zoneinfo import ZoneInfo # Python 3.9+ +import json + +API_ENDPOINT = "https://api.example.com/mailbox/mail/search" +CLIENT_KEY = "your-client-key" +CLIENT_SECRET = "your-client-secret" + +def search_mails(): + # 方式1: 使用当前UTC时间 + now_utc = datetime.now(timezone.utc) + one_day_ago_utc = now_utc - timedelta(days=1) + + # 方式2: 从本地时间转换到UTC(例如:北京时间) + beijing_tz = ZoneInfo("Asia/Shanghai") + local_time = datetime(2025, 1, 15, 0, 0, 0, tzinfo=beijing_tz) + start_time_utc = local_time.astimezone(timezone.utc) + end_time_utc = (local_time + timedelta(days=1) - timedelta(seconds=1)).astimezone(timezone.utc) + + # 方式3: 直接创建UTC时间 + start_time_utc = datetime(2025, 1, 14, 16, 0, 0, tzinfo=timezone.utc) + end_time_utc = datetime(2025, 1, 15, 15, 59, 59, tzinfo=timezone.utc) + + # 格式化为ISO 8601格式(带Z后缀) + start_time = start_time_utc.isoformat() + end_time = end_time_utc.isoformat() + # 结果: "2025-01-14T16:00:00+00:00" 或 "2025-01-14T16:00:00Z" + + # 确保以Z结尾(某些库可能使用+00:00) + if not start_time.endswith('Z'): + start_time = start_time.replace('+00:00', 'Z').replace('+00:00:00', 'Z') + if not end_time.endswith('Z'): + end_time = end_time.replace('+00:00', 'Z').replace('+00:00:00', 'Z') + + # 构建请求体 + payload = { + "keyword": "机票", + "mailType": 1, + "startTime": start_time, # "2025-01-14T16:00:00Z" + "endTime": end_time, # "2025-01-15T15:59:59Z" + "pageIndex": 1, + "pageSize": 20, + "orderBy": "sendTime", + "orderDirection": "DESC" + } + + # 发送请求 + headers = { + "Content-Type": "application/json", + "Accept-Encoding": "gzip, deflate, br", + "client-key": CLIENT_KEY, + "client-secret": CLIENT_SECRET + } + + response = requests.post( + API_ENDPOINT, + data=json.dumps(payload), + headers=headers + ) + + print(f"Status Code: {response.status_code}") + print(f"Response: {response.json()}") + + return response.json() + +# 常用时区转换工具函数 +def convert_to_utc(local_time_str, local_timezone="Asia/Shanghai"): + """ + 将本地时间字符串转换为UTC时间字符串 + + Args: + local_time_str: 本地时间,如 "2025-01-15 00:00:00" + local_timezone: 本地时区,默认 "Asia/Shanghai" + + Returns: + UTC时间字符串,如 "2025-01-14T16:00:00Z" + """ + local_tz = ZoneInfo(local_timezone) + local_dt = datetime.strptime(local_time_str, "%Y-%m-%d %H:%M:%S") + local_dt = local_dt.replace(tzinfo=local_tz) + utc_dt = local_dt.astimezone(timezone.utc) + return utc_dt.isoformat().replace('+00:00', 'Z') + +# 使用示例 +if __name__ == "__main__": + # 北京时间 2025-01-15 00:00:00 转换为 UTC + utc_time = convert_to_utc("2025-01-15 00:00:00", "Asia/Shanghai") + print(f"UTC Time: {utc_time}") # 输出: 2025-01-14T16:00:00Z + + search_mails() +``` + +#### cURL 示例 + +```bash +curl -X POST "https://api.example.com/mailbox/mail/search" \ + -H "Content-Type: application/json" \ + -H "Accept-Encoding: gzip, deflate, br" \ + -H "client-key: your-client-key" \ + -H "client-secret: your-client-secret" \ + -d '{ + "keyword": "机票", + "mailType": 1, + "startTime": "2025-01-14T16:00:00Z", + "endTime": "2025-01-15T15:59:59Z", + "pageIndex": 1, + "pageSize": 20, + "orderBy": "sendTime", + "orderDirection": "DESC" + }' +``` + +## 搜索技巧 + +- 支持模糊匹配,关键词可以匹配邮件主题和发件人 +- mailType 为可选参数,不传则搜索所有类型邮件 +- 建议 pageSize 设置为 20-50 以获得最佳性能 +- **时间参数必须使用 UTC 时区**,发送前请确保时间已正确转换 + +## 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 400 | 请求参数错误,pageIndex、pageSize、orderBy 或 orderDirection 不合法 | +| 401 | 认证失败,client-key 或 client-secret 无效 | +| 500 | 服务器内部错误 | diff --git a/docs/11_proxy-conventions.md b/docs/11_proxy-conventions.md new file mode 100644 index 0000000..36e92bd --- /dev/null +++ b/docs/11_proxy-conventions.md @@ -0,0 +1,55 @@ + +# Proxy约定说明 + +用户可在luban系统(my.mipuyun.com)管理我们提供的高质量住宅代理。 + +为了更有效的使用代理,对于Proxy字段的发送提供以下约定 + +--- + +## 1. 指定代理地区/国家 + +在proxy名后拼接 `-国家码(地区码)`,myproxy是代理命名,举例如下: + +| 地区(国家) | 码 | Proxy命名举例 | +|-----------|---|--------------| +| 美国 | us | myproxy-us | +| 法国 | fr | myproxy-fr | +| 英国 | uk | myproxy-uk | +| **欧洲** | **eu** | **myproxy-eu** | + +--- + +## 2. 利用多个代理提升agent质量 + +为提升agent对接的质量,支持在proxy字段输入多个代理,并提供串行和并发来满足各种场景 + +### 拼接符号介绍 + +| 符号 | 说明 | +|------|------| +| **竖线 \|** | 多个代理随机选择 | +| **逗号 ,** | 多个代理串行选择,希望提升成功率,但可以接受响应速度略慢的场景 | +| **加号 +** | 多个代理并发执行,应用于有较高成功率&较低响应速度的场景 | + +### 代理使用示例 + +| 代理举例 | 适合场景 | 效果 | +|----------|---------|------| +| myproxy-us | 所有场景 | 指定US地区进行agent执行 | +| myproxy-us\|myproxy-uk | 所有场景 | 从US, UK两个地区随机选择一个进行agent执行 | +| myproxy-us\|myproxy-uk,myproxy-us\|myproxy-eu | **shopping/search** | 仅对shpping/search生效,可接受较大的响应时长等待,第一轮采用myproxy-us\|myproxy-uk进行对接。如果成功,立即返回结束业务。如果失败,采用myproxy-us\|myproxy-eu启动第二轮对接。 | +| myproxy-us + myproxy-uk | **shopping/select**
**shopping/baggage**
**shopping/seat** | 采用多线程并发模式,线程a采用myproxy-us,线程b采用myproxy-uk, 同时向航司发起业务请求。 | +| myproxy-us\|myproxy-eu + myproxy-uk\|myproxy-uk | **shopping/select**
**shopping/baggage**
**shopping/seat** | 采用多线程并发模式,线程a采用myproxy-us\|myproxy-eu,线程b采用myproxy-uk\|myproxy-uk, 同时向航司发起业务请求。 | + +--- + +## 3. 异常处理 + +### 如果将符号用在了不支持的场景怎么办? + +系统会最大限度接受异常的代理串输入,但会选择最保守的执行方式,我们会采集最小的代理信息进行执行。 + +**示例**: +- 并发场景(select/baggage/seat)收到逗号:`myproxy-us,myproxy-uk` +- 那么执行层会变成:`myproxy-us` diff --git a/docs/12_brightdata-proxy-advanced.md b/docs/12_brightdata-proxy-advanced.md new file mode 100644 index 0000000..989bbdd --- /dev/null +++ b/docs/12_brightdata-proxy-advanced.md @@ -0,0 +1,157 @@ + +# BrightData 代理串高级用法 + +# 📄 BrightData 代理 URL 使用指南 + +**适用于:BrightData Residential / Datacenter / Mobile Proxies** + +--- + +## 🔗 1. 代理 URL 格式解析 + +示例 URL: + +```plaintext +http://brd-customer-hl_333c2-zone-myproxy-country-us-session-11111vy0c +``` + +### ✅ 结构拆解: + +| **部分** | **说明** | +| --- | --- | +| brd-customer-hl_665225c2 | 客户 ID(固定,由 BrightData 分配) | +| zone=myproxy | 代理子账号名称 | +| country=us | 目标国家代码(ISO 3166-1 alpha-2,如 us, gb, jp) | +| session=11111vy0c | 会话 ID(用于控制 IP 是否复用) | +| proxypassword | 代理子账号密码 | +| brd.superproxy.io:22225 | BrightData 全球统一代理网关 | + +> 💡 **关键参数均通过用户名(username)中的字段传递**,这是 BrightData 的特殊设计。 + +--- + +## 🌍 2. 如何指定国家(Country) + +只需修改 `country=xx` 部分即可。 + +### 示例: + +```plaintext +# 美国 IP +...-country-us-... + +# 英国 IP +...-country-gb-... + +# 日本 IP +...-country-jp-... + +# 德国 IP + +``` + +> ✅ 支持所有 BrightData 覆盖的国家([完整国家列表](https%3A%2F%2Fbrightdata.com%2Fcountries)) +> ⚠️ 如果指定的国家无可用 IP,请求可能失败或返回默认地区 IP(取决于 Zone 设置) + +--- + +## 🔁 3. 如何更换 IP(获取新 IP) + +BrightData 使用 `**session**`** 参数控制 IP 复用**: + +- **相同 **`**session**`** 值 → 复用同一个出口 IP**(通常维持 5 分钟以上) +- **不同 **`**session**`** 值 → 获取全新 IP** +### ✅ 方法:修改 `session` 字段 + +#### 示例: + +```plaintext +# Session A: 固定 IP #1 +...-session-abc123@... + +# Session B: 固定 IP #2(与 abc123 不同) + +``` + +> 🔑 **只要 **`**session**`** 值不同,就会分配新 IP**(即使其他参数相同) + +### 💡 编程建议: + +在代码中动态生成随机 session ID 来每次获取新 IP: + +``` +import random +import string + +def generate_session_id(length=10): + return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) + +session = generate_session_id() + +``` + +--- + +## 🔒 4. 如何保持 IP 不变(固定 IP) + +只需 **在多次请求中使用相同的 **`**session**`** 值**。 + +### ✅ 规则: + +- 同一个 `session` 在 **5 分钟内有效** +- 在此期间,所有使用该 session 的请求将通过 **同一个出口 IP** +- 超过 5 分钟无活动,IP 可能被回收 +### 示例(固定美国 IP): + +```plaintext +http://brd-customer-hl_665225c2-zone-myproxy-country-us-session-my_fixed_ip_001:xxxxy0c@brd.superproxy.io:22225/ +``` + +> 🕒 建议:如果需要长时间固定 IP,每 5~8 分钟发一次心跳请求以保持会话活跃。 + +--- + +## 🛠️ 5. 完整使用示例(Python requests) + +``` +import requests + +# 固定美国 IP(session = "my_session_1") +proxy_url = "http://brd-customer-hl_665225c2-zone-myproxy-country-us-session-my_session_1:xxxxy0c@brd.superproxy.io:22225/" + +proxies = { + "http": proxy_url, + "https": proxy_url +} + +try: + resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10) + print(resp.json()) # 显示出口 IP +except Exception as e: + +``` + +--- + +## ⚙️ 6. 其他高级参数(可选) + +你还可以在 username 中添加更多参数(用 `-` 连接): + +[电子表格] + +> 📌 完整参数文档:[BrightData Proxy Parameters](https%3A%2F%2Fdocs.brightdata.com%2Fapi-reference%2Fproxy%2Fgeolocation-targeting%23city-targeting) + +--- + +## ❗ 注意事项 + +1. **密码(**`**proxypassword**`**)是敏感信息**,切勿泄露或提交到 Git。 +1. **不要硬编码 session** 用于大规模爬虫,避免 IP 被目标网站封禁。 +--- + +## ✅ 总结:核心操作速查 + +[电子表格] + +--- + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e487f1d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,29 @@ + +# 米普云 - 航司接口转换器API文档 + +## 什么是航司接口转换器 + +航司接口转换器是一种中间服务工具,用于将不同航空公司的预订、出票、查询等系统接口进行标准化和适配。由于各航司的系统架构、数据格式、认证方式各不相同,直接对接成本高、开发周期长。航司接口转换器通过统一的接入层,帮助第三方平台(如OTA、旅行社、企业差旅系统等)快速、高效地与多个航司系统对接。 + +该转换器的核心功能包括: + +- **协议转换**:将不同航司的私有API或B2B协议转换为通用标准 +- **数据映射**:将航司返回的原始数据结构转换为易于理解和处理的统一格式 +- **身份代理**:通过Agent机制,自动完成登录、鉴权、会话维持等操作 +- **稳定性保障**:提供重试机制、错误处理、日志记录等功能,提升对接稳定性 + +## 如何使用航司接口转换器 + +### 1. 选择需要转换的航司&渠道 + +访问 https://my.mipuyun.com,登录后选择:航司列表菜单,在你需要转换的航司上面选择对应的渠道,一般渠道有 Web(官网)/B2B/API。 + +### 2. 创建Agent + +### 3. 在线测试 + +创建好 Agent 后,可在控制台中选择该 Agent 并点击"运行"按钮,立即发起一次测试请求。 + +### 4. 通过API与我们对接 + +当测试成功后,你可以调用我们的开放 API 接口,实现全流程自动化。 diff --git a/mipu-api b/mipu-api new file mode 160000 index 0000000..6281559 --- /dev/null +++ b/mipu-api @@ -0,0 +1 @@ +Subproject commit 628155990acb5e0edae46e584782fb140c69e628