家驿 · 接口契约文档
从第一条请求开始,清晰了解家驿的查询接口、报文约定与业务边界。
接入概览
本文档描述当前后端实现中的火车票查询能力。客户 OpenAPI 提供车次余票、经停站、儿童票价格与区间里程查询;车站起售时间见扩展接口章节。文档可公开阅读,接口调用仍需完成客户开通。
| 接口 | 方式 | 路径 |
|---|---|---|
| 余票/车次查询 | POST | /supplier/vendor/openApi/queryTrains |
| 经停站查询 | POST | /supplier/vendor/openApi/queryTrainStop |
| 儿童票价格计算 | POST | /supplier/vendor/openApi/childTicketPrice |
| 车次区间里程 | POST | /supplier/vendor/openApi/queryTrainMileage |
路径相对于环境 Base URL;本站网关前缀为 /api,拼接时不要重复添加。Content-Type: application/json;报文采用 UTF-8。请使用 HTTPS,并在服务端完成签名和加解密。
签名与报文约定
- 生成当前时间
timeStamp,格式yyyy-MM-dd HH:mm:ss。按北京时间联调并确认服务端时区,时钟误差控制在 1 分钟内,每次请求刷新时间。 - 依次直接拼接
timeStamp + supplierId + secretKey,不加分隔符,计算 MD5 十六进制摘要作为messageIdentity。服务端比较不区分大小写。 - 将业务 JSON 的 UTF-8 字节用分配的密钥进行 AES/ECB/PKCS7Padding 加密,再进行标准 Base64 编码,写入
bodyInfo。Java 对应AES/ECB/PKCS5Padding,无 IV。 - 请求路径决定调用接口,
serviceName填对应的接口名。密钥按接入方分配值使用,不做 MD5 或 Base64 派生。
以下仅展示报文结构,尖括号内容均须替换,不能直接发送。
{
"authentication": {
"timeStamp": "<当前时间>",
"serviceName": "queryTrains",
"supplierId": "<分配的客户标识>",
"messageIdentity": "<计算得到的 MD5 摘要>"
},
"bodyInfo": "<业务 JSON 加密后的 Base64 密文>"
}统一响应
{
"errNo": 0,
"errMsg": "SUCCESS",
"bodyInfo": "<响应业务 JSON 的 Base64 密文>"
}先判断 errNo == 0,再使用同一密钥解密 bodyInfo 并解析 JSON。失败时读取 errNo 和 errMsg,bodyInfo 可能为空或不返回。HTTP 成功不等于业务成功。
01 / 余票与车次查询
POST /supplier/vendor/openApi/queryTrains
serviceName:queryTrains。返回车次时刻、席别票价及余票;经停信息通过独立接口查询。此接口需开通客户生产查询权限。
业务请求字段(加密前)
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
| ticketDate | string | 必传 | 乘车日期,yyyy-MM-dd |
| fromStation | string | 必传 | 出发站名称,按环境支持的车站名称传入 |
| toStation | string | 必传 | 到达站名称 |
| trainNo | string | 车次列表可选;经停必传 | 对外车次号,例如 G1 |
不传 seatName;即使传入,当前接口也会忽略该字段。
{
"ticketDate": "2026-10-20",
"fromStation": "北京南",
"toStation": "上海虹桥",
"trainNo": "G1"
}响应业务体(解密后)
trainList 为车次数组,无匹配结果可返回空数组。以下是空结果示例,不是实时查询。
{
"trainList": []
}trainList[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
| trainNo | string | 车次号 |
| trainType | string | 列车类型(高速/动车/普速/直达 等) |
| trainDate | string | 发车日期 yyyy-MM-dd |
| trainDateTime | string | 出发时间 HH:mm |
| arriveDate | string | 到达日期 yyyy-MM-dd |
| arriveDateTime | string | 到达时间 HH:mm |
| fromStationName | string | 发站 |
| toStationName | string | 到站 |
| durationMinutes | integer | 历时分钟 |
| seatOptions | array<object> | 座位详情列表(多种座位类型的价格/库存) |
| quickPassEnabled | integer | 是否支持快速进站,1=支持 0=不支持 |
| bookable | string | >=0 正常售票 -1 不可购票 |
| canChooseBed | string | 是否可选铺位:1 可选铺,0 不可选铺 |
| isDgh | string | 是否是动高:1 是,0 否 |
| isStartStation | boolean | 是否是始发站:true 是,false 否 |
| isEndStation | boolean | 是否是终点站:true 是,false 否 |
| preSaleStartTime | string | 预售时间(已开始售票则取时间最小值) |
| intelligentTrain | string | 是否智能动车组(1=是 其他否) |
| hasQuietCoach | string | 是否有静音车厢(1=是 其他否) |
| rejuvenationTrain | string | 是否复兴号(1=是 其他否) |
seatOptions[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
| seatName | string | 座位类型(硬座/软座/硬卧/软卧 等) |
| price | number | 该座位类型票价(元) |
| availableSeats | integer | 剩余座位数量 |
| lowerBerthPrice | number | 下铺价格(仅卧铺票存在) |
| middleBerthPrice | number | 中铺价格(仅卧铺票存在) |
| upperBerthPrice | number | 上铺价格(仅卧铺票存在) |
| discount | string | 票价折扣标识,例:"9.7" |
| lowerBerthDiscount | string | 下铺折扣 |
| middleBerthDiscount | string | 中铺折扣 |
| upperBerthDiscount | string | 上铺折扣 |
02 / 经停站查询
POST /supplier/vendor/openApi/queryTrainStop
serviceName:queryTrainStop。ticketDate、fromStation、toStation、trainNo 均应提供,字段格式见上表。
{
"ticketDate": "2026-10-20",
"fromStation": "北京南",
"toStation": "上海虹桥",
"trainNo": "G1"
}响应业务体
| 字段 | 类型 | 说明 |
|---|---|---|
| trainNo | string | 查询车次号 |
| originStation | string | 列车始发站 |
| terminalStation | string | 列车终点站 |
| stopList | array | 经停站列表,需兼容为空或缺失 |
stopList[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
| serialNo | integer | 经停站排列序号 |
| stationCode | string | 出发站三字码(如 AOH = 上海虹桥) |
| stationName | string | 出发站名称 |
| trainTime | string | 出发时间 HH:mm |
| arrivalTime | string | 到达时间 HH:mm |
| stayMinutes | string | 停留时间(单位:分钟) |
| mileageKm | string | 自始发站累计运价里程(公里) |
累计里程字段可能缺失,不能直接替代区间票价。始发站的到达时刻和终到站的发车时刻请兼容空值。
儿童票价格计算 · childTicketPrice
POST /supplier/vendor/openApi/childTicketPrice。authentication.serviceName 必须为 childTicketPrice,沿用本文的签名与 AES 加密协议。以下 JSON 是加密前的业务体,不能直接作为完整请求发送。
{
"trainNo": "D1",
"ticketDate": "2026-10-20",
"fromStation": "北京南",
"toStation": "上海虹桥",
"seatName": "二等卧",
"berth": "DOWN",
"adultPrice": 100,
"discount": 8.5
}示例不代表实际车次及票价。trainNo、seatName、adultPrice 必填。adultPrice 为所选席别、铺位的成人实际执行价(元),必须大于 0。discount 大于 0 且不超过 10,8.5 表示八五折;不传或 10 表示无折扣。有折扣时还必须提供 ticketDate、fromStation、toStation,系统查询原价后计算;无折扣直接计算,不重复乘成人折扣。
卧铺必须传 berth:UP(上)、MIDDLE(中)、DOWN(下)。中铺仅支持硬卧、二等卧;座席不传 berth。adultPricesBySeat 可补充普速卧铺基础座席执行价,例如 {"硬座":160};缺失时可能采用估算规则。
解密后的响应字段
| 字段 | 含义 |
|---|---|
| status | SUCCESS / MISSING_BASE_PRICE / UNSUPPORTED_SEAT / INVALID_PRICE |
| calculatedPrice | 儿童票计算价(元);失败时可能为空或缺失 |
| discountRate | 命中的儿童优惠比例,不是请求的成人折扣 |
| ruleCode、dataCompleteness | 计算规则及数据完整性标记,应保留用于识别估算 |
| message | 不能计算时的说明 |
外层 errNo=0 只表示接口执行成功;解密后必须检查 status。参考计算结果不构成实际售票价格承诺。
车次区间里程 · queryTrainMileage
POST /supplier/vendor/openApi/queryTrainMileage。authentication.serviceName 必须为 queryTrainMileage。请求使用同一签名、加密协议,业务体示例:
{
"ticketDate": "2026-10-20",
"trainNo": "G1",
"fromStation": "北京南",
"toStation": "上海虹桥"
}四个字段均必填。日期为有效 yyyy-MM-dd,车站使用完整站名,到达站必须位于出发站之后。
解密后的响应字段
| 字段 | 含义 |
|---|---|
| status | SUCCESS 或 UNAVAILABLE |
| ticketDate、trainNo、fromStation、toStation | 请求日期、车次和区间 |
| mileageKm | 区间公里数;未知为 null 或缺失,不以 0 代替 |
| source | LIANTIE / LOCAL_GRAPH |
| method | CUMULATIVE_DIFFERENCE(累计公里相减)或 STOP_ROUTE_SHORTEST_PATH(沿经停序列逐段计算) |
| mileageType | SUPPLIER_REPORTED 或 FARE_GRAPH_ESTIMATE |
| estimated | true 表示本地图或代理日期估算;false 不代表与 12306 核验一致 |
| dataDate | 实际使用的经停数据查询日期 |
| reason | 不可用原因:STOP_ROUTE_UNAVAILABLE / MILEAGE_DATA_INCOMPLETE / LOCAL_GRAPH_UNAVAILABLE |
必须同时检查外层 errNo 和业务体 status。里程不能替代票价查询,不保证等同于计价所用运价里程。
错误处理
| errNo | 含义 | 处理建议 |
|---|---|---|
| 0 | 成功 | 解密并解析 bodyInfo |
| 1 | 参数错误 | 检查请求及必需字段 |
| 2 | 签名错误/时间戳过期 | 核对时钟、标识、密钥及拼接顺序 |
| 6 | 数据解析错误 | 检查密钥、填充及 Base64 编码 |
| 2011 | 计算参数或基础数据不满足要求 | 核对席别、铺位、折扣及区间,或联系接口负责人 |
| 998 | 接口配置缺失或客户未开通 | 联系接口负责人确认配置和权限 |
| 999 | 系统内部异常 | 记录接口、调用时间与错误码,交由对接人员排查 |
兼容未知非零错误码,展示明确的查询失败提示。空列表属于无结果,不能与接口失败混淆。调用频率、超时与重试策略按双方联调约定配置;本文不承诺未经约定的 SLA。
扩展平台接口
order:train:list 权限,不使用上述 OpenAPI authentication/bodyInfo 报文。仅供已获授权的接入方使用;账号、登录方式和网关前缀由接口负责人提供。| 接口 | 方法与路径 | 关键参数 |
|---|---|---|
| 车站起售时间 | GET /order/train/stationSaleTime | station、queryDate 均可选 |
| 车次区间里程 | GET /order/train/mileage | ticketDate、trainNo、fromStation、toStation 均必传 |
| 儿童票价格计算 | POST /order/train/childTicketPrice | JSON 请求;trainNo、seatName、adultPrice 必传 |
统一使用 AjaxResult 包装(code、msg、data),成功 code 为 200。还需判断 data.status,不能只检查外层成功码。
车站起售时间
station 支持完整站名(可带“站”后缀)或三字电报码;不传则返回全部有效车站。queryDate 为 yyyy-MM-dd,默认北京时间当天。
data 返回 status、queryDate、timezone、stations;每个车站包含 stationName、stationTelecode、saleTime、startDate、stopDate、sourceType、lastRefreshTime。status 为 SUCCESS 或 NOT_FOUND,无记录时 stations 为空数组。saleTime 为 HH:mm,每日起售时刻不等于具体乘车日开售时间,也不代表实时可售。
车次区间里程
日期格式 yyyy-MM-dd;使用完整站名,到达站必须位于出发站之后。data 包含 status、ticketDate、trainNo、fromStation、toStation、mileageKm、source、method、mileageType、estimated、dataDate、reason。
status 为 SUCCESS 或 UNAVAILABLE;未知里程为 null。estimated 标识估算,dataDate 表示实际使用的经停查询日期。里程不可直接替代票价查询。
儿童票价格计算
{
"trainNo": "D1",
"seatName": "二等卧",
"adultPrice": 100,
"berth": "DOWN",
"discount": 10
}仅为参数示例,金额不是真实票价。adultPrice 为所选席别铺位的成人实际执行价(元);discount 大于 0 且不超过 10,8.5 表示八五折。不传或 10 为无折扣,有折扣时另需 ticketDate、fromStation、toStation。
卧铺必须提供 berth:UP、MIDDLE、DOWN;中铺仅接受硬卧、二等卧。座席不传 berth。adultPricesBySeat 可提供基础座席执行价,如 {"硬座":160}。
data 包含 status、calculatedPrice、discountRate、ruleCode、dataCompleteness、message。status 为 SUCCESS、MISSING_BASE_PRICE、UNSUPPORTED_SEAT 或 INVALID_PRICE。缺失基础座席价格可能使用估算规则,应同时读取规则与完整性标记;参数错误和原价缺失走统一异常响应。
联调检查清单
- 确认环境地址、客户标识、密钥和生产查询权限。
- 验证签名、报文加解密、当前时间及服务端时区。
- 验证正常结果、空列表、空字段、错误签名和未开通场景。
- 兼容跨日到达日期,价格保留小数,业务失败不展示为“无票”。
- 密钥仅存储于服务端,不放入网页、浏览器存储或公开代码。
文档更新日期:2026-10-14。接口基于当前项目实现整理,环境与授权范围以实际交付为准。