package jnpf.base.model.ocr.model; import com.alibaba.fastjson.JSONObject; import io.swagger.v3.oas.annotations.media.Schema; import jnpf.base.model.ocr.OcrModel; import jnpf.util.DateUtil; import jnpf.util.StringUtil; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * ocr解析火车票模型 * * @author JNPF开发平台组 * @version v6.1.0 * @copyright 引迈信息技术有限公司 * @date 2025/7/29 16:58:47 */ @Slf4j @EqualsAndHashCode(callSuper = true) @Data @Schema(description = "ocr解析火车票模型") @NoArgsConstructor public class TrainTicketModel extends OcrModel { @Schema(description = "票号") private String ticketNum; @Schema(description = "出发地") private String startingStation; @Schema(description = "目的地") private String destinationStation; @Schema(description = "车次") private String trainNum; @Schema(description = "发车日期") private Long dateTime; @Schema(description = "票价") private String ticketRates; @Schema(description = "售票地点") private String ticketLocation; @Schema(description = "序列号") private String serialNum; @Schema(description = "姓名") private String name; @Schema(description = "身份证号") private String idNum; @Schema(description = "座位号") private String seat; @Schema(description = "座位类型") private String seatCategory; @Schema(description = "检票口") private String ticketGate; @Override public void extract(String url, JSONObject body) { super.extract(url, body); if (StringUtil.isEmpty(this.getContent()) || !this.getContent().contains("车")) { return; } this.setInfo(); this.setContent(null); this.setStrList(null); } private void setInfo() { List list = new ArrayList<>(this.getStrList()); ticketNum = list.get(0).matches("(?:[A-Za-z]*\\d+[A-Za-z]*\\d*|[A-Za-z]+\\d+)") ? list.get(0) : list.get(1); //起始终止站 startingStation(); //序列号 serialNum(); //身份证 idNum(); int index = 0; for (String str : list) { dateTime(str); ticketRates(str); ticketLocation(str); seat(str); seatCategory(str); ticketGate(str); //用户名称 if (StringUtil.isNotEmpty(idNum) && str.contains(idNum)) { if (!str.equals(idNum)) { if (isAllChinese(str.substring(idNum.length()))) { name = str.substring(idNum.length()); } } else if (index + 1 < list.size()) { if (isAllChinese(list.get(index + 1))) { name = list.get(index + 1); } else if (isAllChinese(list.get(index - 1))) { name = list.get(index - 1); } } } index++; } } private void startingStation() { if (StringUtil.isEmpty(startingStation)) { int index = 0; List strList = this.getStrList(); int size = strList.size(); for (String str : strList) { if (isAllChinese(str) && index + 2 < size && isTrainNum(strList.get(index + 1)) && (isAllChinese(strList.get(index + 2)) || isAllCaracter(strList.get(index + 2)))) { startingStation = str.equals("站") ? strList.get(index - 1) + str : str; trainNum = strList.get(index + 1); destinationStation = strList.get(index + 2); if (isAllCaracter(strList.get(index + 2))) { String[] split = strList.get(index + 1).split("次"); trainNum = split.length > 0 ? split[0] : null; destinationStation = split.length > 1 ? split[1] : null; } break; } index++; } //当顺序不正常,做一下补充填写 int firstPY = 0; for (int i = 0; i < strList.size(); i++) { if (isAllCaracter(strList.get(i))) { firstPY = i; break; } } if (StringUtil.isEmpty(destinationStation) && firstPY > 0) { for (String s : strList.subList(firstPY - 3, firstPY)) { if (containsNumber(s)) { trainNum = s; } else { // 第一个非数字字符串作为出发站,第二个作为目的站 if (StringUtil.isEmpty(startingStation)) { startingStation = s; } else { destinationStation = s; } } } } } } private void dateTime(String str) { if (dateTime == null && str.matches("\\d{4}年\\d{1,2}月\\d{1,2}日\\d{1,2}[::]\\d{1,2}")) { try { String[] split = str.split("年"); String year = split[0]; String[] split2 = split[1].split("月"); String month = String.format("%02d", Integer.parseInt(split2[0])); String[] split3 = split2[1].split("日"); String day = String.format("%02d", Integer.parseInt(split3[0])); String[] split4 = split3[1].contains(":") ? split3[1].split(":") : split3[1].split(":"); String hh = String.format("%02d", Integer.parseInt(split4[0])); String mmStr = split4[1].length() > 2 || split4[1].contains("开") ? split4[1].split("开")[0] : split4[1]; String mm = String.format("%02d", Integer.parseInt(mmStr)); String bd = year + "-" + month + "-" + day + " " + hh + ":" + mm; dateTime = DateUtil.checkDate(bd, "yyyy-MM-dd HH:mm").getTime(); } catch (Exception e) { log.error("火车票解析错误:" + e.getMessage()); } } } private void ticketRates(String str) { if (StringUtil.isEmpty(ticketRates) && StringUtil.isNotEmpty(str) && str.matches("[¥¥#]?\\d+(?:\\.\\d+)?")) { ticketRates = str.replaceAll("[¥¥#元]", ""); } } private void ticketLocation(String str) { if (StringUtil.isEmpty(ticketLocation) && str.endsWith("售")) { ticketLocation = str.substring(0, str.length() - 1); } } private void serialNum() { if (StringUtil.isEmpty(serialNum)) { List strList = this.getStrList(); int size = strList.size(); for (int i = size - 1; i >= 0; i--) { if (isNumAndCarater(strList.get(i)) && strList.get(i).length() > 4) { serialNum = strList.get(i); return; } } } } private void seat(String str) { if (StringUtil.isEmpty(seat) && str.matches("\\d{1,2}车(?:\\d{1,3}[A-Z]?号?|无座)(?:下铺|上铺|中铺|硬座|硬卧|软卧)?")) { seat = str; } } private void seatCategory(String str) { String regex = "(?:新空调)?(?:软卧|硬卧|软座|硬座|商务座|一等座|二等座|特等座|动卧|一等卧|二等卧|高级动卧|无座|站票)(?:普快)?"; Matcher matcher = Pattern.compile(regex).matcher(str); if (StringUtil.isEmpty(seatCategory) || matcher.matches()) { seatCategory = str; } } private void idNum() { if (StringUtil.isEmpty(idNum)) { String regex = "[1-9]\\d{5}(?:\\d{4}|\\*{4})(?:\\d{4}|\\*{4})\\d{3}[0-9Xx]"; Matcher matcher = Pattern.compile(regex).matcher(this.getContent()); if (matcher.find()) { idNum = matcher.group(); } } } private void ticketGate(String str) { if (StringUtil.isEmpty(ticketGate) && (str.contains("检票") || str.contains("候车"))) { ticketGate = str; } } private boolean isAllChinese(String str) { return str.matches("[一-龥]+"); } private boolean isAllCaracter(String str) { return str.matches("[a-zA-Z]+"); } private boolean isNumAndCarater(String str) { return str.matches("[0-9a-zA-Z]+"); } private boolean isTrainNum(String str) { return str.matches("([GCDZTK]?\\d{1,4})(次)?(?:\\s*(\\S+))?"); } public static boolean containsNumber(String str) { Pattern pattern = Pattern.compile("\\d"); Matcher matcher = pattern.matcher(str); return matcher.find(); } }