From 09348e7e5317f90531ef81ec6577748175619a8e Mon Sep 17 00:00:00 2001
From: quyixiao <2621048238@qq.com>
Date: Wed, 10 Jun 2026 01:48:44 +0800
Subject: [PATCH] jruqwhnt
---
.../car/RecognizeTrainTicketController.java | 396 +++++++++++++++++-
.../car/TrainTicketRecognizeRequest.java | 22 +-
.../resp/car/RecognizeTrainTicketResp.java | 90 ++--
3 files changed, 446 insertions(+), 62 deletions(-)
diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeTrainTicketController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeTrainTicketController.java
index bf4d9be..5817028 100644
--- a/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeTrainTicketController.java
+++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeTrainTicketController.java
@@ -1,27 +1,401 @@
package com.heyu.api.controller.car;
-import com.heyu.api.baidu.handle.financial.BTrainTicketHandle;
-import com.heyu.api.baidu.request.financial.BTrainTicketRequest;
-import com.heyu.api.controller.BaseController;
-import com.heyu.api.controller.ocr.BaiduOcrResult;
+import com.heyu.api.controller.AbstractRecognizeController;
+import com.heyu.api.controller.BaiduOcrError;
import com.heyu.api.data.annotation.CacheResult;
+import com.heyu.api.data.annotation.EbAuthentication;
import com.heyu.api.data.annotation.NotIntercept;
+import com.heyu.api.data.constants.ApiConstants;
+import com.heyu.api.data.utils.ImageInputUtils;
+import com.heyu.api.data.utils.ImageInputUtils.ResolvedImageInput;
+import com.heyu.api.data.utils.MapUtils;
import com.heyu.api.data.utils.R;
-import org.springframework.beans.factory.annotation.Autowired;
+import com.heyu.api.data.utils.StringUtils;
+import com.heyu.api.request.car.TrainTicketRecognizeRequest;
+import com.heyu.api.resp.car.RecognizeTrainTicketResp;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import java.util.Map;
+
+/**
+ * 火车票识别控制器
+ *
+ * 接口背景:用于差旅报销、行程审核、票务核验等场景,将火车票照片自动识别为结构化字段,
+ * 替代人工录入。底层对接百度智能云文字识别「火车票识别」能力,由本服务完成参数校验、请求转发与字段映射。
+ *
+ *
+ * 支持版式:纸质红色车票 / 蓝色磁卡车票 / 电子客票报销凭证;自动识别始发、终到、车次、席别、座位、票价、
+ * 乘车人姓名与身份证号(输出已脱敏)等核心字段。
+ *
+ *
+ * 百度官方文档
+ *
+ * - 产品文档:火车票识别
+ * - 接口地址:{@code POST https://aip.baidubce.com/rest/2.0/ocr/v1/train_ticket}
+ *
+ *
+ * 本服务接口
+ *
+ * - 路径:{@code POST /train/ticket/recognize}
+ * - Content-Type:{@code application/json}({@code @RequestBody})
+ * - 鉴权:{@link EbAuthentication}(Tencent 鉴权头,见项目网关配置)
+ * - 结果缓存:{@link CacheResult} 命中时不再调用平台,节省计费
+ *
+ *
+ * 请求参数({@link TrainTicketRecognizeRequest})
+ *
+ * - imageUrlOrBase64:HTTP(S) 链接或 Base64 字符串,服务端自动识别(支持 jpg/jpeg/png/bmp)
+ *
+ *
+ * 响应结构({@link RecognizeTrainTicketResp})
+ *
+ * - 核心:date 日期 / time 时间 / departureStation 始发 / destination 终到 / number 车次
+ * - 票务:level 席别 / seat 座位 / price 票价 / ticketNum 票号 / salesStation 售票站 / serialNumber 序列号
+ * - 乘车人:name 姓名 / idNum 身份证号(前 6 位 + 后 4 位,中间脱敏)
+ *
+ *
+ * 返回约定(重要)
+ *
+ * - 一律 {@code R.ok()};入参校验失败、平台异常、识别结果为空等情况,将说明写入 {@code msg},
+ * {@code data} 可能为空或字段不全(对外不暴露「百度」字样,日志内可排查)
+ * - 入参校验失败时未调用百度、不计费
+ * - 身份证号统一脱敏返回(中间 8 位以 * 替代),原值不进入日志
+ *
+ *
+ * @author heyu
+ * @since 1.0.0
+ * @see TrainTicketRecognizeRequest
+ * @see RecognizeTrainTicketResp
+ */
+@Slf4j
@RestController
@RequestMapping("/train/ticket")
@NotIntercept
-public class RecognizeTrainTicketController extends BaseController {
+public class RecognizeTrainTicketController extends AbstractRecognizeController {
- @Autowired
- private BTrainTicketHandle bTrainTicketHandle;
+ /** 百度火车票识别 API 路径 */
+ private static final String TRAIN_TICKET_URI = "/rest/2.0/ocr/v1/train_ticket";
- @RequestMapping("/recognize")
+ /** 火车票识别无 side 概念,统一占位以复用父类上下文 */
+ private static final String SIDE_PLACEHOLDER = "train_ticket";
+
+ /**
+ * 火车票识别
+ *
+ * @param request 火车票识别请求
+ * @return {@link RecognizeTrainTicketResp}
+ */
+ @EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
@CacheResult
- public R recognize(BTrainTicketRequest request) {
- return BaiduOcrResult.raw(bTrainTicketHandle.handle(request));
+ @PostMapping("/recognize")
+ public R recognize(@RequestBody TrainTicketRecognizeRequest request) {
+ long start = System.currentTimeMillis();
+ RecognizeContext ctx = null;
+ try {
+ // ---------- 步骤①:参数校验(不调百度、不扣费) ----------
+ String validateError = validateRequest(request);
+ if (validateError != null) {
+ ResolvedImageInput validateImage = request != null
+ ? ImageInputUtils.resolve(request.getImageUrlOrBase64()) : null;
+ log.info("火车票识别:参数检查没通过,接口仍返回成功并附带提示(还没调识别、不扣费)。{} 返回给客户:{}",
+ buildInputLogContext(request, validateImage),
+ abbreviate(validateError, 120));
+ return okResult(SIDE_PLACEHOLDER, validateError, null);
+ }
+
+ // ---------- 步骤②:解析影像入参 & 构建上下文 ----------
+ ResolvedImageInput imageInput = ImageInputUtils.resolve(request.getImageUrlOrBase64());
+ ctx = new RecognizeContext(
+ SIDE_PLACEHOLDER,
+ imageInput,
+ buildInputLogContext(request, imageInput));
+
+ // ---------- 步骤③:组装百度 API 请求体 ----------
+ String content = buildRequestContent(imageInput);
+ if (isBlank(content)) {
+ log.error("火车票识别:组装请求失败,请求里没带有效图片。{}。{}",
+ ctx.imageInput.getType().getDesc(), ctx.inputLog);
+ return okResult(SIDE_PLACEHOLDER, formatHint(
+ "报文组装异常",
+ "识别请求体在序列化后未包含任何影像载荷,平台侧无法受理本次识别",
+ "请核对 imageUrlOrBase64 是否非空且为有效 Base64 或 HTTP(S) 链接",
+ "影像模式=" + ctx.imageInput.getType().name()
+ ), null);
+ }
+
+ // ---------- 步骤④:调用百度平台识别 ----------
+ Map platformResult = callPlatform(content, ctx);
+ if (platformResult == null) {
+ return okResult(SIDE_PLACEHOLDER, formatHint(
+ "服务无回执",
+ "识别指令已下发,但在约定时间内未收到平台可解析的 JSON 回执",
+ "建议间隔 3~5 秒重试;若连续失败,请记录 traceId、调用时刻并联系技术支持排查链路",
+ "影像模式=" + ctx.imageInput.getType().name()
+ ), null);
+ }
+
+ // ---------- 步骤⑤:解析平台结果 → 构建车票响应(含 PII 脱敏) ----------
+ RecognizeTrainTicketResp data = buildResp(platformResult);
+ String hint = resolvePlatformHint(platformResult, ctx, data);
+
+ // ---------- 步骤⑥:日志记录 & 返回 ----------
+ logRecognizeResult(ctx, platformResult, data, hint, start);
+ return okResult(SIDE_PLACEHOLDER, hint, data);
+
+ } catch (Exception e) {
+ long cost = System.currentTimeMillis() - start;
+ ResolvedImageInput fallbackImage = request != null
+ ? ImageInputUtils.resolve(request.getImageUrlOrBase64()) : null;
+ String mode = ctx != null ? ctx.imageInput.getType().getDesc()
+ : (fallbackImage != null ? fallbackImage.getType().getDesc() : "未知");
+ log.error("火车票识别:程序运行出错,耗时 {} ms。{}。客户传的:{}。异常:{} - {}",
+ cost, mode, buildInputLogContext(request, fallbackImage),
+ e.getClass().getSimpleName(),
+ e.getMessage() != null ? e.getMessage() : "无具体说明", e);
+ return okResult(SIDE_PLACEHOLDER, formatHint(
+ "运行时故障",
+ "服务端在处理识别流程时抛出未预期异常,识别结果不可用",
+ "请勿重复高频重试;请保存 traceId、异常发生时间,由技术支持结合堆栈进一步定位",
+ "异常类型=" + e.getClass().getSimpleName()
+ + (e.getMessage() != null ? ",摘要=" + e.getMessage() : "")
+ ), null);
+ }
+ }
+
+ /**
+ * 子类钩子:火车票识别只有一种响应类型,无 side 区分。
+ */
+ @Override
+ protected Object defaultEmptyResp(String side) {
+ return new RecognizeTrainTicketResp();
+ }
+
+ // ===================== 流程拆分方法 =====================
+
+ private Map callPlatform(String content, RecognizeContext ctx) {
+ int len = content.length();
+ log.info("火车票识别:开始调用平台识别。{},请求大小约 {} 字节。{}",
+ ctx.imageInput.getType().getDesc(), len, ctx.inputLog);
+ Map result = requestBaidu(TRAIN_TICKET_URI, content);
+ if (result == null) {
+ log.error("火车票识别:平台没有返回结果(可能网络超时、鉴权失败或响应无法解析)。{},请求约 {} 字节。{}",
+ ctx.imageInput.getType().getDesc(), len, ctx.inputLog);
+ }
+ return result;
+ }
+
+ /**
+ * 解析平台返回结果,判断是否需要向客户返回提示信息。
+ * 判断顺序:
+ *
+ * - error_code 存在 → 平台业务拒绝
+ * - words_result 缺失或非对象 → 平台未识别出车票
+ * - 响应对象全部 String 字段为空 → 字段映射失败
+ * - 以上均不命中 → 返回 null(识别正常)
+ *
+ */
+ private String resolvePlatformHint(Map platformResult, RecognizeContext ctx,
+ RecognizeTrainTicketResp data) {
+ Object errorCodeObj = platformResult.get("error_code");
+ if (errorCodeObj != null) {
+ String errorCode = String.valueOf(errorCodeObj);
+ Object errorMsgObj = platformResult.get("error_msg");
+ String errorMsg = errorMsgObj != null ? errorMsgObj.toString() : "平台未返回文字描述";
+ String category = BaiduOcrError.categoryOf(errorCode);
+ log.error("火车票识别:平台拒绝了本次识别。[{}] 错误码 {},原因:{}。{}。客户传的:{}。{}",
+ category, errorCode, errorMsg,
+ ctx.imageInput.getType().getDesc(),
+ ctx.inputLog, buildPlatformReceiptSummary(platformResult));
+ return formatHint(
+ category,
+ BaiduOcrError.reasonOf(errorCode),
+ BaiduOcrError.suggestionOf(errorCode, "火车票"),
+ "错误码=" + errorCode + ",错误描述=" + errorMsg
+ );
+ }
+ Object wordsResult = platformResult.get("words_result");
+ if (!(wordsResult instanceof Map) || ((Map, ?>) wordsResult).isEmpty()) {
+ log.info("火车票识别:平台返回了,但 words_result 缺失或为空对象(可能不是火车票、画面残缺或非证件照)。{}。客户传的:{}。{}",
+ ctx.imageInput.getType().getDesc(),
+ ctx.inputLog, buildPlatformReceiptSummary(platformResult));
+ return formatHint(
+ "结构化结果缺失",
+ "平台回执未包含可解析的火车票识别结果,无法进入字段映射环节",
+ "优先排查:① 画面是否为完整火车票(含车次/座位等核心信息);② 是否模糊 / 反光 / 过度倾斜;"
+ + "③ 使用 URL 时确保平台抓取节点可访问且无 403/302 拦截",
+ "解析状态=结果对象为空"
+ );
+ }
+ if (isAllStringFieldsBlank(data)) {
+ log.info("火车票识别:平台有返回,但车次/始发/终到/姓名等字段一个都没识别出来。{}。客户传的:{}。平台回执:{}",
+ ctx.imageInput.getType().getDesc(),
+ ctx.inputLog, buildPlatformReceiptSummary(platformResult));
+ return formatHint(
+ "字段映射为空",
+ "平台回执已通过结构校验,但车次、始发/终到、座位等结构化字段均未命中",
+ "请确认上传图片为正面完整的火车票照片;推荐:白底拍摄、避开反光、保证四角与文字清晰",
+ "已映射字段数=0"
+ );
+ }
+ return null;
+ }
+
+ private void logRecognizeResult(RecognizeContext ctx, Map platformResult,
+ Object data, String hint, long start) {
+ long cost = System.currentTimeMillis() - start;
+ int mapped = countNonBlankStringFields(data);
+ if (StringUtils.isNotBlank(hint)) {
+ log.info("火车票识别:处理结束(接口仍返回成功,但带了提示信息)。耗时 {} ms,识别出 {} 个字段。{}。客户传的:{}。平台回执:{}。给客户的提示:{}",
+ cost, mapped, ctx.imageInput.getType().getDesc(),
+ ctx.inputLog, buildPlatformReceiptSummary(platformResult), abbreviate(hint, 120));
+ } else {
+ log.info("火车票识别:识别成功。耗时 {} ms,共识别出 {} 个字段。{}。客户传的:{}。平台回执:{}",
+ cost, mapped, ctx.imageInput.getType().getDesc(),
+ ctx.inputLog, buildPlatformReceiptSummary(platformResult));
+ }
+ }
+
+ // ===================== 校验 =====================
+
+ private String validateRequest(TrainTicketRecognizeRequest request) {
+ if (request == null) {
+ log.info("火车票识别:没收到任何请求参数(表单未绑定成功),直接拒绝,未调用识别、不扣费");
+ return formatHint(
+ "入参绑定失败",
+ "控制器未接收到可绑定的请求对象,所有业务字段均为空",
+ "请确认使用 POST 提交;Content-Type 为 application/json;字段名为 imageUrlOrBase64",
+ "绑定结果=TrainTicketRecognizeRequest 为 null"
+ );
+ }
+ ImageInputUtils.ValidationResult imageValidation =
+ ImageInputUtils.validate(request.getImageUrlOrBase64());
+ if (!imageValidation.isValid()) {
+ log.info("火车票识别:imageUrlOrBase64 校验未通过({}),未调用识别、不扣费。原因:{}。{}",
+ imageValidation.getCategory(),
+ imageValidation.getReason(),
+ buildInputLogContext(request, null));
+ return formatHint(
+ imageValidation.getCategory(),
+ imageValidation.getReason(),
+ imageValidation.getSuggestion(),
+ imageValidation.getDetail()
+ );
+ }
+ return null;
+ }
+
+ // ===================== 请求 / 响应构造 =====================
+
+ private String buildRequestContent(ResolvedImageInput imageInput) {
+ StringBuilder sb = new StringBuilder();
+ if (imageInput == null || StringUtils.isBlank(imageInput.getFormValue())) {
+ return sb.toString();
+ }
+ if (ImageInputUtils.ImageInputType.URL == imageInput.getType()) {
+ sb.append("&url=").append(imageInput.getFormValue());
+ } else {
+ sb.append("&image=").append(imageInput.getFormValue());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 将百度返回的 words_result(单对象)映射为对外响应。
+ * 身份证号在此步骤完成脱敏(仅保留前 6 位与后 4 位,中间用 * 替代)。
+ */
+ private RecognizeTrainTicketResp buildResp(Map platformResult) {
+ RecognizeTrainTicketResp resp = new RecognizeTrainTicketResp();
+ resp.setDate(getWord(platformResult, "date"));
+ resp.setTime(getWord(platformResult, "time"));
+ resp.setDepartureStation(firstNonBlank(
+ getWord(platformResult, "starting_station"),
+ getWord(platformResult, "sales_station")));
+ resp.setDestination(getWord(platformResult, "destination_station"));
+ resp.setNumber(getWord(platformResult, "train_num"));
+ resp.setLevel(getWord(platformResult, "seat_category"));
+ resp.setSeat(getWord(platformResult, "seat_num"));
+ resp.setName(getWord(platformResult, "name"));
+ resp.setIdNum(maskIdNumber(getWord(platformResult, "id_num")));
+ resp.setPrice(parsePrice(getWord(platformResult, "ticket_rates")));
+ resp.setTicketNum(getWord(platformResult, "ticket_num"));
+ resp.setSalesStation(getWord(platformResult, "sales_station"));
+ resp.setSerialNumber(getWord(platformResult, "serial_number"));
+ return resp;
+ }
+
+ /**
+ * 从 words_result 对象中取指定字段(百度返回的是 string,无需 .words 包裹)。
+ */
+ private String getWord(Map platformResult, String field) {
+ return MapUtils.getByExpr(platformResult, "words_result." + field);
+ }
+
+ /**
+ * 把百度返回的 "ticket_rates"(字符串如 "104.5元" / "104.5")解析为 Float。
+ * 解析失败返回 null(保持响应字段不混入脏数据)。
+ */
+ private Float parsePrice(String rawPrice) {
+ if (StringUtils.isBlank(rawPrice)) {
+ return null;
+ }
+ String cleaned = rawPrice.replaceAll("[^0-9.]", "").trim();
+ if (cleaned.isEmpty()) {
+ return null;
+ }
+ try {
+ return Float.parseFloat(cleaned);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 对身份证号脱敏:保留前 6 位 + 后 4 位,中间统一用 * 替代。
+ * 长度不足 10 位时整体置为 ****,避免反推。
+ */
+ private String maskIdNumber(String raw) {
+ if (StringUtils.isBlank(raw)) {
+ return null;
+ }
+ String trim = raw.trim();
+ if (trim.length() < 10) {
+ return "****";
+ }
+ int total = trim.length();
+ String head = trim.substring(0, 6);
+ String tail = trim.substring(total - 4);
+ StringBuilder middle = new StringBuilder();
+ for (int i = 0; i < total - 10; i++) {
+ middle.append('*');
+ }
+ return head + middle + tail;
+ }
+
+ // ===================== 私有工具 =====================
+
+ private String formatHint(String category, String reason, String suggestion, String detail) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("【火车票识别·").append(category).append("】").append(reason);
+ if (StringUtils.isNotBlank(detail)) {
+ sb.append("|定位信息:").append(detail);
+ }
+ sb.append("|处置指引:").append(suggestion);
+ return sb.toString();
+ }
+
+ /** 入参日志说明(不打印影像正文,只说类型与长度,禁止泄露 PII) */
+ private String buildInputLogContext(TrainTicketRecognizeRequest request, ResolvedImageInput imageInput) {
+ if (request == null) {
+ return "未收到请求体";
+ }
+ String modeDesc = imageInput != null ? imageInput.getType().getDesc() : "影像未解析";
+ int rawLen = imageInput != null
+ ? textLength(imageInput.getRawValue())
+ : textLength(request.getImageUrlOrBase64());
+ return String.format("%s,影像原始长度 %d 字符", modeDesc, rawLen);
}
}
diff --git a/api-web/api-interface/src/main/java/com/heyu/api/request/car/TrainTicketRecognizeRequest.java b/api-web/api-interface/src/main/java/com/heyu/api/request/car/TrainTicketRecognizeRequest.java
index 7dc25bf..4438c59 100644
--- a/api-web/api-interface/src/main/java/com/heyu/api/request/car/TrainTicketRecognizeRequest.java
+++ b/api-web/api-interface/src/main/java/com/heyu/api/request/car/TrainTicketRecognizeRequest.java
@@ -3,25 +3,23 @@ package com.heyu.api.request.car;
import lombok.Data;
/**
- * 火车票识别请求参数(百度OCR)
+ * 火车票识别请求参数。
*
- * 支持识别各类火车票的全部字段,包括车票号码、乘车日期时间、出发站、到达站、车次号、席别、座位号、
+ * 对外提供简洁接口,支持识别各类火车票的全部字段,包括车票号码、乘车日期时间、出发站、到达站、车次号、席别、座位号、
* 乘车人姓名、票价等信息。
*/
@Data
public class TrainTicketRecognizeRequest {
/**
- * 图像数据,base64编码后进行urlencode,要求base64编码和urlencode后大小不超过4M
- * 支持jpg/jpeg/png/bmp格式
- * 和url二选一
+ * 影像入参(URL 或 Base64 二合一):
+ *
+ * - HTTP/HTTPS 图片链接(≤1024 字符,未 urlencode 时由服务端自动编码)
+ * - 图片 Base64 字符串(支持 jpg/jpeg/png/bmp;可带 data:image/...;base64, 前缀;
+ * 编码后 urlencode 前建议≤4M,未 urlencode 时由服务端自动编码)
+ *
+ * 服务端根据内容自动识别为链接或 Base64。
*/
- private String imageBase64;
-
- /**
- * 图片完整URL,URL长度不超过1024字节
- * 和imageBase64二选一
- */
- private String imageUrl;
+ private String imageUrlOrBase64;
}
diff --git a/api-web/api-interface/src/main/java/com/heyu/api/resp/car/RecognizeTrainTicketResp.java b/api-web/api-interface/src/main/java/com/heyu/api/resp/car/RecognizeTrainTicketResp.java
index 0ec3b6d..d9acfc8 100644
--- a/api-web/api-interface/src/main/java/com/heyu/api/resp/car/RecognizeTrainTicketResp.java
+++ b/api-web/api-interface/src/main/java/com/heyu/api/resp/car/RecognizeTrainTicketResp.java
@@ -1,80 +1,92 @@
package com.heyu.api.resp.car;
-
import com.heyu.api.data.dto.BaseResp;
import lombok.Data;
/**
- * 火车票识别响应
+ * 火车票识别响应。
*
- * 百度OCR文档:https://console.bce.baidu.com/support/#/api?product=AI&project=文字识别&parent=财务票据OCR&api=rest%2F2.0%2Focr%2Fv1%2Ftrain_ticket&method=post
+ * 字段对齐阿里云 RecognizeTrainTicket 规范,覆盖纸质 / 电子 / 蓝色 / 红色等常见车票版式。
*/
@Data
public class RecognizeTrainTicketResp extends BaseResp {
-
/**
- * 乘车日期时间。
- *
- * 示例值:
- * 2017年08月05日22:09开
+ * 乘车日期。
+ * 示例值:2017年08月05日
*/
public String date;
- /***
- * 始发站点。
- *
- * 示例值:
- * 苏州站
+ /**
+ * 开车时间(含"开"字尾缀)。
+ * 示例值:22:09开
+ */
+ public String time;
+
+ /**
+ * 始发站。
+ * 示例值:苏州站
*/
public String departureStation;
- /***
- * 目的站点。
- *
- * 示例值:
- * 南京南站
+ /**
+ * 目的站。
+ * 示例值:南京南站
*/
public String destination;
+ /**
+ * 车次号。
+ * 示例值:G7350
+ */
+ public String number;
+
/**
* 座位席别。
- *
- * 示例值:
- * 二等座
+ * 示例值:二等座
*/
public String level;
+ /**
+ * 座位车厢及座次号。
+ * 示例值:04车13A号
+ */
+ public String seat;
+
/**
* 乘车人姓名。
- *
- * 示例值:
- * 帅帅
+ * 示例值:帅帅
*/
public String name;
/**
- * 车次号。
- *
- * 示例值:
- * G7350
+ * 乘车人身份证号(已脱敏,仅保留前 6 位 + 后 4 位)。
+ * 示例值:320106********0024
*/
- public String number;
+ public String idNum;
- /***
+ /**
* 票价。
- *
- * 示例值:
- * 104.5
+ * 示例值:104.5
*/
public Float price;
- /***
- * 座位车厢及座次号。
- *
- * 示例值:
- * 04车13A号
+ /**
+ * 票号。
+ * 示例值:Y123456
*/
- public String seat;
+ public String ticketNum;
+
+ /**
+ * 售票站。
+ * 示例值:苏州站
+ */
+ public String salesStation;
+
+ /**
+ * 序列号。
+ * 示例值:08051022090107L
+ */
+ public String serialNumber;
}