├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── application.properties │ │ └── train │ │ │ └── TrainHotStation.json │ └── java │ │ └── com │ │ └── sinosun │ │ └── train │ │ ├── model │ │ ├── request │ │ │ ├── NoneRequest.java │ │ │ ├── SearchCityRequest.java │ │ │ ├── GetTrainStationTimeTableRequest.java │ │ │ ├── GetTrainLineRequest.java │ │ │ └── GetTicketListRequest.java │ │ ├── response │ │ │ ├── StationResult.java │ │ │ ├── TrainLineResult.java │ │ │ ├── TicketListResult.java │ │ │ ├── TicketList.java │ │ │ ├── StationList.java │ │ │ ├── BaseResult.java │ │ │ ├── TrainCodeResult.java │ │ │ ├── TrainStationTimeTableResult.java │ │ │ ├── Station.java │ │ │ ├── Stop.java │ │ │ ├── TrainLine.java │ │ │ ├── TrainStationTimeTable.java │ │ │ └── Ticket.java │ │ └── vo │ │ │ └── TicketPrice.java │ │ ├── StarterApplication.java │ │ ├── constants │ │ ├── RedisKeyConstant.java │ │ ├── FileNameConstant.java │ │ └── UrlConstant.java │ │ ├── enums │ │ ├── train │ │ │ └── PassengerType.java │ │ ├── PlatformErrorCode.java │ │ └── BusinessErrorCode.java │ │ ├── controller │ │ ├── TestController.java │ │ └── TrainController.java │ │ ├── datamap │ │ ├── TrainCodeTrainNoMap.java │ │ └── SeatTypeMap.java │ │ ├── config │ │ └── ControllerAspect.java │ │ ├── exception │ │ ├── ControllerExceptionHandleAdvice.java │ │ └── ServiceException.java │ │ ├── utils │ │ ├── HttpUtil.java │ │ ├── PreloadData.java │ │ ├── JsonUtil.java │ │ ├── TrainHelper.java │ │ └── RedisUtils.java │ │ └── service │ │ ├── TrainStationService.java │ │ ├── TrainStationTimeTableService.java │ │ └── TrainTicketService.java └── test │ └── java │ └── com │ └── sinosun │ └── train │ ├── StarterApplicationTests.java │ ├── vo │ ├── Extension.java │ ├── Head.java │ └── GetTicketPriceReq.java │ ├── CtripTrainTest.java │ ├── TrainRestTest.java │ └── TrainTest.java ├── .gitignore ├── gradlew.bat ├── gradlew └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'train' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jieniyimiao/12306-rest-api/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jieniyimiao/12306-rest-api/HEAD/src/main/resources/application.properties -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/request/NoneRequest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.request; 2 | 3 | /** 4 | * Created on 2019/1/10 20:57. 5 | * 6 | * @author caogu 7 | */ 8 | public class NoneRequest { 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 20 13:32:28 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | /out/ 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/StarterApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class StarterApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/StarterApplication.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Created on 2019/1/10 20:37. 8 | * 9 | * @author caogu 10 | */ 11 | @SpringBootApplication 12 | public class StarterApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(StarterApplication.class, args); 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/constants/RedisKeyConstant.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.constants; 2 | 3 | /** 4 | * Created on 2019-03-11 5 | * 6 | * @author 猎隼丶止戈 7 | */ 8 | public class RedisKeyConstant { 9 | 10 | /** 11 | * @{value} 火车站 key 12 | */ 13 | public static final String REDIS_KEY_LOCAL_DATA_STATION = "LOCAL_DATA:STATION"; 14 | 15 | /** 16 | * @{value} 热门火车站 key 17 | */ 18 | public static final String REDIS_KEY_LOCAL_DATA_HOT_STATION = "LOCAL_DATA:HOT_STATION"; 19 | 20 | /** 21 | * @{value} 车次 - 列车号关联 key 22 | */ 23 | public static final String REDIS_KEY_LOCAL_DATA_TRAIN_NO_LINK = "LOCAL_DATA:TRAIN_NO_LINK"; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/constants/FileNameConstant.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.constants; 2 | 3 | /** 4 | * Created on 2019/1/14 10:29. 5 | * 6 | * @author caogu 7 | */ 8 | public class FileNameConstant { 9 | /** 10 | * {@value} 火车站点本地全量数据json文件名 11 | */ 12 | public static final String TRAIN_ALL_STATION_LOCAL_FILE_NAME = "TrainAllStation.json"; 13 | 14 | /** 15 | * {@value} 火车站点本地热点(喜爱)数据json文件名 16 | */ 17 | public static final String TRAIN_HOT_STATION_LOCAL_FILE_NAME = "TrainHotStation.json"; 18 | 19 | /** 20 | * {@value} 火车车次数据json文件名 21 | */ 22 | public static final String TRAIN_ALL_CODE_LOCAL_FILE_NAME = "TrainAllCode.json"; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/constants/UrlConstant.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.constants; 2 | 3 | /** 4 | * Created on 2019/1/16 13:48. 5 | * 6 | * @author caogu 7 | */ 8 | public class UrlConstant { 9 | /** 10 | * {@value}12306获取火车站站点列表URL 11 | */ 12 | public static final String TRAIN_ALL_STATION_URL = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js"; 13 | 14 | 15 | /** 16 | * {@value}12306获取热点火车站点列表URL 17 | */ 18 | public static final String TRAIN_HOT_STATION_URL = "https://kyfw.12306.cn/otn/resources/js/framework/favorite_name.js"; 19 | 20 | /** 21 | * {@value}12306获取所有车次列表URL 22 | */ 23 | public static final String TRAIN_ALL_CODE_LIST_URL = "https://kyfw.12306.cn/otn/resources/js/query/train_list.js"; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/enums/train/PassengerType.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.enums.train; 2 | 3 | /** 4 | * Created on 2019/1/20 12:21. 5 | * 乘客类型 6 | * 7 | * @author caogu 8 | */ 9 | public enum PassengerType { 10 | /** 11 | * 成人:ADULT 12 | */ 13 | ADULT("ADULT"), 14 | 15 | /** 16 | * 学生:0X00 17 | */ 18 | STUDENT("0X00"); 19 | 20 | private final String value; 21 | 22 | PassengerType(String value) { 23 | this.value = value; 24 | } 25 | 26 | public String value() { 27 | return value; 28 | } 29 | 30 | public static PassengerType fromValue(String v) { 31 | for (PassengerType c : PassengerType.values()) 32 | if (c.value.equals(v)) { 33 | return c; 34 | } 35 | throw new IllegalArgumentException(v); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/enums/PlatformErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.enums; 2 | 3 | /** 4 | * Created on 2019/1/14 12:10. 5 | * 6 | * @author caogu 7 | */ 8 | public enum PlatformErrorCode { 9 | SERVICE_INTERNAL_ERROR(1, "服务器内部错误") 10 | ; 11 | private static final int BASE_CODE_VALUE = 10000; 12 | private static final String BASE_CODE_PREFIX = "P"; 13 | private String code; 14 | private String message; 15 | 16 | PlatformErrorCode(int code, String message) { 17 | this.code = buildCode(code); 18 | this.message = message; 19 | } 20 | 21 | private String buildCode(int code) { 22 | int codeValue = BASE_CODE_VALUE + code; 23 | return BASE_CODE_PREFIX + codeValue; 24 | } 25 | 26 | public String getCode() { 27 | return code; 28 | } 29 | 30 | public String getMessage() { 31 | return message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/enums/BusinessErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.enums; 2 | 3 | /** 4 | * Created on 2019/1/14 12:10. 5 | * 6 | * @author caogu 7 | */ 8 | public enum BusinessErrorCode { 9 | REQUEST_PARAM_ERROR(1, "请求参数错误"), 10 | REQUEST_PARAM_MISS(2, "请求参数缺失") 11 | ; 12 | private static final int BASE_CODE_VALUE = 10000; 13 | private static final String BASE_CODE_PREFIX = "T"; 14 | private String code; 15 | private String message; 16 | 17 | BusinessErrorCode(int code, String message) { 18 | this.code = buildCode(code); 19 | this.message = message; 20 | } 21 | 22 | private String buildCode(int code) { 23 | int codeValue = BASE_CODE_VALUE + code; 24 | return BASE_CODE_PREFIX + codeValue; 25 | } 26 | 27 | public String getCode() { 28 | return code; 29 | } 30 | 31 | public String getMessage() { 32 | return message; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * Created by caogu on 2018/12/20 13:34. 12 | * Copyright © 2018.Sinosun All rights reserved 13 | */ 14 | @RestController 15 | @RequestMapping(value = "/test") 16 | public class TestController { 17 | private final static Logger logger = LoggerFactory.getLogger(TestController.class); 18 | 19 | @RequestMapping(method = RequestMethod.GET, value = "/test") 20 | public String test() { 21 | JSONObject ret = new JSONObject(); 22 | ret.put("data", "hello world"); 23 | return JSONObject.toJSONString(ret); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/StationResult.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * Created on 2019/1/10 20:37. 9 | * 10 | * @author caogu 11 | */ 12 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 13 | public class StationResult extends BaseResult { 14 | private StationList result; 15 | 16 | public StationResult() { 17 | } 18 | 19 | public StationResult(StationList result) { 20 | this.result = result; 21 | } 22 | 23 | public StationList getResult() { 24 | return result; 25 | } 26 | 27 | public void setResult(StationList result) { 28 | this.result = result; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return MoreObjects.toStringHelper(this) 34 | .add("result", result) 35 | .toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TrainLineResult.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * Created on 2019/1/10 20:37. 9 | * 10 | * @author caogu 11 | */ 12 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 13 | public class TrainLineResult extends BaseResult { 14 | private TrainLine result; 15 | 16 | public TrainLineResult() { 17 | } 18 | 19 | public TrainLineResult(TrainLine result) { 20 | this.result = result; 21 | } 22 | 23 | public TrainLine getResult() { 24 | return result; 25 | } 26 | 27 | public void setResult(TrainLine result) { 28 | this.result = result; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return MoreObjects.toStringHelper(this) 34 | .add("result", result) 35 | .toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TicketListResult.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * Created on 2019/1/10 20:37. 9 | * 10 | * @author caogu 11 | */ 12 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 13 | public class TicketListResult extends BaseResult { 14 | private TicketList result; 15 | 16 | public TicketListResult() { 17 | } 18 | 19 | public TicketListResult(TicketList result) { 20 | this.result = result; 21 | } 22 | 23 | public TicketList getResult() { 24 | return result; 25 | } 26 | 27 | public void setResult(TicketList result) { 28 | this.result = result; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return MoreObjects.toStringHelper(this) 34 | .add("result", result) 35 | .toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TicketList.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created on 2019/1/10 20:45. 11 | * 12 | * @author caogu 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class TicketList { 16 | protected List tickets; 17 | 18 | public List getTickets() { 19 | return tickets; 20 | } 21 | 22 | public void setTickets(List tickets) { 23 | this.tickets = tickets; 24 | } 25 | 26 | public TicketList(List tickets) { 27 | this.tickets = tickets; 28 | } 29 | 30 | public TicketList() { 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return MoreObjects.toStringHelper(this) 36 | .add("tickets", tickets) 37 | .toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/StationList.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created on 2019/1/10 20:45. 11 | * 12 | * @author caogu 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class StationList { 16 | protected List stations; 17 | 18 | public StationList() { 19 | } 20 | 21 | public StationList(List stations) { 22 | this.stations = stations; 23 | } 24 | 25 | public List getStations() { 26 | return stations; 27 | } 28 | 29 | public void setStations(List stations) { 30 | this.stations = stations; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return MoreObjects.toStringHelper(this) 36 | .add("stations", stations) 37 | .toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/BaseResult.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * Created on 2019/1/10 20:21. 9 | * 10 | * @author caogu 11 | */ 12 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 13 | public class BaseResult { 14 | private String code = "0"; 15 | 16 | private String message; 17 | 18 | public String getCode() { 19 | return code; 20 | } 21 | 22 | public void setCode(String code) { 23 | this.code = code; 24 | } 25 | 26 | public String getMessage() { 27 | return message; 28 | } 29 | 30 | public void setMessage(String message) { 31 | this.message = message; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return MoreObjects.toStringHelper(this) 37 | .add("code", code) 38 | .add("message", message) 39 | .toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TrainCodeResult.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Created on 2019/03/07 09:52:18 11 | * 12 | * @author 猎隼丶止戈 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class TrainCodeResult extends BaseResult { 16 | private Map result; 17 | 18 | public TrainCodeResult() { 19 | } 20 | 21 | public TrainCodeResult(Map result) { 22 | this.result = result; 23 | } 24 | 25 | public Map getResult() { 26 | return result; 27 | } 28 | 29 | public void setResult(Map result) { 30 | this.result = result; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return MoreObjects.toStringHelper(this) 36 | .add("result", result) 37 | .toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/vo/Extension.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.vo; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | import org.assertj.core.util.Lists; 7 | 8 | import java.util.ArrayList; 9 | 10 | /** 11 | * Created on 2019/7/31 9:41. 12 | * 13 | * @author caogu 14 | */ 15 | @JSONType(naming = PropertyNamingStrategy.CamelCase) 16 | public class Extension { 17 | private String name = "protocal"; 18 | private String value = "https"; 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getValue() { 29 | return value; 30 | } 31 | 32 | public void setValue(String value) { 33 | this.value = value; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return MoreObjects.toStringHelper(this) 39 | .add("name", name) 40 | .add("value", value) 41 | .toString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/datamap/TrainCodeTrainNoMap.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.datamap; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.sinosun.train.utils.PreloadData; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Created on 2019/1/20 17:47. 12 | * 13 | * @author caogu 14 | */ 15 | public class TrainCodeTrainNoMap { 16 | private static Logger logger = LoggerFactory.getLogger(PreloadData.class); 17 | 18 | /** 19 | * 车次号-列车号 映射 对外车次号,内部实用列车号 20 | */ 21 | private static Map TRAIN_CODE_TRAIN_NO_MAP = Maps.newHashMap(); 22 | 23 | /** 24 | * 通过车次号获取列车号 25 | * @param trainCode 车次号 26 | * @return 列车号 27 | */ 28 | public static String getTrainNo(String trainCode) { 29 | return TRAIN_CODE_TRAIN_NO_MAP.get(trainCode); 30 | } 31 | 32 | /** 33 | * 设置车次号-列车号 映射 34 | * @param trainCode 车次号 35 | * @param trainNo 列车号 36 | */ 37 | public static void put(String trainCode, String trainNo) { 38 | TRAIN_CODE_TRAIN_NO_MAP.put(trainCode, trainNo); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TrainStationTimeTableResult.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created on 2019/03/07 09:52:18 11 | * 12 | * @author 猎隼丶止戈 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class TrainStationTimeTableResult extends BaseResult { 16 | private List result; 17 | 18 | public TrainStationTimeTableResult() { 19 | } 20 | 21 | public TrainStationTimeTableResult(List result) { 22 | this.result = result; 23 | } 24 | 25 | public List getResult() { 26 | return result; 27 | } 28 | 29 | public void setResult(List result) { 30 | this.result = result; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return MoreObjects.toStringHelper(this) 36 | .add("result", result) 37 | .toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/request/SearchCityRequest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.request; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | import com.sinosun.train.enums.BusinessErrorCode; 7 | import com.sinosun.train.exception.ServiceException; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | /** 11 | * Created on 2019/1/10 20:57. 12 | * 13 | * @author caogu 14 | */ 15 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 16 | public class SearchCityRequest { 17 | private String keyword; 18 | 19 | public String getKeyword() { 20 | return keyword; 21 | } 22 | 23 | public void setKeyword(String keyword) { 24 | this.keyword = keyword; 25 | } 26 | 27 | public void validate() { 28 | if (StringUtils.isEmpty(keyword)) { 29 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_MISS); 30 | } 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return MoreObjects.toStringHelper(this) 36 | .add("keyword", keyword) 37 | .toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/config/ControllerAspect.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.config; 2 | 3 | import com.google.common.base.Stopwatch; 4 | import com.sinosun.train.model.response.BaseResult; 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.annotation.Pointcut; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Arrays; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * Created on 2019/1/16 8:44. 18 | * 19 | * @author caogu 20 | */ 21 | @Component 22 | @Aspect 23 | public class ControllerAspect { 24 | private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class); 25 | 26 | @Pointcut("execution(public * com.sinosun.train.controller..*.*(..))") 27 | public void allControllerFunction() { 28 | } 29 | 30 | @Around("allControllerFunction()") 31 | public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable { 32 | Stopwatch stopwatch = Stopwatch.createStarted(); 33 | 34 | logger.info("执行Controller方法{}开始,参数:{}", pjp.getSignature().getName(), Arrays.toString(pjp.getArgs())); 35 | BaseResult baseResult = (BaseResult) pjp.proceed(pjp.getArgs()); 36 | logger.info("执行Controller方法{}结束,返回值:{}", pjp.getSignature().getName(), baseResult); 37 | logger.info("执行Controller方法{}耗时:{}(ms).", pjp.getSignature().getName(), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS)); 38 | return baseResult; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/datamap/SeatTypeMap.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.datamap; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.sinosun.train.utils.PreloadData; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Created on 2019/1/20 17:12. 12 | * 13 | * @author caogu 14 | */ 15 | public class SeatTypeMap { 16 | 17 | private static Logger logger = LoggerFactory.getLogger(PreloadData.class); 18 | 19 | /** 20 | * 查询票价时的座位类型和票价代码的映射 21 | */ 22 | private static Map SEAT_TYPE_MAP = getSeatTypeMap(); 23 | 24 | 25 | /** 26 | * 获取我们可读的座位类型与票价对应的座位类型的映射 27 | * A9 商务座/特等座 28 | * M 一等座 29 | * O 二等座 30 | * A6 高级软卧 31 | * A4 软卧/一等卧 32 | * F 动卧 33 | * A3 硬卧/二等卧 34 | * A2 软座 (广州到汉口) 35 | * A1 硬座 36 | * WZ 无座 37 | * @return 可读的座位类型-票价对应的座位类型 38 | */ 39 | public static Map getSeatTypeMap() { 40 | if (SEAT_TYPE_MAP == null) { 41 | Map seatTypeMap = Maps.newHashMap(); 42 | seatTypeMap.put("swz", "A9"); 43 | seatTypeMap.put("ydz", "M"); 44 | seatTypeMap.put("edz", "O"); 45 | seatTypeMap.put("gr", "A6"); 46 | seatTypeMap.put("rw", "A4"); 47 | seatTypeMap.put("dw", "F"); 48 | seatTypeMap.put("yw", "A3"); 49 | seatTypeMap.put("rz", "A2"); 50 | seatTypeMap.put("yz", "A1"); 51 | seatTypeMap.put("wz", "WZ"); 52 | SEAT_TYPE_MAP = seatTypeMap; 53 | } 54 | return SEAT_TYPE_MAP; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/Station.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * Created on 2019/1/10 20:38. 9 | * 10 | * @author caogu 11 | */ 12 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 13 | public class Station { 14 | private String name; 15 | 16 | private String stationCode; 17 | 18 | private String pingYin; 19 | 20 | private String pingYinShort; 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public String getStationCode() { 31 | return stationCode; 32 | } 33 | 34 | public void setStationCode(String stationCode) { 35 | this.stationCode = stationCode; 36 | } 37 | 38 | public String getPingYin() { 39 | return pingYin; 40 | } 41 | 42 | public void setPingYin(String pingYin) { 43 | this.pingYin = pingYin; 44 | } 45 | 46 | public String getPingYinShort() { 47 | return pingYinShort; 48 | } 49 | 50 | public void setPingYinShort(String pingYinShort) { 51 | this.pingYinShort = pingYinShort; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return MoreObjects.toStringHelper(this) 57 | .add("name", name) 58 | .add("stationCode", stationCode) 59 | .add("pingYin", pingYin) 60 | .add("pingYinShort", pingYinShort) 61 | .toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/exception/ControllerExceptionHandleAdvice.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.exception; 2 | 3 | import com.sinosun.train.enums.PlatformErrorCode; 4 | import com.sinosun.train.model.response.BaseResult; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | /** 15 | * Created on 2019/1/15 19:09. 16 | * 17 | * @author caogu 18 | */ 19 | @RestControllerAdvice 20 | public class ControllerExceptionHandleAdvice { 21 | private final static Logger logger = LoggerFactory.getLogger(ControllerExceptionHandleAdvice.class); 22 | 23 | @ExceptionHandler 24 | public BaseResult handler(HttpServletRequest request, HttpServletResponse response, Exception e) { 25 | logger.error("Restful Http请求发生异常, Path=" + request.getServletPath(), e); 26 | 27 | if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { 28 | logger.info("修改返回状态值,oldStatus={} newStatus={}", response.getStatus(), HttpStatus.BAD_REQUEST.value()); 29 | response.setStatus(HttpStatus.OK.value()); 30 | } 31 | 32 | BaseResult baseResult = new BaseResult(); 33 | if (e instanceof ServiceException) { 34 | ServiceException exception = (ServiceException) e; 35 | baseResult.setCode(exception.getCode()); 36 | baseResult.setMessage(exception.getMessage()); 37 | } else { 38 | baseResult.setCode(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getCode()); 39 | baseResult.setMessage(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getMessage()); 40 | } 41 | 42 | return baseResult; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/CtripTrainTest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.sinosun.train.utils.HttpUtil; 6 | import com.sinosun.train.utils.JsonUtil; 7 | import com.sinosun.train.vo.GetTicketPriceReq; 8 | import org.joda.time.LocalDateTime; 9 | import org.jsoup.Connection; 10 | import org.junit.BeforeClass; 11 | import org.junit.Test; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | /** 17 | * 从携程获取火车票价格数据 18 | * Created on 2019/7/31 8:48. 19 | * @author caogu 20 | */ 21 | public class CtripTrainTest { 22 | private static Logger logger = LoggerFactory.getLogger(CtripTrainTest.class); 23 | private static RestTemplate restTemplate; 24 | 25 | @BeforeClass 26 | public static void startUp() { 27 | restTemplate = new RestTemplate(); 28 | } 29 | 30 | @Test 31 | public void getTrainList() { 32 | 33 | String getTicketPriceUrl = "https://m.ctrip.com/restapi/soa2/14666/json/GetBookingByStationV3?_fxpcqlniredt=09031010110784000474"; 34 | 35 | GetTicketPriceReq getTicketPriceReq = new GetTicketPriceReq(); 36 | getTicketPriceReq.setDepartDate(LocalDateTime.parse("2019-07-31").toDate()); 37 | getTicketPriceReq.setArriveStation("咸阳"); 38 | getTicketPriceReq.setDepartStation("西安"); 39 | getTicketPriceReq.validate(); 40 | 41 | JSONObject req = JSONObject.parseObject(JSONObject.toJSONString(getTicketPriceReq)); 42 | 43 | JSONObject ret = JsonUtil.parseObject(HttpUtil.request(getTicketPriceUrl, Connection.Method.POST, req)); 44 | System.out.println(JSON.toJSONString(ret, true)); 45 | 46 | 47 | JSONObject responseStatus = ret.getJSONObject("ResponseStatus"); 48 | if (!"Success".equals(responseStatus.getString("Ack"))) { 49 | logger.error("请求携程获取火车票价格失败:{}", responseStatus.toJSONString()); 50 | } 51 | 52 | JSONObject responseBody = ret.getJSONObject("ResponseBody"); 53 | System.out.println(JSON.toJSONString(responseBody, true)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/utils/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.utils; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.sinosun.train.enums.PlatformErrorCode; 5 | import com.sinosun.train.exception.ServiceException; 6 | import org.jsoup.Connection; 7 | import org.jsoup.Jsoup; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.http.HttpStatus; 11 | 12 | import java.io.IOException; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * Created on 2019/1/15 19:46. 17 | * 18 | * @author caogu 19 | */ 20 | public class HttpUtil { 21 | private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class); 22 | private static final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(15); 23 | 24 | /** 25 | * 执行HTTP请求

get请求时data传null

26 | * 27 | * @param url url完整地址 28 | * @param method Connection.Method.GET Connection.Method.POST 29 | * @param data 请求参数的JSON对象 30 | * @return HTTP接口返回值 31 | */ 32 | public static String request(String url, Connection.Method method, JSONObject data) { 33 | logger.info("======request url={} data={}", url, data); 34 | String result; 35 | try { 36 | Connection conn = Jsoup.connect(url) 37 | .timeout(TIMEOUT) 38 | .header("Content-Type", "application/json") 39 | .header("Accept", "application/json") 40 | .header("Connection", "close") 41 | .followRedirects(true) 42 | .ignoreContentType(true); 43 | 44 | if (data != null) { 45 | conn.requestBody(data.toJSONString()); 46 | } 47 | Connection.Response response = conn.method(method).execute(); 48 | 49 | int code = response.statusCode(); 50 | if (code == HttpStatus.OK.value() || code == HttpStatus.FOUND.value()) { 51 | result = response.body(); 52 | } else { 53 | logger.error("执行url={}返回非200/302值, statusCode={}", url, response.statusCode()); 54 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR); 55 | } 56 | logger.info("======request code={} result={}", code, result); 57 | } catch (IOException e) { 58 | logger.error("执行" + url + "出错, data=" + data, e); 59 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 60 | } 61 | return result; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/request/GetTrainStationTimeTableRequest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.request; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import com.alibaba.fastjson.annotation.JSONType; 6 | import com.google.common.base.MoreObjects; 7 | import com.sinosun.train.enums.BusinessErrorCode; 8 | import com.sinosun.train.exception.ServiceException; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.joda.time.DateTime; 11 | 12 | import java.util.Date; 13 | 14 | /** 15 | * Created on 2019-03-07 11:08:00 16 | * 17 | * @author 猎隼丶止戈 18 | */ 19 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 20 | public class GetTrainStationTimeTableRequest { 21 | 22 | /** 23 | * 列车站站点名称 24 | */ 25 | private String trainStationName; 26 | 27 | /** 28 | * 列车站站点代码 29 | */ 30 | private String trainStationCode; 31 | 32 | /** 33 | * 出发日期(格式:yyyy-mm-dd) 34 | */ 35 | @JSONField(format = "yyyy-MM-dd") 36 | private Date trainStartDate; 37 | 38 | public void validate() { 39 | if (StringUtils.isBlank(trainStationName) 40 | || StringUtils.isBlank(trainStationCode) 41 | || trainStartDate == null) { 42 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_MISS); 43 | } 44 | 45 | if (new DateTime(trainStartDate).plusDays(1).minusSeconds(1).isBeforeNow()) { 46 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_ERROR, "出发时间必须大于当前时间"); 47 | } 48 | } 49 | 50 | public String getTrainStationName() { 51 | return trainStationName; 52 | } 53 | 54 | public void setTrainStationName(String trainStationName) { 55 | this.trainStationName = trainStationName; 56 | } 57 | 58 | public String getTrainStationCode() { 59 | return trainStationCode; 60 | } 61 | 62 | public void setTrainStationCode(String trainStationCode) { 63 | this.trainStationCode = trainStationCode; 64 | } 65 | 66 | public Date getTrainStartDate() { 67 | return trainStartDate; 68 | } 69 | 70 | public void setTrainStartDate(Date trainStartDate) { 71 | this.trainStartDate = trainStartDate; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return MoreObjects.toStringHelper(this) 77 | .add("trainStationCode", trainStationCode) 78 | .add("trainStartDate", trainStartDate) 79 | .toString(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.exception; 2 | 3 | import com.sinosun.train.enums.BusinessErrorCode; 4 | import com.sinosun.train.enums.PlatformErrorCode; 5 | 6 | /** 7 | * Created on 2019/1/14 11:37. 8 | * 9 | * @author caogu 10 | */ 11 | public class ServiceException extends RuntimeException { 12 | /** 13 | * 错误码 14 | */ 15 | private String code; 16 | 17 | /*public ServiceException( String code, String message) { 18 | super(message); 19 | this.code = code; 20 | }*/ 21 | 22 | public ServiceException(BusinessErrorCode businessErrorCode) { 23 | super(businessErrorCode.getMessage()); 24 | this.code = businessErrorCode.getCode(); 25 | } 26 | 27 | public ServiceException(BusinessErrorCode businessErrorCode, String extraMessage) { 28 | super(businessErrorCode.getMessage() + "|" + extraMessage); 29 | this.code = businessErrorCode.getCode(); 30 | } 31 | 32 | public ServiceException(BusinessErrorCode businessErrorCode, Throwable cause) { 33 | super(businessErrorCode.getMessage(), cause); 34 | this.code = businessErrorCode.getCode(); 35 | } 36 | 37 | public ServiceException(BusinessErrorCode businessErrorCode, Throwable cause, String extraMessage) { 38 | super(businessErrorCode.getMessage() + "|" + extraMessage, cause); 39 | this.code = businessErrorCode.getCode(); 40 | } 41 | 42 | public ServiceException(PlatformErrorCode platformErrorCode) { 43 | super(platformErrorCode.getMessage()); 44 | this.code = platformErrorCode.getCode(); 45 | } 46 | 47 | public ServiceException(PlatformErrorCode platformErrorCode, String extraMessage) { 48 | super(platformErrorCode.getMessage() + "|" + extraMessage); 49 | this.code = platformErrorCode.getCode(); 50 | } 51 | 52 | public ServiceException(PlatformErrorCode businessErrorCode, Throwable cause) { 53 | super(businessErrorCode.getMessage(), cause); 54 | this.code = businessErrorCode.getCode(); 55 | } 56 | 57 | public ServiceException(PlatformErrorCode businessErrorCode, Throwable cause, String extraMessage) { 58 | super(businessErrorCode.getMessage() + "|" + extraMessage, cause); 59 | this.code = businessErrorCode.getCode(); 60 | } 61 | 62 | /*public ServiceException( String code, String message, Throwable cause) { 63 | super(message, cause); 64 | this.code = code; 65 | }*/ 66 | 67 | public String getCode() { 68 | return code; 69 | } 70 | 71 | public void setCode(String code) { 72 | this.code = code; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/Stop.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * Created on 2019/1/10 20:38. 9 | * 10 | * @author caogu 11 | */ 12 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 13 | public class Stop { 14 | /** 15 | * 出发时间(格式 HH:mm) 16 | */ 17 | private String startTime; 18 | 19 | /** 20 | * 到达时间(格式 HH:mm 或者----) 21 | */ 22 | private String arriveTime; 23 | 24 | /** 25 | * 到达站名 26 | */ 27 | private String stationName; 28 | 29 | /** 30 | * 停留时间(分钟) 可能为---- 31 | */ 32 | private String stopoverTime; 33 | 34 | /** 35 | * 站序(01开始) 36 | */ 37 | private String stationNo; 38 | 39 | /** 40 | * 是否是我们搜索的出行站和到达站 false不是 true是 41 | */ 42 | private Boolean isSearchStation; 43 | 44 | public String getStartTime() { 45 | return startTime; 46 | } 47 | 48 | public void setStartTime(String startTime) { 49 | this.startTime = startTime; 50 | } 51 | 52 | public String getArriveTime() { 53 | return arriveTime; 54 | } 55 | 56 | public void setArriveTime(String arriveTime) { 57 | this.arriveTime = arriveTime; 58 | } 59 | 60 | public String getStationName() { 61 | return stationName; 62 | } 63 | 64 | public void setStationName(String stationName) { 65 | this.stationName = stationName; 66 | } 67 | 68 | public String getStopoverTime() { 69 | return stopoverTime; 70 | } 71 | 72 | public void setStopoverTime(String stopoverTime) { 73 | this.stopoverTime = stopoverTime; 74 | } 75 | 76 | public String getStationNo() { 77 | return stationNo; 78 | } 79 | 80 | public void setStationNo(String stationNo) { 81 | this.stationNo = stationNo; 82 | } 83 | 84 | public Boolean getIsSearchStation() { 85 | return isSearchStation; 86 | } 87 | 88 | public void setIsSearchStation(Boolean searchStation) { 89 | isSearchStation = searchStation; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return MoreObjects.toStringHelper(this) 95 | .add("startTime", startTime) 96 | .add("arriveTime", arriveTime) 97 | .add("stationName", stationName) 98 | .add("stopoverTime", stopoverTime) 99 | .add("stationNo", stationNo) 100 | .add("isSearchStation", isSearchStation) 101 | .toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/vo/Head.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.vo; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | import org.assertj.core.util.Lists; 7 | 8 | import java.util.ArrayList; 9 | 10 | /** 11 | * Created on 2019/7/31 9:34. 12 | * 13 | * @author caogu 14 | */ 15 | @JSONType(naming = PropertyNamingStrategy.CamelCase) 16 | public class Head { 17 | private String auth = null; 18 | private String cid = "09031010110784000474"; 19 | private String ctok = ""; 20 | private String cver = "1.0"; 21 | private String lang = "01"; 22 | private String sid = "8888"; 23 | private String syscode = "09"; 24 | private ArrayList extension = Lists.newArrayList(new Extension()); 25 | 26 | public String getAuth() { 27 | return auth; 28 | } 29 | 30 | public void setAuth(String auth) { 31 | this.auth = auth; 32 | } 33 | 34 | public String getCid() { 35 | return cid; 36 | } 37 | 38 | public void setCid(String cid) { 39 | this.cid = cid; 40 | } 41 | 42 | public String getCtok() { 43 | return ctok; 44 | } 45 | 46 | public void setCtok(String ctok) { 47 | this.ctok = ctok; 48 | } 49 | 50 | public String getCver() { 51 | return cver; 52 | } 53 | 54 | public void setCver(String cver) { 55 | this.cver = cver; 56 | } 57 | 58 | public String getLang() { 59 | return lang; 60 | } 61 | 62 | public void setLang(String lang) { 63 | this.lang = lang; 64 | } 65 | 66 | public String getSid() { 67 | return sid; 68 | } 69 | 70 | public void setSid(String sid) { 71 | this.sid = sid; 72 | } 73 | 74 | public String getSyscode() { 75 | return syscode; 76 | } 77 | 78 | public void setSyscode(String syscode) { 79 | this.syscode = syscode; 80 | } 81 | 82 | public ArrayList getExtension() { 83 | return extension; 84 | } 85 | 86 | public void setExtension(ArrayList extension) { 87 | this.extension = extension; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return MoreObjects.toStringHelper(this) 93 | .add("auth", auth) 94 | .add("cid", cid) 95 | .add("ctok", ctok) 96 | .add("cver", cver) 97 | .add("lang", lang) 98 | .add("sid", sid) 99 | .add("syscode", syscode) 100 | .add("extension", extension) 101 | .toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/utils/PreloadData.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.utils; 2 | 3 | import cn.hutool.core.io.resource.ClassPathResource; 4 | import com.sinosun.train.constants.FileNameConstant; 5 | import com.sinosun.train.enums.PlatformErrorCode; 6 | import com.sinosun.train.exception.ServiceException; 7 | import com.sinosun.train.model.response.Station; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.util.ResourceUtils; 12 | 13 | import java.io.File; 14 | import java.io.FileNotFoundException; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * Created on 2019/1/14 10:26. 20 | * 21 | * @author caogu 22 | */ 23 | public class PreloadData { 24 | private static Logger logger = LoggerFactory.getLogger(PreloadData.class); 25 | 26 | /** 27 | * 火车全量站点数据 28 | */ 29 | private static List TRAIN_ALL_STATION = getTrainCityData(FileNameConstant.TRAIN_ALL_STATION_LOCAL_FILE_NAME); 30 | /** 31 | * 火车热点站点数据 32 | */ 33 | private static List TRAIN_HOT_STATION = getTrainCityData(FileNameConstant.TRAIN_HOT_STATION_LOCAL_FILE_NAME); 34 | /** 35 | * 列车号全量数据 36 | */ 37 | private static Map TRAIN_ALL_CODE = getTrainAllCodeData(FileNameConstant.TRAIN_ALL_CODE_LOCAL_FILE_NAME); 38 | 39 | @NotNull 40 | public static List getTrainAllCity() { 41 | return TRAIN_ALL_STATION; 42 | } 43 | 44 | @NotNull 45 | public static List getTrainHotCity() { 46 | return TRAIN_HOT_STATION; 47 | } 48 | 49 | @NotNull 50 | public static Map getTrainAllCode() { 51 | return TRAIN_ALL_CODE; 52 | } 53 | 54 | @NotNull 55 | private static List getTrainCityData(String fileName) { 56 | ClassPathResource resource = new ClassPathResource("train" + File.separator + fileName); 57 | return JsonUtil.readFileToJsonArray(resource.getStream()).toJavaList(Station.class); 58 | } 59 | 60 | @NotNull 61 | private static Map getTrainAllCodeData(String fileName) { 62 | ClassPathResource resource = new ClassPathResource("train" + File.separator + fileName); 63 | return JsonUtil.readJsonFile(resource.getStream()); 64 | } 65 | 66 | @NotNull 67 | private static String getFilePath(String relativeFileName) { 68 | String path; 69 | try { 70 | path = ResourceUtils.getFile("classpath:" + relativeFileName).getAbsolutePath(); 71 | } catch (FileNotFoundException e) { 72 | logger.error("classpath:{}文件找不到", relativeFileName); 73 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 74 | } 75 | return path; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/request/GetTrainLineRequest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.request; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import com.alibaba.fastjson.annotation.JSONType; 6 | import com.sinosun.train.enums.BusinessErrorCode; 7 | import com.sinosun.train.exception.ServiceException; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.joda.time.DateTime; 10 | 11 | import java.util.Date; 12 | 13 | /** 14 | * Created on 2019/1/10 20:57. 15 | * 16 | * @author caogu 17 | */ 18 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 19 | public class GetTrainLineRequest { 20 | /** 21 | * 车次代码 22 | */ 23 | private String trainCode; 24 | 25 | /** 26 | * 出发站点代码 27 | */ 28 | private String fromStationCode; 29 | 30 | /** 31 | * 到达站点代码 32 | */ 33 | private String toStationCode; 34 | 35 | /** 36 | * 出发日期(格式:yyyy-mm-dd) 37 | */ 38 | @JSONField(format = "yyyy-MM-dd") 39 | private Date fromDate; 40 | 41 | public void validate() { 42 | if (StringUtils.isBlank(fromStationCode) 43 | || StringUtils.isBlank(toStationCode) 44 | || fromDate == null 45 | || StringUtils.isBlank(trainCode)) { 46 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_MISS); 47 | } 48 | 49 | if (new DateTime(fromDate).plusDays(1).minusSeconds(1).isBeforeNow()) { 50 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_ERROR, "出发时间必须大于当前时间"); 51 | } 52 | } 53 | 54 | public String getTrainCode() { 55 | return trainCode; 56 | } 57 | 58 | public void setTrainCode(String trainCode) { 59 | this.trainCode = trainCode; 60 | } 61 | 62 | public String getFromStationCode() { 63 | return fromStationCode; 64 | } 65 | 66 | public void setFromStationCode(String fromStationCode) { 67 | this.fromStationCode = fromStationCode; 68 | } 69 | 70 | public String getToStationCode() { 71 | return toStationCode; 72 | } 73 | 74 | public void setToStationCode(String toStationCode) { 75 | this.toStationCode = toStationCode; 76 | } 77 | 78 | public Date getFromDate() { 79 | return fromDate; 80 | } 81 | 82 | public void setFromDate(Date fromDate) { 83 | this.fromDate = fromDate; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return "GetTrainLineRequest{" + 89 | "trainCode='" + trainCode + '\'' + 90 | ", fromStationCode='" + fromStationCode + '\'' + 91 | ", toStationCode='" + toStationCode + '\'' + 92 | ", fromDate=" + fromDate + 93 | '}'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TrainLine.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created on 2019/1/10 20:45. 11 | * 12 | * @author caogu 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class TrainLine { 16 | /** 17 | * 经停信息列表 18 | */ 19 | private List stops; 20 | 21 | /** 22 | * 始发站名 23 | */ 24 | private String startStationName; 25 | 26 | /** 27 | * 终点站名 28 | */ 29 | private String endStationName; 30 | 31 | /** 32 | * 车次代码 33 | */ 34 | private String trainCode; 35 | 36 | /** 37 | * 车次类型 例如:快速 38 | */ 39 | private String trainClassName; 40 | 41 | /** 42 | * 服务类型 例如:"无空调" , "有空调" 43 | */ 44 | private String serviceName; 45 | 46 | public List getStops() { 47 | return stops; 48 | } 49 | 50 | public void setStops(List stops) { 51 | this.stops = stops; 52 | } 53 | 54 | public String getStartStationName() { 55 | return startStationName; 56 | } 57 | 58 | public void setStartStationName(String startStationName) { 59 | this.startStationName = startStationName; 60 | } 61 | 62 | public String getEndStationName() { 63 | return endStationName; 64 | } 65 | 66 | public void setEndStationName(String endStationName) { 67 | this.endStationName = endStationName; 68 | } 69 | 70 | public String getTrainCode() { 71 | return trainCode; 72 | } 73 | 74 | public void setTrainCode(String trainCode) { 75 | this.trainCode = trainCode; 76 | } 77 | 78 | public String getTrainClassName() { 79 | return trainClassName; 80 | } 81 | 82 | public void setTrainClassName(String trainClassName) { 83 | this.trainClassName = trainClassName; 84 | } 85 | 86 | public String getServiceName() { 87 | return serviceName; 88 | } 89 | 90 | public void setServiceName(String serviceName) { 91 | this.serviceName = serviceName; 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | return MoreObjects.toStringHelper(this) 97 | .add("stops", stops) 98 | .add("startStationName", startStationName) 99 | .add("endStationName", endStationName) 100 | .add("trainCode", trainCode) 101 | .add("trainClassName", trainClassName) 102 | .add("serviceName", serviceName) 103 | .toString(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/request/GetTicketListRequest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.request; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import com.alibaba.fastjson.annotation.JSONType; 6 | import com.google.common.base.MoreObjects; 7 | import com.sinosun.train.enums.BusinessErrorCode; 8 | import com.sinosun.train.exception.ServiceException; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.joda.time.DateTime; 11 | 12 | import java.util.Date; 13 | 14 | /** 15 | * Created on 2019/1/10 20:57. 16 | * 17 | * @author caogu 18 | */ 19 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 20 | public class GetTicketListRequest { 21 | /** 22 | * 出发站点代码 23 | */ 24 | private String fromStationCode; 25 | 26 | /** 27 | * 到达站点代码 28 | */ 29 | private String toStationCode; 30 | 31 | /** 32 | * 出发日期(格式:yyyy-mm-dd) 33 | */ 34 | @JSONField(format = "yyyy-MM-dd") 35 | private Date fromDate; 36 | 37 | /** 38 | * 是否是学生票(默认成人票) 39 | */ 40 | private Boolean isStudent = Boolean.FALSE; 41 | 42 | public void validate() { 43 | if (StringUtils.isBlank(fromStationCode) 44 | || StringUtils.isBlank(toStationCode) 45 | || fromDate == null 46 | || isStudent == null) { 47 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_MISS); 48 | } 49 | 50 | if (new DateTime(fromDate).plusDays(1).minusSeconds(1).isBeforeNow()) { 51 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_ERROR, "出发时间必须大于当前时间"); 52 | } 53 | } 54 | 55 | public String getFromStationCode() { 56 | return fromStationCode; 57 | } 58 | 59 | public void setFromStationCode(String fromStationCode) { 60 | this.fromStationCode = fromStationCode; 61 | } 62 | 63 | public String getToStationCode() { 64 | return toStationCode; 65 | } 66 | 67 | public void setToStationCode(String toStationCode) { 68 | this.toStationCode = toStationCode; 69 | } 70 | 71 | public Date getFromDate() { 72 | return fromDate; 73 | } 74 | 75 | public void setFromDate(Date fromDate) { 76 | this.fromDate = fromDate; 77 | } 78 | 79 | public Boolean getIsStudent() { 80 | return isStudent; 81 | } 82 | 83 | public void setIsStudent(Boolean student) { 84 | isStudent = student; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return MoreObjects.toStringHelper(this) 90 | .add("fromStationCode", fromStationCode) 91 | .add("toStationCode", toStationCode) 92 | .add("fromDate", fromDate) 93 | .add("isStudent", isStudent) 94 | .toString(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/controller/TrainController.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.sinosun.train.model.request.*; 5 | import com.sinosun.train.model.response.*; 6 | import com.sinosun.train.service.TrainStationService; 7 | import com.sinosun.train.service.TrainStationTimeTableService; 8 | import com.sinosun.train.service.TrainTicketService; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | 21 | /** 22 | * Created on 2019/1/10 20:05. 23 | * 24 | * @author caogu 25 | */ 26 | @Controller 27 | @RequestMapping(value = "/train/", 28 | method = RequestMethod.POST, 29 | produces = {MediaType.APPLICATION_JSON_VALUE}, 30 | consumes = {MediaType.APPLICATION_JSON_VALUE} 31 | ) 32 | public class TrainController { 33 | private static final Logger logger = LoggerFactory.getLogger(TrainController.class); 34 | 35 | @Autowired 36 | private TrainStationService trainStationService; 37 | @Autowired 38 | private TrainTicketService trainTicketService; 39 | @Autowired 40 | private TrainStationTimeTableService trainStationTimeTableService; 41 | 42 | @RequestMapping(value = "getAllCity") 43 | @ResponseBody 44 | public StationResult getAllCityHandler(HttpServletRequest request, @RequestBody JSONObject requestBody) { 45 | return trainStationService.getAllCity(requestBody.toJavaObject(NoneRequest.class)); 46 | } 47 | 48 | @RequestMapping(value = "getHotCity") 49 | @ResponseBody 50 | public StationResult getHotCityHandler(HttpServletRequest request, @RequestBody JSONObject requestBody) { 51 | return trainStationService.getHotCity(requestBody.toJavaObject(NoneRequest.class)); 52 | } 53 | 54 | @RequestMapping(value = "getTrainCode") 55 | @ResponseBody 56 | public TrainCodeResult getTrainCodeHandler(HttpServletRequest request, @RequestBody JSONObject requestBody) { 57 | return trainStationService.getAllTrainCode(requestBody.toJavaObject(NoneRequest.class)); 58 | } 59 | 60 | @RequestMapping(value = "searchCity") 61 | @ResponseBody 62 | public StationResult searchCityHandler(HttpServletRequest request, @RequestBody JSONObject requestBody) { 63 | return trainStationService.searchCity(requestBody.toJavaObject(SearchCityRequest.class)); 64 | } 65 | 66 | @RequestMapping(value = "getTicketList") 67 | @ResponseBody 68 | public TicketListResult getTicketListHandler(HttpServletRequest request, @RequestBody JSONObject requestBody) { 69 | return trainTicketService.getTicketList(requestBody.toJavaObject(GetTicketListRequest.class)); 70 | } 71 | 72 | @RequestMapping(value = "getTrainLine") 73 | @ResponseBody 74 | public TrainLineResult getTrainLineHandler(HttpServletRequest request, @RequestBody JSONObject requestBody) { 75 | return trainTicketService.getTrainLine(requestBody.toJavaObject(GetTrainLineRequest.class)); 76 | } 77 | 78 | @RequestMapping(value = "getTrainStationTimeTable") 79 | @ResponseBody 80 | public TrainStationTimeTableResult getTrainStationTimeTable(HttpServletRequest request, @RequestBody JSONObject requestBody) { 81 | return trainStationTimeTableService.getTrainStationTimeTable(requestBody.toJavaObject(GetTrainStationTimeTableRequest.class)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/vo/GetTicketPriceReq.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.vo; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | import com.alibaba.fastjson.annotation.JSONType; 6 | import com.google.common.base.MoreObjects; 7 | import com.sinosun.train.enums.BusinessErrorCode; 8 | import com.sinosun.train.exception.ServiceException; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.joda.time.DateTime; 11 | 12 | import java.util.Date; 13 | 14 | /** 15 | * Created on 2019/7/31 9:23. 16 | * 17 | * @author caogu 18 | */ 19 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 20 | public class GetTicketPriceReq { 21 | //到达站:"咸阳" 22 | private String arriveStation; 23 | 24 | //出发站:"西安" 25 | private String departStation; 26 | 27 | //出发日期(格式:yyyy-MM-dd):2019-07-31 28 | @JSONField(format = "yyyy-MM-dd") 29 | private Date departDate; 30 | 31 | //是否是学生票 32 | private Boolean student = Boolean.FALSE; 33 | 34 | //预定渠道 35 | private String channelName = "ctrip.h5"; 36 | 37 | //返回数据格式 38 | @JSONField(name = "contentType") 39 | private String contentType = "json"; 40 | 41 | //请求头部 42 | @JSONField(name = "head") 43 | private Head head = new Head(); 44 | 45 | public void validate() { 46 | if (StringUtils.isBlank(arriveStation) 47 | || StringUtils.isBlank(departStation) 48 | || departDate == null) { 49 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_MISS); 50 | } 51 | 52 | if (new DateTime(departDate).plusDays(1).minusSeconds(1).isBeforeNow()) { 53 | throw new ServiceException(BusinessErrorCode.REQUEST_PARAM_ERROR, "出发时间必须大于当前时间"); 54 | } 55 | } 56 | 57 | public String getArriveStation() { 58 | return arriveStation; 59 | } 60 | 61 | public void setArriveStation(String arriveStation) { 62 | this.arriveStation = arriveStation; 63 | } 64 | 65 | public String getDepartStation() { 66 | return departStation; 67 | } 68 | 69 | public void setDepartStation(String departStation) { 70 | this.departStation = departStation; 71 | } 72 | 73 | public Date getDepartDate() { 74 | return departDate; 75 | } 76 | 77 | public void setDepartDate(Date departDate) { 78 | this.departDate = departDate; 79 | } 80 | 81 | public Boolean getStudent() { 82 | return student; 83 | } 84 | 85 | public void setStudent(Boolean student) { 86 | this.student = student; 87 | } 88 | 89 | public String getChannelName() { 90 | return channelName; 91 | } 92 | 93 | public void setChannelName(String channelName) { 94 | this.channelName = channelName; 95 | } 96 | 97 | public String getContentType() { 98 | return contentType; 99 | } 100 | 101 | public void setContentType(String contentType) { 102 | this.contentType = contentType; 103 | } 104 | 105 | public Head getHead() { 106 | return head; 107 | } 108 | 109 | public void setHead(Head head) { 110 | this.head = head; 111 | } 112 | 113 | @Override 114 | public String toString() { 115 | return MoreObjects.toStringHelper(this) 116 | .add("arriveStation", arriveStation) 117 | .add("departStation", departStation) 118 | .add("departDate", departDate) 119 | .add("student", student) 120 | .add("channelName", channelName) 121 | .add("contentType", contentType) 122 | .add("head", head) 123 | .toString(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/TrainRestTest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.sinosun.train.model.response.StationResult; 6 | import com.sinosun.train.model.response.TicketListResult; 7 | import com.sinosun.train.model.response.TrainLineResult; 8 | import com.sinosun.train.model.response.TrainStationTimeTableResult; 9 | import org.junit.Assert; 10 | import org.junit.BeforeClass; 11 | import org.junit.Test; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | /** 15 | * Created on 2019/1/15 19:46. 16 | * 17 | * @author caogu 18 | */ 19 | public class TrainRestTest { 20 | private static RestTemplate restTemplate; 21 | private static final String DOMAIN = "http://localhost:8080/"; 22 | 23 | @BeforeClass 24 | public static void startUp() { 25 | restTemplate = new RestTemplate(); 26 | } 27 | 28 | @Test 29 | public void getAllCity() { 30 | JSONObject request = new JSONObject(); 31 | StationResult ret = restTemplate.postForObject(DOMAIN + "train/getAllCity", request, StationResult.class); 32 | 33 | Assert.assertEquals("0", ret.getCode()); 34 | Assert.assertTrue(!ret.getResult().getStations().isEmpty()); 35 | 36 | System.out.println(JSON.toJSONString(ret, true)); 37 | // String httpRes = HttpUtil.request(DOMAIN + "train/getAllCity", Connection.Method.POST, request); 38 | } 39 | 40 | @Test 41 | public void getHotCity() { 42 | JSONObject request = new JSONObject(); 43 | StationResult ret = restTemplate.postForObject(DOMAIN + "train/getHotCity", request, StationResult.class); 44 | 45 | Assert.assertEquals("0", ret.getCode()); 46 | Assert.assertTrue(!ret.getResult().getStations().isEmpty()); 47 | 48 | System.out.println(JSON.toJSONString(ret, true)); 49 | } 50 | 51 | @Test 52 | public void searchCity() { 53 | JSONObject request = new JSONObject().fluentPut("Keyword", "西安"); 54 | StationResult ret = restTemplate.postForObject(DOMAIN + "train/searchCity", request, StationResult.class); 55 | 56 | Assert.assertEquals("0", ret.getCode()); 57 | Assert.assertTrue(ret.getResult().getStations().size() == 3); 58 | 59 | System.out.println(JSON.toJSONString(ret, true)); 60 | } 61 | 62 | @Test 63 | public void getTicketList() { 64 | JSONObject request = new JSONObject(); 65 | request.put("FromStationCode", "XAY"); 66 | request.put("ToStationCode", "XYY"); 67 | request.put("FromDate", "2019-01-28"); 68 | // request.put("IsStudent", true); 69 | TicketListResult ret = restTemplate.postForObject(DOMAIN + "train/getTicketList", request, TicketListResult.class); 70 | System.out.println(JSON.toJSONString(ret, true)); 71 | } 72 | 73 | @Test 74 | public void getTrainLine() { 75 | JSONObject request = new JSONObject(); 76 | request.put("TrainNo", "5n000G167004"); 77 | request.put("TrainCode", "K227"); 78 | request.put("FromStationCode", "XAY"); 79 | request.put("ToStationCode", "XYY"); 80 | request.put("FromDate", "2019-02-22"); 81 | TrainLineResult ret = restTemplate.postForObject(DOMAIN + "train/getTrainLine", request, TrainLineResult.class); 82 | System.out.println(JSON.toJSONString(ret, true)); 83 | } 84 | 85 | @Test 86 | public void getTrainStationTimeTable() { 87 | JSONObject request = new JSONObject(); 88 | request.put("TrainStationName", "南靖"); 89 | request.put("TrainStationCode", "NJS"); 90 | request.put("TrainStartDate", "2019-03-08"); 91 | TrainStationTimeTableResult ret = restTemplate.postForObject(DOMAIN + "train/getTrainStationTimeTable", request, TrainStationTimeTableResult.class); 92 | System.out.println(JSON.toJSONString(ret, true)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/vo/TicketPrice.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.vo; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | 6 | import java.math.BigDecimal; 7 | 8 | /** 9 | * Created on 2019/1/10 20:38. 10 | * 11 | * @author caogu 12 | */ 13 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 14 | public class TicketPrice { 15 | /** 16 | * 商务座/特等座价格 17 | */ 18 | private BigDecimal swzPrice; 19 | 20 | /** 21 | * 一等座价格 22 | */ 23 | private BigDecimal ydzPrice; 24 | 25 | /** 26 | * 二等座价格 27 | */ 28 | private BigDecimal edzPrice; 29 | 30 | /** 31 | * 高级软卧价格 32 | */ 33 | private BigDecimal gjrwPrice; 34 | 35 | /** 36 | * 软卧/一等卧剩余票数 37 | */ 38 | private BigDecimal rwPrice; 39 | 40 | /** 41 | * 动卧价格 42 | */ 43 | private BigDecimal dwPrice; 44 | 45 | /** 46 | * 硬卧价格 47 | */ 48 | private BigDecimal ywPrice; 49 | 50 | /** 51 | * 软座价格 52 | */ 53 | private BigDecimal rzPrice; 54 | 55 | /** 56 | * 硬座价格 57 | */ 58 | private BigDecimal yzPrice; 59 | 60 | /** 61 | * 无座价格 62 | */ 63 | private BigDecimal wzPrice; 64 | 65 | /** 66 | * 其他价格 67 | */ 68 | private BigDecimal qtPrice; 69 | 70 | public BigDecimal getSwzPrice() { 71 | return swzPrice; 72 | } 73 | 74 | public void setSwzPrice(BigDecimal swzPrice) { 75 | this.swzPrice = swzPrice; 76 | } 77 | 78 | public BigDecimal getYdzPrice() { 79 | return ydzPrice; 80 | } 81 | 82 | public void setYdzPrice(BigDecimal ydzPrice) { 83 | this.ydzPrice = ydzPrice; 84 | } 85 | 86 | public BigDecimal getEdzPrice() { 87 | return edzPrice; 88 | } 89 | 90 | public void setEdzPrice(BigDecimal edzPrice) { 91 | this.edzPrice = edzPrice; 92 | } 93 | 94 | public BigDecimal getGjrwPrice() { 95 | return gjrwPrice; 96 | } 97 | 98 | public void setGjrwPrice(BigDecimal gjrwPrice) { 99 | this.gjrwPrice = gjrwPrice; 100 | } 101 | 102 | public BigDecimal getRwPrice() { 103 | return rwPrice; 104 | } 105 | 106 | public void setRwPrice(BigDecimal rwPrice) { 107 | this.rwPrice = rwPrice; 108 | } 109 | 110 | public BigDecimal getDwPrice() { 111 | return dwPrice; 112 | } 113 | 114 | public void setDwPrice(BigDecimal dwPrice) { 115 | this.dwPrice = dwPrice; 116 | } 117 | 118 | public BigDecimal getYwPrice() { 119 | return ywPrice; 120 | } 121 | 122 | public void setYwPrice(BigDecimal ywPrice) { 123 | this.ywPrice = ywPrice; 124 | } 125 | 126 | public BigDecimal getRzPrice() { 127 | return rzPrice; 128 | } 129 | 130 | public void setRzPrice(BigDecimal rzPrice) { 131 | this.rzPrice = rzPrice; 132 | } 133 | 134 | public BigDecimal getYzPrice() { 135 | return yzPrice; 136 | } 137 | 138 | public void setYzPrice(BigDecimal yzPrice) { 139 | this.yzPrice = yzPrice; 140 | } 141 | 142 | public BigDecimal getWzPrice() { 143 | return wzPrice; 144 | } 145 | 146 | public void setWzPrice(BigDecimal wzPrice) { 147 | this.wzPrice = wzPrice; 148 | } 149 | 150 | public BigDecimal getQtPrice() { 151 | return qtPrice; 152 | } 153 | 154 | public void setQtPrice(BigDecimal qtPrice) { 155 | this.qtPrice = qtPrice; 156 | } 157 | 158 | @Override 159 | public String toString() { 160 | return "TicketPrice{" + 161 | ", swzPrice=" + swzPrice + 162 | ", ydzPrice=" + ydzPrice + 163 | ", edzPrice=" + edzPrice + 164 | ", gjrwPrice=" + gjrwPrice + 165 | ", rwPrice=" + rwPrice + 166 | ", dwPrice=" + dwPrice + 167 | ", ywPrice=" + ywPrice + 168 | ", rzPrice=" + rzPrice + 169 | ", yzPrice=" + yzPrice + 170 | ", wzPrice=" + wzPrice + 171 | ", qtPrice=" + qtPrice + 172 | '}'; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/resources/train/TrainHotStation.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name":"北京", 4 | "StationCode":"BJP", 5 | "PingYin":"beijing", 6 | "PingYinShort":"bj" 7 | }, 8 | { 9 | "Name":"上海", 10 | "StationCode":"SHH", 11 | "PingYin":"shanghai", 12 | "PingYinShort":"sh" 13 | }, 14 | { 15 | "Name":"天津", 16 | "StationCode":"TJP", 17 | "PingYin":"tianjin", 18 | "PingYinShort":"tj" 19 | }, 20 | { 21 | "Name":"重庆", 22 | "StationCode":"CQW", 23 | "PingYin":"chongqing", 24 | "PingYinShort":"cq" 25 | }, 26 | { 27 | "Name":"长沙", 28 | "StationCode":"CSQ", 29 | "PingYin":"changsha", 30 | "PingYinShort":"cs" 31 | }, 32 | { 33 | "Name":"长春", 34 | "StationCode":"CCT", 35 | "PingYin":"changchun", 36 | "PingYinShort":"cc" 37 | }, 38 | { 39 | "Name":"成都", 40 | "StationCode":"CDW", 41 | "PingYin":"chengdu", 42 | "PingYinShort":"cd" 43 | }, 44 | { 45 | "Name":"福州", 46 | "StationCode":"FZS", 47 | "PingYin":"fuzhou", 48 | "PingYinShort":"fz" 49 | }, 50 | { 51 | "Name":"广州", 52 | "StationCode":"GZQ", 53 | "PingYin":"guangzhou", 54 | "PingYinShort":"gz" 55 | }, 56 | { 57 | "Name":"贵阳", 58 | "StationCode":"GIW", 59 | "PingYin":"guiyang", 60 | "PingYinShort":"gy" 61 | }, 62 | { 63 | "Name":"呼和浩特", 64 | "StationCode":"HHC", 65 | "PingYin":"huhehaote", 66 | "PingYinShort":"hhht" 67 | }, 68 | { 69 | "Name":"哈尔滨", 70 | "StationCode":"HBB", 71 | "PingYin":"haerbin", 72 | "PingYinShort":"heb" 73 | }, 74 | { 75 | "Name":"合肥", 76 | "StationCode":"HFH", 77 | "PingYin":"hefei", 78 | "PingYinShort":"hf" 79 | }, 80 | { 81 | "Name":"杭州", 82 | "StationCode":"HZH", 83 | "PingYin":"hangzhou", 84 | "PingYinShort":"hz" 85 | }, 86 | { 87 | "Name":"海口", 88 | "StationCode":"VUQ", 89 | "PingYin":"haikou", 90 | "PingYinShort":"hk" 91 | }, 92 | { 93 | "Name":"济南", 94 | "StationCode":"JNK", 95 | "PingYin":"jinan", 96 | "PingYinShort":"jn" 97 | }, 98 | { 99 | "Name":"昆明", 100 | "StationCode":"KMM", 101 | "PingYin":"kunming", 102 | "PingYinShort":"km" 103 | }, 104 | { 105 | "Name":"拉萨", 106 | "StationCode":"LSO", 107 | "PingYin":"lasa", 108 | "PingYinShort":"ls" 109 | }, 110 | { 111 | "Name":"兰州", 112 | "StationCode":"LZJ", 113 | "PingYin":"lanzhou", 114 | "PingYinShort":"lz" 115 | }, 116 | { 117 | "Name":"南宁", 118 | "StationCode":"NNZ", 119 | "PingYin":"nanning", 120 | "PingYinShort":"nn" 121 | }, 122 | { 123 | "Name":"南京", 124 | "StationCode":"NJH", 125 | "PingYin":"nanjing", 126 | "PingYinShort":"nj" 127 | }, 128 | { 129 | "Name":"南 昌", 130 | "StationCode":"NOG", 131 | "PingYin":"nanchang", 132 | "PingYinShort":"nc" 133 | }, 134 | { 135 | "Name":"石家庄", 136 | "StationCode":"SJP", 137 | "PingYin":"shijiazhuang", 138 | "PingYinShort":"sjz" 139 | }, 140 | { 141 | "Name":"沈阳", 142 | "StationCode":"SYT", 143 | "PingYin":"shenyang", 144 | "PingYinShort":"sy" 145 | }, 146 | { 147 | "Name":"太原", 148 | "StationCode":"TYV", 149 | "PingYin":"taiyuan", 150 | "PingYinShort":"ty" 151 | }, 152 | { 153 | "Name":"乌鲁木齐", 154 | "StationCode":"WAR", 155 | "PingYin":"wulumuqi", 156 | "PingYinShort":"wlmq" 157 | }, 158 | { 159 | "Name":"武汉", 160 | "StationCode":"WHN", 161 | "PingYin":"wuhan", 162 | "PingYinShort":"wh" 163 | }, 164 | { 165 | "Name":"西宁", 166 | "StationCode":"XNO", 167 | "PingYin":"xining", 168 | "PingYinShort":"xn" 169 | }, 170 | { 171 | "Name":"西安", 172 | "StationCode":"XAY", 173 | "PingYin":"xian", 174 | "PingYinShort":"xa" 175 | }, 176 | { 177 | "Name":"银川", 178 | "StationCode":"YIJ", 179 | "PingYin":"yinchuan", 180 | "PingYinShort":"yc" 181 | }, 182 | { 183 | "Name":"郑州", 184 | "StationCode":"ZZF", 185 | "PingYin":"zhengzhou", 186 | "PingYinShort":"zz" 187 | }, 188 | { 189 | "Name":"深圳", 190 | "StationCode":"SZQ", 191 | "PingYin":"shenzhen", 192 | "PingYinShort":"sz" 193 | }, 194 | { 195 | "Name":"厦门", 196 | "StationCode":"XMS", 197 | "PingYin":"xiamen", 198 | "PingYinShort":"xm" 199 | } 200 | ] -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/service/TrainStationService.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.google.common.collect.Lists; 5 | import com.sinosun.train.constants.RedisKeyConstant; 6 | import com.sinosun.train.model.request.NoneRequest; 7 | import com.sinosun.train.model.request.SearchCityRequest; 8 | import com.sinosun.train.model.response.Station; 9 | import com.sinosun.train.model.response.StationList; 10 | import com.sinosun.train.model.response.StationResult; 11 | import com.sinosun.train.model.response.TrainCodeResult; 12 | import com.sinosun.train.utils.PreloadData; 13 | import com.sinosun.train.utils.RedisUtils; 14 | import com.sinosun.train.utils.TrainHelper; 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.util.CollectionUtils; 21 | 22 | import java.util.List; 23 | import java.util.Locale; 24 | import java.util.Map; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | /** 28 | * Created on 2019/1/10 21:00. 29 | * 30 | * @author caogu 31 | */ 32 | @Service 33 | public class TrainStationService { 34 | private static final Logger logger = LoggerFactory.getLogger(TrainStationService.class); 35 | 36 | @Autowired 37 | private RedisUtils redisUtils; 38 | 39 | public StationResult getAllCity(NoneRequest requestBody) { 40 | // 从本地获取 41 | // return new StationResult(new StationList(getAllStation())); 42 | // 优先从redis获取 43 | return new StationResult(new StationList(getAllStatioonFromRedis())); 44 | } 45 | 46 | public StationResult getHotCity(NoneRequest requestBody) { 47 | // 从本地获取 48 | // return new StationResult(new StationList(PreloadData.getTrainHotCity())); 49 | // 优先从redis获取 50 | return new StationResult(new StationList(getHotStatioonFromRedis())); 51 | } 52 | 53 | /** 54 | * 获取 车次 - 列车号 数据 55 | * 56 | * @param requestBody 57 | * @return 58 | */ 59 | public TrainCodeResult getAllTrainCode(NoneRequest requestBody) { 60 | // 优先从redis获取 61 | return new TrainCodeResult(getTrainCodeFromRedis()); 62 | } 63 | 64 | public StationResult searchCity(SearchCityRequest requestBody) { 65 | requestBody.validate(); 66 | 67 | List ret = Lists.newArrayList(); 68 | String keyword = requestBody.getKeyword(); 69 | // 从本地获取 70 | // List stations = getAllStation(); 71 | // 从redis获取 72 | List stations = getAllStatioonFromRedis(); 73 | for (Station station : stations) { 74 | boolean isMatching = station.getName().startsWith(keyword) 75 | || station.getPingYin().toLowerCase(Locale.ENGLISH).startsWith(keyword.toLowerCase(Locale.ENGLISH)) 76 | || station.getPingYinShort().toLowerCase(Locale.ENGLISH).startsWith(keyword.toLowerCase(Locale.ENGLISH)); 77 | if (isMatching) { 78 | ret.add(station); 79 | } 80 | } 81 | return new StationResult(new StationList(ret)); 82 | } 83 | 84 | /** 85 | * 获取火车站点数据,先从本地获取,获取是失败在从12306获取 86 | * 87 | * @return 火车站点数据 88 | */ 89 | private List getAllStation() { 90 | List stations = PreloadData.getTrainAllCity(); 91 | if (CollectionUtils.isEmpty(stations)) { 92 | stations = TrainHelper.getTrainAllCityFromNet(); 93 | } 94 | return stations; 95 | } 96 | 97 | /** 98 | * 获取火车站点数据,先从redis获取,获取是失败在从12306获取 99 | * 100 | * @return 101 | */ 102 | private List getAllStatioonFromRedis() { 103 | // 优先从redis中获取站点信息 104 | String allStationStr = (String) redisUtils.get(RedisKeyConstant.REDIS_KEY_LOCAL_DATA_STATION); 105 | List stations = null; 106 | if (StringUtils.isNotBlank(allStationStr)) { 107 | stations = JSONObject.parseArray(allStationStr, Station.class); 108 | } 109 | 110 | if (CollectionUtils.isEmpty(stations)) { 111 | stations = TrainHelper.getTrainAllCityFromNet(); 112 | // 设置到缓存 113 | redisUtils.set(RedisKeyConstant.REDIS_KEY_LOCAL_DATA_STATION, JSONObject.toJSONString(stations), 1L, TimeUnit.DAYS); 114 | } 115 | return stations; 116 | } 117 | 118 | /** 119 | * 获取热门火车站点数据,先从redis获取,获取是失败在从12306获取 120 | * 121 | * @return 122 | */ 123 | private List getHotStatioonFromRedis() { 124 | // 优先从redis中获取站点信息 125 | String HotStationStr = (String) redisUtils.get(RedisKeyConstant.REDIS_KEY_LOCAL_DATA_HOT_STATION); 126 | List hotStationsList = null; 127 | if (StringUtils.isNotBlank(HotStationStr)) { 128 | hotStationsList = JSONObject.parseArray(HotStationStr, Station.class); 129 | } 130 | 131 | if (CollectionUtils.isEmpty(hotStationsList)) { 132 | hotStationsList = TrainHelper.getTrainHotStationFromNet(); 133 | // 设置到缓存 134 | redisUtils.set(RedisKeyConstant.REDIS_KEY_LOCAL_DATA_HOT_STATION, JSONObject.toJSONString(hotStationsList), 1L, TimeUnit.DAYS); 135 | } 136 | return hotStationsList; 137 | } 138 | 139 | /** 140 | * 获取 车次 - 列车号 数据,先从redis获取,获取是失败在从12306获取 141 | * 142 | * @return 143 | */ 144 | private Map getTrainCodeFromRedis() { 145 | // 优先从redis中获取站点信息 146 | String trainNoLinkStr = (String) redisUtils.get(RedisKeyConstant.REDIS_KEY_LOCAL_DATA_TRAIN_NO_LINK); 147 | Map trainNoLinkMap = null; 148 | if (StringUtils.isNotBlank(trainNoLinkStr)) { 149 | trainNoLinkMap = JSONObject.parseObject(trainNoLinkStr, Map.class); 150 | } 151 | 152 | if (CollectionUtils.isEmpty(trainNoLinkMap)) { 153 | trainNoLinkMap = TrainHelper.getAllTrainNoListFromNet(); 154 | // 设置到缓存 155 | redisUtils.set(RedisKeyConstant.REDIS_KEY_LOCAL_DATA_TRAIN_NO_LINK, JSONObject.toJSONString(trainNoLinkMap), 1L, TimeUnit.DAYS); 156 | } 157 | return trainNoLinkMap; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/utils/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.utils; 2 | 3 | import cn.hutool.core.io.IoUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONArray; 6 | import com.alibaba.fastjson.JSONObject; 7 | import com.alibaba.fastjson.parser.Feature; 8 | import com.alibaba.fastjson.serializer.NameFilter; 9 | import com.alibaba.fastjson.serializer.PascalNameFilter; 10 | import com.sinosun.train.enums.PlatformErrorCode; 11 | import com.sinosun.train.exception.ServiceException; 12 | import org.apache.commons.io.FileUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.nio.charset.StandardCharsets; 20 | 21 | /** 22 | * Created on 2019/1/14 21:53. 23 | * 24 | * @author caogu 25 | */ 26 | public class JsonUtil { 27 | private static final Logger logger = LoggerFactory.getLogger(JsonUtil.class); 28 | 29 | /** 30 | * 读取json文件并且返回json对象 31 | * 32 | * @param filePath json文件路径 33 | * @return json对象 34 | */ 35 | public static JSONObject readJsonFile(String filePath) { 36 | JSONObject jo; 37 | try { 38 | String input = FileUtils.readFileToString(new File(filePath), "UTF-8"); 39 | jo = JSONObject.parseObject(input, Feature.OrderedField); 40 | } catch (IOException e) { 41 | logger.error("读取json文件出错,filePath=" + filePath, e); 42 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 43 | } 44 | return jo; 45 | } 46 | 47 | /** 48 | * 读取json文件流并且返回json对象 49 | * 50 | * @param inputStream json文件流 51 | * @return json数组 52 | */ 53 | public static JSONObject readJsonFile(InputStream inputStream) { 54 | JSONObject jo; 55 | try { 56 | String input = IoUtil.read(inputStream, StandardCharsets.UTF_8); 57 | jo = JSONObject.parseObject(input); 58 | } catch (Exception e) { 59 | logger.error("读取json文件流出错", e); 60 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 61 | } 62 | return jo; 63 | } 64 | 65 | /** 66 | * 读取json文件并且返回class对象 67 | * 68 | * @param filePath json文件路径 69 | * @param clz VO的class对象 70 | * @param json对应的VO 71 | * @return json对应的VO 72 | */ 73 | public static T readJsonFile(String filePath, Class clz) { 74 | T jo; 75 | try { 76 | String input = FileUtils.readFileToString(new File(filePath), "UTF-8"); 77 | jo = JSONObject.parseObject(input, clz); 78 | } catch (IOException e) { 79 | logger.error("读取json文件出错,filePath=" + filePath, e); 80 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 81 | } 82 | return jo; 83 | } 84 | 85 | 86 | /** 87 | * 读取json文件并且返回json数组 88 | * 89 | * @param inputStream json文件流 90 | * @return json数组 91 | */ 92 | public static JSONArray readFileToJsonArray(InputStream inputStream) { 93 | JSONArray jo; 94 | try { 95 | String input = IoUtil.read(inputStream, StandardCharsets.UTF_8); 96 | jo = JSONObject.parseArray(input); 97 | } catch (Exception e) { 98 | logger.error("读取json文件出错", e); 99 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 100 | } 101 | return jo; 102 | } 103 | 104 | /** 105 | * 读取json文件并且返回json数组 106 | * 107 | * @param filePath json文件路径 108 | * @return json数组 109 | */ 110 | public static JSONArray readFileToJsonArray(String filePath) { 111 | JSONArray jo; 112 | try { 113 | String input = FileUtils.readFileToString(new File(filePath), "UTF-8"); 114 | jo = JSONObject.parseArray(input); 115 | } catch (IOException e) { 116 | logger.error("读取json文件出错,filePath=" + filePath, e); 117 | throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR, e); 118 | } 119 | return jo; 120 | } 121 | 122 | /** 123 | * json字符串转JSON对象 124 | * 125 | * @param text json字符串 126 | * @return json对象 127 | */ 128 | public static JSONObject parseObject(String text) { 129 | return JSONObject.parseObject(text); 130 | } 131 | 132 | /** 133 | * json字符串转JSON数组 134 | * 135 | * @param text json字符串 136 | * @return json数组 137 | */ 138 | public static JSONArray parseArray(String text) { 139 | return JSONObject.parseArray(text); 140 | } 141 | 142 | /** 143 | * 对象转json字符串 144 | * 145 | * @param t java对象 146 | * @param 对象对应的类型 147 | * @return json字符串 148 | */ 149 | public static String entity2Json(T t) { 150 | try { 151 | return JSON.toJSONString(t/*, SerializerFeature.DisableCircularReferenceDetect*/); 152 | } catch (Exception e) { 153 | logger.error("JsonUtil.entity2Json", e); 154 | throw e; 155 | } 156 | } 157 | 158 | /** 159 | * json对象的key的首字母转为大写 160 | *

采用先序列化在反序列化,在序列化时改变key的思路

161 | * @param object json对象 162 | * @return key首字母转为大写的json对象 163 | */ 164 | public static JSONObject keyFirstCharToUpCase(Object object) { 165 | return JSONObject.parseObject(JSON.toJSONString(object, new PascalNameFilter())); 166 | } 167 | 168 | /** 169 | * json对象的key的首字母转为小写 170 | * 171 | * @param object json对象 172 | * @return key首字母转为小写的json对象 173 | */ 174 | public static JSONObject keyFirstCharToLowerCase(Object object) { 175 | return JSONObject.parseObject(JSON.toJSONString(object, new PascalNameLowerFilter())); 176 | } 177 | 178 | static class PascalNameLowerFilter implements NameFilter { 179 | @Override 180 | public String process(Object source, String name, Object value) { 181 | if (name == null || name.length() == 0) { 182 | return name; 183 | } 184 | 185 | char[] chars = name.toCharArray(); 186 | chars[0] = Character.toLowerCase(chars[0]); 187 | 188 | return new String(chars); 189 | } 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/utils/TrainHelper.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.utils; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.google.common.base.Splitter; 6 | import com.google.common.collect.Lists; 7 | import com.sinosun.train.constants.UrlConstant; 8 | import com.sinosun.train.model.response.Station; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.jsoup.Connection; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * Created on 2019/1/16 20:42. 20 | * 21 | * @author caogu 22 | */ 23 | public class TrainHelper { 24 | private static final Logger logger = LoggerFactory.getLogger(TrainHelper.class); 25 | 26 | 27 | /** 28 | * 从12306获取最新的火车站点数据 29 | * @return 火车站点数据 30 | */ 31 | public static List getTrainAllCityFromNet() { 32 | List stations = Lists.newArrayList(); 33 | 34 | String httpRes = HttpUtil.request(UrlConstant.TRAIN_ALL_STATION_URL, Connection.Method.GET, null); 35 | if (StringUtils.isNotEmpty(httpRes)) { 36 | // var station_names ='@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1' 37 | String originalStationStr = httpRes.substring(httpRes.indexOf("\'") + 1, httpRes.lastIndexOf("\'")); 38 | List originalStations = Lists.newArrayList(Splitter.on("@").omitEmptyStrings().trimResults().split(originalStationStr)); 39 | for (String originalStation : originalStations) { 40 | List stationInfo = Lists.newArrayList(Splitter.on("|").omitEmptyStrings().trimResults().split(originalStation)); 41 | if (stationInfo.size() > 3) { 42 | Station station = new Station(); 43 | station.setPingYinShort(stationInfo.get(0)); 44 | station.setName(stationInfo.get(1)); 45 | station.setStationCode(stationInfo.get(2)); 46 | station.setPingYin(stationInfo.get(3)); 47 | stations.add(station); 48 | } else { 49 | logger.error("从12306获取到的火车站点信息有误,stationInfo={}", originalStation); 50 | } 51 | } 52 | } else { 53 | logger.error("从12306获取到的火车站点信息为空"); 54 | } 55 | return stations; 56 | } 57 | 58 | /** 59 | * 从12306获取最新的热门火车站点数据 60 | * @return 火车站点数据 61 | */ 62 | public static List getTrainHotStationFromNet() { 63 | List stations = Lists.newArrayList(); 64 | 65 | String httpRes = HttpUtil.request(UrlConstant.TRAIN_HOT_STATION_URL, Connection.Method.GET, null); 66 | if (StringUtils.isNotEmpty(httpRes)) { 67 | // var station_names ='@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1' 68 | String originalStationStr = httpRes.substring(httpRes.indexOf("\'") + 1, httpRes.lastIndexOf("\'")); 69 | List originalStations = Lists.newArrayList(Splitter.on("@").omitEmptyStrings().trimResults().split(originalStationStr)); 70 | for (String originalStation : originalStations) { 71 | List stationInfo = Lists.newArrayList(Splitter.on("|").omitEmptyStrings().trimResults().split(originalStation)); 72 | if (stationInfo.size() > 3) { 73 | Station station = new Station(); 74 | station.setPingYinShort(stationInfo.get(0)); 75 | station.setName(stationInfo.get(1)); 76 | station.setStationCode(stationInfo.get(2)); 77 | station.setPingYin(stationInfo.get(3)); 78 | stations.add(station); 79 | } else { 80 | logger.error("从12306获取到的热门火车站点信息有误,stationInfo={}", originalStation); 81 | } 82 | } 83 | } else { 84 | logger.error("从12306获取到的热门火车站点信息为空"); 85 | } 86 | return stations; 87 | } 88 | 89 | /** 90 | * 向12306发送get请求,并返回json对象 91 | * @param url 请求URL 92 | * @return json对象 93 | */ 94 | public static JSONObject requestTo12306(String url) { 95 | return JsonUtil.parseObject(HttpUtil.request(url, Connection.Method.GET, null)); 96 | } 97 | 98 | /** 99 | * 从12306获取最新的 车次 - 列车号 关联数据 100 | * 101 | * @return 102 | */ 103 | public static Map getAllTrainNoListFromNet() { 104 | Map resultMap = new HashMap(); 105 | 106 | try { 107 | // HttpUtil.request获得的结果在json对象序列化时存在unclosed.str异常 108 | // String httpRes = HttpUtil.request(UrlConstant.TRAIN_ALL_CODE_LIST_URL, Connection.Method.GET, null); 109 | String httpRes = cn.hutool.http.HttpUtil.get(UrlConstant.TRAIN_ALL_CODE_LIST_URL); 110 | 111 | // 返回例子:{ "2019-03-17": { "D": [ { "station_train_code": "D1(北京-沈阳南)", "train_no": "24000000D120" },.... ], .... } } 112 | if (StringUtils.isNotEmpty(httpRes) && httpRes.startsWith("var")) { 113 | httpRes = httpRes.replaceAll("var train_list =", ""); 114 | 115 | JSONObject jsonObject = JSONObject.parseObject(httpRes); 116 | 117 | // 第一层遍历,时间 118 | for (Map.Entry entry : jsonObject.entrySet()) { 119 | // 获取列车类型对象 120 | JSONObject dateElementObj = (JSONObject) entry.getValue(); 121 | // 遍历每个时间的每个列车类型 122 | for (Map.Entry trainTypeEntry : dateElementObj.entrySet()) { 123 | JSONArray trainTypeArray = (JSONArray) trainTypeEntry.getValue(); 124 | // 遍历每个车型的车次 125 | for (int i = 0; i < trainTypeArray.size(); i++) { 126 | JSONObject trainInfo = trainTypeArray.getJSONObject(i); 127 | 128 | resultMap.put(trainInfo.getString("station_train_code").replaceAll("[^0-9a-zA-Z]", ""), trainInfo.getString("train_no")); 129 | } 130 | } 131 | } 132 | } else { 133 | logger.error("从12306获取到的火车站点信息为空"); 134 | } 135 | } catch (Exception e) { 136 | e.printStackTrace(); 137 | } 138 | 139 | return resultMap; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/utils/RedisUtils.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.data.redis.core.*; 9 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.io.Serializable; 13 | import java.util.List; 14 | import java.util.Set; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * redis 工具类 19 | * 20 | * Created on 2019-03-11 09:35:31 21 | * 22 | * @author 猎隼丶止戈 23 | */ 24 | @Service 25 | public class RedisUtils { 26 | 27 | @Autowired 28 | private RedisTemplate redisTemplate; 29 | 30 | @Autowired 31 | private StringRedisTemplate template; 32 | 33 | @Bean 34 | public RedisTemplate redisTemplate() { 35 | // 设置序列化工具,这样ReportBean不需要实现Serializable接口 36 | setSerializer(template); 37 | template.afterPropertiesSet(); 38 | return template; 39 | } 40 | 41 | private void setSerializer(StringRedisTemplate template) { 42 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 43 | ObjectMapper om = new ObjectMapper(); 44 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 45 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 46 | jackson2JsonRedisSerializer.setObjectMapper(om); 47 | template.setValueSerializer(jackson2JsonRedisSerializer); 48 | } 49 | 50 | /** 51 | * 写入缓存 52 | * 53 | * @param key 54 | * @param value 55 | * @return 56 | */ 57 | public boolean set(final String key, Object value) { 58 | boolean result = false; 59 | try { 60 | ValueOperations operations = redisTemplate.opsForValue(); 61 | operations.set(key, value); 62 | result = true; 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | return result; 67 | } 68 | 69 | /** 70 | * 写入缓存设置时效时间 71 | * 72 | * @param key 73 | * @param value 74 | * @param expireTime 75 | * @param timeUnit 76 | * @return 77 | */ 78 | public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) { 79 | boolean result = false; 80 | try { 81 | ValueOperations operations = redisTemplate.opsForValue(); 82 | operations.set(key, value); 83 | redisTemplate.expire(key, expireTime, timeUnit); 84 | result = true; 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | return result; 89 | } 90 | 91 | /** 92 | * 批量删除对应的value 93 | * 94 | * @param keys 95 | */ 96 | public void remove(final String... keys) { 97 | for (String key : keys) { 98 | remove(key); 99 | } 100 | } 101 | 102 | /** 103 | * 批量删除key 104 | * 105 | * @param pattern 106 | */ 107 | public void removePattern(final String pattern) { 108 | Set keys = redisTemplate.keys(pattern); 109 | if (keys.size() > 0) { 110 | redisTemplate.delete(keys); 111 | } 112 | } 113 | 114 | /** 115 | * 删除对应的value 116 | * 117 | * @param key 118 | */ 119 | public void remove(final String key) { 120 | if (exists(key)) { 121 | redisTemplate.delete(key); 122 | } 123 | } 124 | 125 | /** 126 | * 判断缓存中是否有对应的value 127 | * 128 | * @param key 129 | * @return 130 | */ 131 | public boolean exists(final String key) { 132 | return redisTemplate.hasKey(key); 133 | } 134 | 135 | /** 136 | * 读取缓存 137 | * 138 | * @param key 139 | * @return 140 | */ 141 | public Object get(final String key) { 142 | Object result = null; 143 | ValueOperations operations = redisTemplate.opsForValue(); 144 | result = operations.get(key); 145 | return result; 146 | } 147 | 148 | /** 149 | * 读取缓存 150 | * 151 | * @param key 152 | * @return 153 | */ 154 | public Object getValueByKey(String key) { 155 | Object result = null; 156 | result = redisTemplate.opsForValue().get(key); 157 | return result; 158 | } 159 | 160 | /** 161 | * 哈希 添加 162 | * 163 | * @param key 164 | * @param hashKey 165 | * @param value 166 | */ 167 | public void hmSet(String key, Object hashKey, Object value) { 168 | HashOperations hash = redisTemplate.opsForHash(); 169 | hash.put(key, hashKey, value); 170 | } 171 | 172 | /** 173 | * 哈希获取数据 174 | * 175 | * @param key 176 | * @param hashKey 177 | * @return 178 | */ 179 | public Object hmGet(String key, Object hashKey) { 180 | HashOperations hash = redisTemplate.opsForHash(); 181 | return hash.get(key, hashKey); 182 | } 183 | 184 | /** 185 | * 列表添加 186 | * 187 | * @param k 188 | * @param v 189 | */ 190 | public void lPush(String k, Object v) { 191 | ListOperations list = redisTemplate.opsForList(); 192 | list.rightPush(k, v); 193 | } 194 | 195 | /** 196 | * 列表获取 197 | * 198 | * @param k 199 | * @param l 200 | * @param l1 201 | * @return 202 | */ 203 | public List lRange(String k, long l, long l1) { 204 | ListOperations list = redisTemplate.opsForList(); 205 | return list.range(k, l, l1); 206 | } 207 | 208 | /** 209 | * 集合添加 210 | * 211 | * @param key 212 | * @param value 213 | */ 214 | public void add(String key, Object value) { 215 | SetOperations set = redisTemplate.opsForSet(); 216 | set.add(key, value); 217 | } 218 | 219 | /** 220 | * 集合获取 221 | * 222 | * @param key 223 | * @return 224 | */ 225 | public Set setMembers(String key) { 226 | SetOperations set = redisTemplate.opsForSet(); 227 | return set.members(key); 228 | } 229 | 230 | /** 231 | * 有序集合添加 232 | * 233 | * @param key 234 | * @param value 235 | * @param scoure 236 | */ 237 | public void zAdd(String key, Object value, double scoure) { 238 | ZSetOperations zset = redisTemplate.opsForZSet(); 239 | zset.add(key, value, scoure); 240 | } 241 | 242 | /** 243 | * 有序集合获取 244 | * 245 | * @param key 246 | * @param scoure 247 | * @param scoure1 248 | * @return 249 | */ 250 | public Set rangeByScore(String key, double scoure, double scoure1) { 251 | ZSetOperations zset = redisTemplate.opsForZSet(); 252 | return zset.rangeByScore(key, scoure, scoure1); 253 | } 254 | } -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/service/TrainStationTimeTableService.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.service; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.google.common.collect.Lists; 7 | import com.sinosun.train.model.request.GetTrainStationTimeTableRequest; 8 | import com.sinosun.train.model.response.TrainStationTimeTable; 9 | import com.sinosun.train.model.response.TrainStationTimeTableResult; 10 | import com.sinosun.train.utils.TrainHelper; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.util.CollectionUtils; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Created on 2019/03/08 20 | * 21 | * @author 猎隼丶止戈 22 | */ 23 | @Service 24 | public class TrainStationTimeTableService { 25 | private static final Logger logger = LoggerFactory.getLogger(TrainStationTimeTableService.class); 26 | 27 | private static final String DATE_FORMAT = "yyyy-MM-dd"; 28 | 29 | private static String baseUrl = "https://kyfw.12306.cn"; 30 | private static String publicName = "/otn"; 31 | private static String getTrainStationTimeTableUrlFmt = baseUrl + publicName + 32 | "/czxx/query?" + 33 | "train_start_date=%s&" + 34 | "train_station_name=%s&" + 35 | "train_station_code=%s&" + 36 | "randCode="; 37 | 38 | 39 | /** 40 | * 获取查询站点时刻表 41 | * 42 | * @param requestBody 43 | * @return 44 | */ 45 | public TrainStationTimeTableResult getTrainStationTimeTable(GetTrainStationTimeTableRequest requestBody) { 46 | requestBody.validate(); 47 | 48 | String trainStationName = requestBody.getTrainStationName(); 49 | String trainStationCode = requestBody.getTrainStationCode(); 50 | String trainStartDate = DateUtil.formatDate(requestBody.getTrainStartDate()); 51 | 52 | return new TrainStationTimeTableResult(getAnalysisTrainStaionTimeTable(trainStationName, trainStationCode, trainStartDate)); 53 | } 54 | 55 | /** 56 | * 查询列车站点时刻表 57 | * 58 | * @param trainStationName 59 | * @param trainStationCode 60 | * @param trainStartDate 61 | * @return 62 | */ 63 | private List getAnalysisTrainStaionTimeTable(String trainStationName, String trainStationCode, String trainStartDate) { 64 | // 生成请求链接 65 | String trainStaionTimeTableUrl = String.format(getTrainStationTimeTableUrlFmt, trainStartDate, trainStationName, trainStationCode); 66 | JSONObject ret12306 = TrainHelper.requestTo12306(trainStaionTimeTableUrl); 67 | JSONArray dataArray = ret12306.getJSONObject("data").getJSONArray("data"); 68 | 69 | // 返回结果不为空时 70 | List trainStationTimeTableList = Lists.newArrayList(); 71 | if (!CollectionUtils.isEmpty(dataArray)) { 72 | for (int i = 0; i < dataArray.size(); i++) { 73 | TrainStationTimeTable trainStationTimeTable = new TrainStationTimeTable(); 74 | JSONObject trainStationTimeInfo = dataArray.getJSONObject(i); 75 | 76 | String startTrainDate = trainStationTimeInfo.getString("start_train_date"); 77 | String trainNo = trainStationTimeInfo.getString("train_no"); 78 | String startStationTelecode = trainStationTimeInfo.getString("start_station_telecode"); 79 | String startStationName = trainStationTimeInfo.getString("start_station_name"); 80 | String startStartTime = trainStationTimeInfo.getString("start_start_time"); 81 | String endStationTelecode = trainStationTimeInfo.getString("end_station_telecode"); 82 | String endStationName = trainStationTimeInfo.getString("end_station_name"); 83 | String endArriveTime = trainStationTimeInfo.getString("end_arrive_time"); 84 | String trainTypeCode = trainStationTimeInfo.getString("train_type_code"); 85 | String trainTypeName = trainStationTimeInfo.getString("train_type_name"); 86 | String trainClassCode = trainStationTimeInfo.getString("train_class_code"); 87 | String trainClassName = trainStationTimeInfo.getString("train_class_name"); 88 | String stationNo = trainStationTimeInfo.getString("station_no"); 89 | String stationName = trainStationTimeInfo.getString("station_name"); 90 | String stationTelecode = trainStationTimeInfo.getString("station_telecode"); 91 | String stationTrainCode = trainStationTimeInfo.getString("station_train_code"); 92 | String arriveDayDiff = trainStationTimeInfo.getString("arrive_day_diff"); 93 | String arriveTime = trainStationTimeInfo.getString("arrive_time"); 94 | String startTime = trainStationTimeInfo.getString("start_time"); 95 | String startDayDiff = trainStationTimeInfo.getString("start_day_diff"); 96 | String stopoverTime = trainStationTimeInfo.getString("stopover_time"); 97 | String runningTime = trainStationTimeInfo.getString("running_time"); 98 | String seatTypes = trainStationTimeInfo.getString("seat_types"); 99 | String serviceType = trainStationTimeInfo.getString("service_type"); 100 | String serviceTypeStr = "0".equals(serviceType) ? "无空调" : "有空调"; 101 | 102 | trainStationTimeTable.setStartTrainDate(startTrainDate); 103 | trainStationTimeTable.setTrainNo(trainNo); 104 | trainStationTimeTable.setStartStationTelecode(startStationTelecode); 105 | trainStationTimeTable.setStartStationName(startStationName); 106 | trainStationTimeTable.setStartStartTime(startStartTime); 107 | trainStationTimeTable.setEndStationTelecode(endStationTelecode); 108 | trainStationTimeTable.setEndStationName(endStationName); 109 | trainStationTimeTable.setEndArriveTime(endArriveTime); 110 | trainStationTimeTable.setTrainTypeCode(trainTypeCode); 111 | trainStationTimeTable.setTrainTypeName(trainTypeName); 112 | trainStationTimeTable.setTrainClassCode(trainClassCode); 113 | trainStationTimeTable.setTrainClassName(trainClassName); 114 | trainStationTimeTable.setStationNo(stationNo); 115 | trainStationTimeTable.setStationName(stationName); 116 | trainStationTimeTable.setStationTelecode(stationTelecode); 117 | trainStationTimeTable.setStationTrainCode(stationTrainCode); 118 | trainStationTimeTable.setArriveDayDiff(arriveDayDiff); 119 | trainStationTimeTable.setArriveTime(arriveTime); 120 | trainStationTimeTable.setStartTime(startTime); 121 | trainStationTimeTable.setStartDayDiff(startDayDiff); 122 | trainStationTimeTable.setStopoverTime(stopoverTime); 123 | trainStationTimeTable.setRunningTime(runningTime); 124 | trainStationTimeTable.setSeatTypes(seatTypes); 125 | trainStationTimeTable.setServiceType(serviceType); 126 | trainStationTimeTable.setServiceTypeStr(serviceTypeStr); 127 | 128 | trainStationTimeTableList.add(trainStationTimeTable); 129 | } 130 | } 131 | 132 | return trainStationTimeTableList; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 1.项目简介 2 | 本项目旨在分析12306接口协议,构建一个简单易用的Rest API风格的火车票订票服务。 3 | 为火车票抢购软件提供强有力的后台支撑。 4 | > [详细原理分析和代码讲解](https://blog.csdn.net/u013467442/article/details/87898708) 5 | 6 | ## 2.接口公共协议 7 | 8 | ### 协议说明 9 | - 接口采用HTTP的POST方式请求。 10 | - 输入和输出参数都采用UTF-8编码。 11 | - 出入参数均为json格式 12 | ### 参数描述 13 | #### 公共请求参数 14 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 15 | | :------:| :------:| ------: | :------: |:------: | 16 | | --- | --- | --- | --- |--- | 17 | #### 公共响应参数 18 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 19 | | :------:| :------:| :------: | :------: |:------: | 20 | | Code | 结果代码 | String | N |0表示请求成功返回;非0表示存在业务异常。业务异常详见错误码 | 21 | | Message | 错误描述 | String | Y | Code非零时有值,表示错误描述 | 22 | | Result | 结果数据 | Object | Y | 不同的请求返回的不同结果。 | 23 | 24 | ## 3.火车票业务协议 25 | `### 3.1 获取所有/热点车站信息 26 | #### 方法名称 27 | - ```train/getAllCity``` 28 | - ```train/getHotCity``` 29 | #### 使用说明 30 | - ```train/getAllCity```获取所有的火车站信息,建议每天拉取一次保存到本地,以免影响性能。 31 | - ```train/getHotCity```获取热点火车站信息,建议每天拉取一次保存到本地,以免影响性能。 32 | #### 请求参数 33 | 无 34 | #### 响应参数 35 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 36 | | :------:| :------:| :------: | :------: |:------: | 37 | | Stations | 火车站列表 | Station[] | N | | 38 | 39 | Station 40 | 41 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 42 | | :------:| :------:| :------: | :------: |:------: | 43 | | Name | 站点名 | String | N | | 44 | | StationCode | 车站代码 | String | N | | 45 | | pingYin | 车站拼音 | String | N | | 46 | | PingYinShort | 车站简拼 | String | N | | 47 | 48 | ### 3.2 关键字搜索车站 49 | #### 使用说明 50 | #### 方法名称 51 | - ```train/searchCity``` 52 | #### 使用说明 53 | - 使用关键字搜索匹配到的车站 54 | #### 请求参数 55 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 56 | | :------:| :------:| :------: | :------: |:------: | 57 | | Keyword | 关键字 | String | N | | 58 | #### 响应参数 59 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 60 | | :------:| :------:| :------: | :------: |:------: | 61 | | Stations | 火车站列表 | Station[] | N | | 62 | 63 | Station 64 | 65 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 66 | | :------:| :------:| :------: | :------: |:------: | 67 | | Name | 站点名 | String | N | | 68 | | StationCode | 车站代码 | String | N | | 69 | | pingYin | 车站拼音 | String | N | | 70 | | PingYinShort | 车站简拼 | String | N | | 71 | 72 | ### 3.3 火车票查询 73 | #### 使用说明 74 | #### 方法名称 75 | - ```train/getTicketList``` 76 | #### 使用说明 77 | - 查询火车票列表 78 | #### 请求参数 79 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 80 | | :------:| :------:| :------: | :------: |:------: | 81 | | FromStationCode | 出发站点代码 | String | N | | 82 | | ToStationCode | 到达站点代码 | String | N | | 83 | | FromDate | 出发日期(格式:yyyy-mm-dd) | String | N | | 84 | | IsStudent | 是否是学生票(默认成人票) | Boolean | N | | 85 | #### 响应参数 86 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 87 | | :------:| :------:| :------: | :------: |:------: | 88 | | Tickets | 车票信息列表 | Ticket[] | N | | 89 | 90 | Ticket 91 | 92 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 93 | | :------:| :------:| :------: | :------: |:------: | 94 | | TrainNo | 列车号 | String | N | | 95 | | TrainCode | 车次代码 | String | N | | 96 | | TrainType | 车次类型 | String | N | | 97 | | FromStation | 出发站点名字 | String | N | | 98 | | ToStation | 到达站点名字 | String | N | | 99 | | FromStationType | 出发站点类型名 "始-终-过"类型 | String | N | | 100 | | ToStationType | 到达站点类型名 "始-终-过"类型 | String | N | | 101 | | FromTime | 出发时间 | String | N | | 102 | | ToTime | 到达时间 | String | N | | 103 | | RunTime | 运行时间 | String | N | | 104 | | CanBook | 该车次是否有余票可以预定,所有席别无票则为false | Boolean | N | | 105 | | SwzNum | 商务座/特等座剩余票数| String | N | (有)表示充足 (--)表示无此类型的座位 数字表示剩余座位数 下同 | 106 | | SwzPrice | 商务座/特等座价格 | BigDecimal | N | | 107 | | YdzNum | 一等座剩余票数| String | N | | 108 | | YdzPrice | 一等座价格 | BigDecimal | N | | 109 | | EdzNum | 二等座剩余票数| String | N | | 110 | | EdzPrice | 二等座价格 | BigDecimal | N | | 111 | | GjrwNum | 高级软卧剩余票数| String | N | | 112 | | GjrwPrice | 高级软卧价格 | BigDecimal | N | | 113 | | RwNum | 软卧/一等卧价格| String | N | | 114 | | RwPrice | 软卧/一等卧剩余票数 | BigDecimal | N | | 115 | | DwNum | 动卧剩余票数| String | N | | 116 | | DwPrice | 动卧价格 | BigDecimal | N | | 117 | | YwNum | 硬卧剩余票数| String | N | | 118 | | YwPrice | 硬卧价格 | BigDecimal | N | | 119 | | RzNum | 软座剩余票数| String | N | | 120 | | RzPrice | 软座价格 | BigDecimal | N | | 121 | | YzNum | 硬座剩余票数| String | N | | 122 | | YzPrice | 硬座价格 | BigDecimal | N | | 123 | | WzNum | 无座剩余票数| String | N | | 124 | | WzPrice | 无座价格 | BigDecimal | N | | 125 | | QtNum | 其他剩余票数| String | N | | 126 | | QtPrice | 其他价格 | BigDecimal | N | | 127 | 128 | ### 3.4 查询车次经停信息 129 | #### 使用说明 130 | #### 方法名称 131 | - ```train/getTrainLine``` 132 | #### 使用说明 133 | - 查询车次经停信息 134 | #### 请求参数 135 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 136 | | :------:| :------:| :------: | :------: |:------: | 137 | | TrainCode | 车次代码 | String | N | | 138 | | FromStationCode | 出发站点代码 | String | N | | 139 | | ToStationCode | 到达站点代码 | String | N | | 140 | | FromDate | 出发日期(格式:yyyy-mm-dd) | String | N | | 141 | #### 响应参数 142 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 143 | | :------:| :------:| :------: | :------: |:------: | 144 | | Stops | 经停信息列表 | Stop[] | N | | 145 | | StartStationName | 始发站名 | String | N | | 146 | | EndStationName | 终点站名 | String | N | | 147 | | TrainCode | 车次代码 | String | N | | 148 | | TrainClassName | 车次类型 例如:快速 | String | N | | 149 | | ServiceName | 服务类型 | String | N | 例如:"无空调" , "有空调" | 150 | 151 | Stop 152 | 153 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 154 | | :------:| :------:| :------: | :------: |:------: | 155 | | StartTime | 出发时间(格式 HH:mm) | String | N | | 156 | | ArriveTime | 到达时间(格式 HH:mm 或者----) | String | N | | 157 | | StationName | 到达站名 | String | N | | 158 | | StopoverTime | 停留时间(分钟) 可能为---- | String | N | | 159 | | StationNo | 站序(01开始) | String | N | | 160 | | IsSearchStation | 是否是我们搜索的出行站和到达站 | String | N | false不是 true是 | 161 | 162 | ### 3.5 查询某个站点的途经列车时 163 | #### 使用说明 164 | #### 方法名称 165 | - ```train/getTrainStationTimeTable``` 166 | #### 使用说明 167 | - 查询某个站点的途经列车时 168 | #### 请求参数 169 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 170 | | :------:| :------:| :------: | :------: |:------: | 171 | | TrainStationName | 站点名称 | String | N | | 172 | | TrainStationCode | 站点代码 | String | N | | 173 | | TrainStartDate | 出发日期(格式:yyyy-mm-dd) | String | N | | 174 | #### 响应参数 175 | | 节点 | 名称 | 类型 | 可为空 | 说明 | 176 | | :------:| :------:| :------: | :------: |:------: | 177 | | startTrainDate | 发车时间(猜测意思) | | N | 例如:20190309| 178 | | trainNo | 列车号 | String | N | 例如:5l000D323501| 179 | | startStationTelecode | 始发站 站点代码 | String | N | 例如:NKH| 180 | | startStationName | 始发站 | String | N | 例如:南京南| 181 | | startStartTime | 始发站发车时间 | String | N | 例如:15:08| 182 | | endStationTelecode | 终到站 站点代码 | String | N | 例如:FYS | 183 | | endStationName | 终到站 | String | N | 例如:福州南 | 184 | | endArriveTime | 终到站到达时间 | String | N | 例如:22:26 | 185 | | trainTypeCode | | String | N | 例如:2 | 186 | | trainTypeName | | String | N | 例如:直通| 187 | | trainClassCode | 车次类型代码 | String | N | 例如:D | 188 | | trainClassName | 车次类型 | String | N | 例如:动车 | 189 | | seatTypes | 座位类型 | String | N | 例如:12582912 | 190 | | serviceType | 服务类型 | String | N | 例如:2 | 191 | | serviceTypeStr | 服务类型中文,对上一属性的解释(猜测) | String | N | 例如:有空调 | 192 | | stationNo | 查询站点在次列车中的位置 | String | N | 例如:01 | 193 | | stationName | 查询站点名称 | String | N | 例如:南京南 | 194 | | stationTelecode | 查询站点代码 | String | N | 例如:NKH | 195 | | stationTrainCode | 途经车次号 | String | N | 例如:D3235 | 196 | | arriveDayDiff | 到达日差异天数(猜测) | String | N | 例如:0 | 197 | | arriveTime | 到达查询站点时间 | String | N | 例如:16:27 | 198 | | startTime | 查询站点发车时间 | String | N | 例如:16:34 | 199 | | startDayDiff | 发车日差异天数(猜测) | String | N | 例如:0 | 200 | | stopoverTime | 查询站点停靠时间 | String | N | 例如:7 | 201 | | runningTime | 到达查询站点时,列车运行时间 | String | N | 例如:52分 | 202 | 203 | ### 3.6 获取所有 车次 - 列车号 对应关系表 204 | #### 方法名称 205 | - ```train/getTrainCode``` 206 | #### 使用说明 207 | - ```train/getTrainCode```获取所有 车次 - 列车号 对应关系信息,拉取完信息存入redis,redis有效期为一天。 208 | #### 请求参数 209 | 无 210 | #### 响应示例 211 | ```json 212 | { 213 | "C6628" : "76000C662801", 214 | ...... 215 | } 216 | ``` 217 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/TrainStationTimeTable.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | /** 8 | * 列车时刻Entity 9 | * 10 | * Created on 2019/03/07 09:52:18 11 | * 12 | * @author 猎隼丶止戈 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class TrainStationTimeTable { 16 | 17 | /** 18 | * 发车时间(猜测意思) 19 | */ 20 | private String startTrainDate; 21 | 22 | /** 23 | * 列车号 24 | */ 25 | private String trainNo; 26 | 27 | /** 28 | * 始发站 站点代码 29 | */ 30 | private String startStationTelecode; 31 | 32 | /** 33 | * 始发站 34 | */ 35 | private String startStationName; 36 | 37 | /** 38 | * 始发站发车时间 39 | */ 40 | private String startStartTime; 41 | 42 | /** 43 | * 终到站 站点代码 44 | */ 45 | private String endStationTelecode; 46 | 47 | /** 48 | * 终到站 49 | */ 50 | private String endStationName; 51 | 52 | /** 53 | * 终到站到达时间 54 | */ 55 | private String endArriveTime; 56 | 57 | /** 58 | * 59 | */ 60 | private String trainTypeCode; 61 | 62 | /** 63 | * 64 | */ 65 | private String trainTypeName; 66 | 67 | /** 68 | * 车次类型代码 69 | */ 70 | private String trainClassCode; 71 | 72 | /** 73 | * 车次类型 74 | */ 75 | private String trainClassName; 76 | 77 | /** 78 | * 座位类型 79 | */ 80 | private String seatTypes; 81 | 82 | /** 83 | * 服务类型 84 | */ 85 | private String serviceType; 86 | 87 | /** 88 | * 服务类型中文猜测 89 | */ 90 | private String serviceTypeStr; 91 | 92 | /** 93 | * 查询站点在次列车中的位置 94 | */ 95 | private String stationNo; 96 | 97 | /** 98 | * 查询站点名称 99 | */ 100 | private String stationName; 101 | 102 | /** 103 | * 查询站点代码 104 | */ 105 | private String stationTelecode; 106 | 107 | /** 108 | * 途经车次号 109 | */ 110 | private String stationTrainCode; 111 | 112 | /** 113 | * 到达日差异天数(猜测) 114 | */ 115 | private String arriveDayDiff; 116 | 117 | /** 118 | * 到达查询站点时间 119 | */ 120 | private String arriveTime; 121 | 122 | /** 123 | * 查询站点发车时间 124 | */ 125 | private String startTime; 126 | 127 | /** 128 | * 发车日差异天数(猜测) 129 | */ 130 | private String startDayDiff; 131 | 132 | /** 133 | * 查询站点停靠时间 134 | */ 135 | private String stopoverTime; 136 | 137 | /** 138 | * 到达查询站点时,列车运行时间 139 | */ 140 | private String runningTime; 141 | 142 | public String getStartTrainDate() { 143 | return startTrainDate; 144 | } 145 | 146 | public void setStartTrainDate(String startTrainDate) { 147 | this.startTrainDate = startTrainDate; 148 | } 149 | 150 | public String getTrainNo() { 151 | return trainNo; 152 | } 153 | 154 | public void setTrainNo(String trainNo) { 155 | this.trainNo = trainNo; 156 | } 157 | 158 | public String getStartStationTelecode() { 159 | return startStationTelecode; 160 | } 161 | 162 | public void setStartStationTelecode(String startStationTelecode) { 163 | this.startStationTelecode = startStationTelecode; 164 | } 165 | 166 | public String getStartStationName() { 167 | return startStationName; 168 | } 169 | 170 | public void setStartStationName(String startStationName) { 171 | this.startStationName = startStationName; 172 | } 173 | 174 | public String getStartStartTime() { 175 | return startStartTime; 176 | } 177 | 178 | public void setStartStartTime(String startStartTime) { 179 | this.startStartTime = startStartTime; 180 | } 181 | 182 | public String getEndStationTelecode() { 183 | return endStationTelecode; 184 | } 185 | 186 | public void setEndStationTelecode(String endStationTelecode) { 187 | this.endStationTelecode = endStationTelecode; 188 | } 189 | 190 | public String getEndStationName() { 191 | return endStationName; 192 | } 193 | 194 | public void setEndStationName(String endStationName) { 195 | this.endStationName = endStationName; 196 | } 197 | 198 | public String getEndArriveTime() { 199 | return endArriveTime; 200 | } 201 | 202 | public void setEndArriveTime(String endArriveTime) { 203 | this.endArriveTime = endArriveTime; 204 | } 205 | 206 | public String getTrainTypeCode() { 207 | return trainTypeCode; 208 | } 209 | 210 | public void setTrainTypeCode(String trainTypeCode) { 211 | this.trainTypeCode = trainTypeCode; 212 | } 213 | 214 | public String getTrainTypeName() { 215 | return trainTypeName; 216 | } 217 | 218 | public void setTrainTypeName(String trainTypeName) { 219 | this.trainTypeName = trainTypeName; 220 | } 221 | 222 | public String getTrainClassCode() { 223 | return trainClassCode; 224 | } 225 | 226 | public void setTrainClassCode(String trainClassCode) { 227 | this.trainClassCode = trainClassCode; 228 | } 229 | 230 | public String getTrainClassName() { 231 | return trainClassName; 232 | } 233 | 234 | public void setTrainClassName(String trainClassName) { 235 | this.trainClassName = trainClassName; 236 | } 237 | 238 | public String getStationNo() { 239 | return stationNo; 240 | } 241 | 242 | public void setStationNo(String stationNo) { 243 | this.stationNo = stationNo; 244 | } 245 | 246 | public String getStationName() { 247 | return stationName; 248 | } 249 | 250 | public void setStationName(String stationName) { 251 | this.stationName = stationName; 252 | } 253 | 254 | public String getStationTelecode() { 255 | return stationTelecode; 256 | } 257 | 258 | public void setStationTelecode(String stationTelecode) { 259 | this.stationTelecode = stationTelecode; 260 | } 261 | 262 | public String getStationTrainCode() { 263 | return stationTrainCode; 264 | } 265 | 266 | public void setStationTrainCode(String stationTrainCode) { 267 | this.stationTrainCode = stationTrainCode; 268 | } 269 | 270 | public String getArriveDayDiff() { 271 | return arriveDayDiff; 272 | } 273 | 274 | public void setArriveDayDiff(String arriveDayDiff) { 275 | this.arriveDayDiff = arriveDayDiff; 276 | } 277 | 278 | public String getArriveTime() { 279 | return arriveTime; 280 | } 281 | 282 | public void setArriveTime(String arriveTime) { 283 | this.arriveTime = arriveTime; 284 | } 285 | 286 | public String getStartTime() { 287 | return startTime; 288 | } 289 | 290 | public void setStartTime(String startTime) { 291 | this.startTime = startTime; 292 | } 293 | 294 | public String getStartDayDiff() { 295 | return startDayDiff; 296 | } 297 | 298 | public void setStartDayDiff(String startDayDiff) { 299 | this.startDayDiff = startDayDiff; 300 | } 301 | 302 | public String getStopoverTime() { 303 | return stopoverTime; 304 | } 305 | 306 | public void setStopoverTime(String stopoverTime) { 307 | this.stopoverTime = stopoverTime; 308 | } 309 | 310 | public String getRunningTime() { 311 | return runningTime; 312 | } 313 | 314 | public void setRunningTime(String runningTime) { 315 | this.runningTime = runningTime; 316 | } 317 | 318 | public String getSeatTypes() { 319 | return seatTypes; 320 | } 321 | 322 | public void setSeatTypes(String seatTypes) { 323 | this.seatTypes = seatTypes; 324 | } 325 | 326 | public String getServiceType() { 327 | return serviceType; 328 | } 329 | 330 | public void setServiceType(String serviceType) { 331 | this.serviceType = serviceType; 332 | } 333 | 334 | public String getServiceTypeStr() { 335 | return serviceTypeStr; 336 | } 337 | 338 | public void setServiceTypeStr(String serviceTypeStr) { 339 | this.serviceTypeStr = serviceTypeStr; 340 | } 341 | 342 | @Override 343 | public String toString() { 344 | return MoreObjects.toStringHelper(this) 345 | .add("startTrainDate", startTrainDate) 346 | .add("trainNo", trainNo) 347 | .add("startStationTelecode", startStationTelecode) 348 | .add("startStationName", startStationName) 349 | .add("startStartTime", startStartTime) 350 | .add("endStationTelecode", endStationTelecode) 351 | .add("endStationName", endStationName) 352 | .add("endArriveTime", endArriveTime) 353 | .add("trainTypeCode", trainTypeCode) 354 | .add("trainTypeName", trainTypeName) 355 | .add("trainClassCode", trainClassCode) 356 | .add("trainClassName", trainClassName) 357 | .add("stationNo", stationNo) 358 | .add("stationName", stationName) 359 | .add("stationTelecode", stationTelecode) 360 | .add("stationTrainCode", stationTrainCode) 361 | .add("arriveDayDiff", arriveDayDiff) 362 | .add("arriveTime", arriveTime) 363 | .add("startTime", startTime) 364 | .add("startDayDiff", startDayDiff) 365 | .add("stopoverTime", stopoverTime) 366 | .add("runningTime", runningTime) 367 | .toString(); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/model/response/Ticket.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.model.response; 2 | 3 | import com.alibaba.fastjson.PropertyNamingStrategy; 4 | import com.alibaba.fastjson.annotation.JSONType; 5 | import com.google.common.base.MoreObjects; 6 | 7 | import java.math.BigDecimal; 8 | 9 | /** 10 | * Created on 2019/1/10 20:38. 11 | * 12 | * @author caogu 13 | */ 14 | @JSONType(naming = PropertyNamingStrategy.PascalCase) 15 | public class Ticket { 16 | 17 | /** 18 | * 列车号 19 | */ 20 | private String trainNo; 21 | 22 | /** 23 | * 车次代码 24 | */ 25 | private String trainCode; 26 | 27 | /** 28 | * 车次类型 29 | */ 30 | private String trainType; 31 | 32 | /** 33 | * 出发站点名字 34 | */ 35 | private String fromStation; 36 | 37 | /** 38 | * 到达站点名字 39 | */ 40 | private String toStation; 41 | 42 | /** 43 | * 出发站点类型名 "始|终|过"类型 44 | */ 45 | private String fromStationType; 46 | 47 | /** 48 | * 到达站点类型名 "始|终|过"类型 49 | */ 50 | private String toStationType; 51 | 52 | /** 53 | * 出发时间 54 | */ 55 | private String fromTime; 56 | 57 | /** 58 | * 到达时间 59 | */ 60 | private String toTime; 61 | 62 | /** 63 | * 运行时间 64 | */ 65 | private String runTime; 66 | 67 | /** 68 | * 该车次是否有余票可以预定,所有席别无票则为false 69 | */ 70 | private Boolean canBook; 71 | 72 | /** 73 | * 商务座/特等座剩余票数 74 | *

可能值 75 | * 有|表示充足 76 | * --|表示无此类型的座位 77 | * 数字表示剩余座位数 ) 78 | *

79 | *

下同

80 | */ 81 | private String swzNum; 82 | 83 | /** 84 | * 商务座/特等座价格 85 | */ 86 | private BigDecimal swzPrice; 87 | 88 | /** 89 | * 一等座剩余票数 90 | */ 91 | private String ydzNum; 92 | 93 | /** 94 | * 一等座价格 95 | */ 96 | private BigDecimal ydzPrice; 97 | 98 | /** 99 | * 二等座剩余票数 100 | */ 101 | private String edzNum; 102 | 103 | /** 104 | * 二等座价格 105 | */ 106 | private BigDecimal edzPrice; 107 | 108 | /** 109 | * 高级软卧剩余票数 110 | */ 111 | private String gjrwNum; 112 | 113 | /** 114 | * 高级软卧价格 115 | */ 116 | private BigDecimal gjrwPrice; 117 | 118 | /** 119 | * 软卧/一等卧价格 120 | */ 121 | private String rwNum; 122 | 123 | /** 124 | * 软卧/一等卧剩余票数 125 | */ 126 | private BigDecimal rwPrice; 127 | 128 | /** 129 | * 动卧剩余票数 130 | */ 131 | private String dwNum; 132 | 133 | /** 134 | * 动卧价格 135 | */ 136 | private BigDecimal dwPrice; 137 | 138 | /** 139 | * 硬卧剩余票数 140 | */ 141 | private String ywNum; 142 | 143 | /** 144 | * 硬卧价格 145 | */ 146 | private BigDecimal ywPrice; 147 | 148 | /** 149 | * 软座剩余票数 150 | */ 151 | private String rzNum; 152 | 153 | /** 154 | * 软座价格 155 | */ 156 | private BigDecimal rzPrice; 157 | 158 | /** 159 | * 硬座剩余票数 160 | */ 161 | private String yzNum; 162 | 163 | /** 164 | * 硬座价格 165 | */ 166 | private BigDecimal yzPrice; 167 | 168 | /** 169 | * 无座剩余票数 170 | */ 171 | private String wzNum; 172 | 173 | /** 174 | * 无座价格 175 | */ 176 | private BigDecimal wzPrice; 177 | 178 | /** 179 | * 其他剩余票数 180 | */ 181 | private String qtNum; 182 | 183 | /** 184 | * 其他价格 185 | */ 186 | private BigDecimal qtPrice; 187 | 188 | public String getTrainNo() { 189 | return trainNo; 190 | } 191 | 192 | public void setTrainNo(String trainNo) { 193 | this.trainNo = trainNo; 194 | } 195 | 196 | public String getTrainCode() { 197 | return trainCode; 198 | } 199 | 200 | public void setTrainCode(String trainCode) { 201 | this.trainCode = trainCode; 202 | } 203 | 204 | public String getTrainType() { 205 | return trainType; 206 | } 207 | 208 | public void setTrainType(String trainType) { 209 | this.trainType = trainType; 210 | } 211 | 212 | public String getFromStation() { 213 | return fromStation; 214 | } 215 | 216 | public void setFromStation(String fromStation) { 217 | this.fromStation = fromStation; 218 | } 219 | 220 | public String getToStation() { 221 | return toStation; 222 | } 223 | 224 | public void setToStation(String toStation) { 225 | this.toStation = toStation; 226 | } 227 | 228 | public String getFromStationType() { 229 | return fromStationType; 230 | } 231 | 232 | public void setFromStationType(String fromStationType) { 233 | this.fromStationType = fromStationType; 234 | } 235 | 236 | public String getToStationType() { 237 | return toStationType; 238 | } 239 | 240 | public void setToStationType(String toStationType) { 241 | this.toStationType = toStationType; 242 | } 243 | 244 | public String getFromTime() { 245 | return fromTime; 246 | } 247 | 248 | public void setFromTime(String fromTime) { 249 | this.fromTime = fromTime; 250 | } 251 | 252 | public String getToTime() { 253 | return toTime; 254 | } 255 | 256 | public void setToTime(String toTime) { 257 | this.toTime = toTime; 258 | } 259 | 260 | public String getRunTime() { 261 | return runTime; 262 | } 263 | 264 | public void setRunTime(String runTime) { 265 | this.runTime = runTime; 266 | } 267 | 268 | public Boolean getCanBook() { 269 | return canBook; 270 | } 271 | 272 | public void setCanBook(Boolean canBook) { 273 | this.canBook = canBook; 274 | } 275 | 276 | public String getSwzNum() { 277 | return swzNum; 278 | } 279 | 280 | public void setSwzNum(String swzNum) { 281 | this.swzNum = swzNum; 282 | } 283 | 284 | public BigDecimal getSwzPrice() { 285 | return swzPrice; 286 | } 287 | 288 | public void setSwzPrice(BigDecimal swzPrice) { 289 | this.swzPrice = swzPrice; 290 | } 291 | 292 | public String getYdzNum() { 293 | return ydzNum; 294 | } 295 | 296 | public void setYdzNum(String ydzNum) { 297 | this.ydzNum = ydzNum; 298 | } 299 | 300 | public BigDecimal getYdzPrice() { 301 | return ydzPrice; 302 | } 303 | 304 | public void setYdzPrice(BigDecimal ydzPrice) { 305 | this.ydzPrice = ydzPrice; 306 | } 307 | 308 | public String getEdzNum() { 309 | return edzNum; 310 | } 311 | 312 | public void setEdzNum(String edzNum) { 313 | this.edzNum = edzNum; 314 | } 315 | 316 | public BigDecimal getEdzPrice() { 317 | return edzPrice; 318 | } 319 | 320 | public void setEdzPrice(BigDecimal edzPrice) { 321 | this.edzPrice = edzPrice; 322 | } 323 | 324 | public String getGjrwNum() { 325 | return gjrwNum; 326 | } 327 | 328 | public void setGjrwNum(String gjrwNum) { 329 | this.gjrwNum = gjrwNum; 330 | } 331 | 332 | public BigDecimal getGjrwPrice() { 333 | return gjrwPrice; 334 | } 335 | 336 | public void setGjrwPrice(BigDecimal gjrwPrice) { 337 | this.gjrwPrice = gjrwPrice; 338 | } 339 | 340 | public String getRwNum() { 341 | return rwNum; 342 | } 343 | 344 | public void setRwNum(String rwNum) { 345 | this.rwNum = rwNum; 346 | } 347 | 348 | public BigDecimal getRwPrice() { 349 | return rwPrice; 350 | } 351 | 352 | public void setRwPrice(BigDecimal rwPrice) { 353 | this.rwPrice = rwPrice; 354 | } 355 | 356 | public String getDwNum() { 357 | return dwNum; 358 | } 359 | 360 | public void setDwNum(String dwNum) { 361 | this.dwNum = dwNum; 362 | } 363 | 364 | public BigDecimal getDwPrice() { 365 | return dwPrice; 366 | } 367 | 368 | public void setDwPrice(BigDecimal dwPrice) { 369 | this.dwPrice = dwPrice; 370 | } 371 | 372 | public String getYwNum() { 373 | return ywNum; 374 | } 375 | 376 | public void setYwNum(String ywNum) { 377 | this.ywNum = ywNum; 378 | } 379 | 380 | public BigDecimal getYwPrice() { 381 | return ywPrice; 382 | } 383 | 384 | public void setYwPrice(BigDecimal ywPrice) { 385 | this.ywPrice = ywPrice; 386 | } 387 | 388 | public String getRzNum() { 389 | return rzNum; 390 | } 391 | 392 | public void setRzNum(String rzNum) { 393 | this.rzNum = rzNum; 394 | } 395 | 396 | public BigDecimal getRzPrice() { 397 | return rzPrice; 398 | } 399 | 400 | public void setRzPrice(BigDecimal rzPrice) { 401 | this.rzPrice = rzPrice; 402 | } 403 | 404 | public String getYzNum() { 405 | return yzNum; 406 | } 407 | 408 | public void setYzNum(String yzNum) { 409 | this.yzNum = yzNum; 410 | } 411 | 412 | public BigDecimal getYzPrice() { 413 | return yzPrice; 414 | } 415 | 416 | public void setYzPrice(BigDecimal yzPrice) { 417 | this.yzPrice = yzPrice; 418 | } 419 | 420 | public String getWzNum() { 421 | return wzNum; 422 | } 423 | 424 | public void setWzNum(String wzNum) { 425 | this.wzNum = wzNum; 426 | } 427 | 428 | public BigDecimal getWzPrice() { 429 | return wzPrice; 430 | } 431 | 432 | public void setWzPrice(BigDecimal wzPrice) { 433 | this.wzPrice = wzPrice; 434 | } 435 | 436 | public String getQtNum() { 437 | return qtNum; 438 | } 439 | 440 | public void setQtNum(String qtNum) { 441 | this.qtNum = qtNum; 442 | } 443 | 444 | public BigDecimal getQtPrice() { 445 | return qtPrice; 446 | } 447 | 448 | public void setQtPrice(BigDecimal qtPrice) { 449 | this.qtPrice = qtPrice; 450 | } 451 | 452 | @Override 453 | public String toString() { 454 | return MoreObjects.toStringHelper(this) 455 | .add("trainNo",trainNo) 456 | .add("trainCode", trainCode) 457 | .add("trainType", trainType) 458 | .add("fromStation", fromStation) 459 | .add("toStation", toStation) 460 | .add("fromStationType", fromStationType) 461 | .add("toStationType", toStationType) 462 | .add("fromTime", fromTime) 463 | .add("toTime", toTime) 464 | .add("runTime", runTime) 465 | .add("canBook", canBook) 466 | .add("swzNum", swzNum) 467 | .add("swzPrice", swzPrice) 468 | .add("ydzNum", ydzNum) 469 | .add("ydzPrice", ydzPrice) 470 | .add("edzNum", edzNum) 471 | .add("edzPrice", edzPrice) 472 | .add("gjrwNum", gjrwNum) 473 | .add("gjrwPrice", gjrwPrice) 474 | .add("rwNum", rwNum) 475 | .add("rwPrice", rwPrice) 476 | .add("dwNum", dwNum) 477 | .add("dwPrice", dwPrice) 478 | .add("ywNum", ywNum) 479 | .add("ywPrice", ywPrice) 480 | .add("rzNum", rzNum) 481 | .add("rzPrice", rzPrice) 482 | .add("yzNum", yzNum) 483 | .add("yzPrice", yzPrice) 484 | .add("wzNum", wzNum) 485 | .add("wzPrice", wzPrice) 486 | .add("qtNum", qtNum) 487 | .add("qtPrice", qtPrice) 488 | .toString(); 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /src/test/java/com/sinosun/train/TrainTest.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.google.common.base.Splitter; 7 | import com.google.common.collect.Maps; 8 | import com.sinosun.train.utils.HttpUtil; 9 | import com.sinosun.train.utils.JsonUtil; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.jsoup.Connection; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import java.math.BigDecimal; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * Created on 2019/1/15 19:46. 22 | * 23 | * @author caogu 24 | */ 25 | public class TrainTest { 26 | 27 | private static RestTemplate restTemplate; 28 | 29 | @BeforeClass 30 | public static void startUp() 31 | { 32 | restTemplate = new RestTemplate(); 33 | } 34 | 35 | 36 | private static String fromDate = "2019-01-28"; //出发时间(格式:yyyy-mm-dd) 37 | private static String fromStation = "XAY"; //出发站代号 38 | private static String toStation = "XYY"; //到达站代号 39 | private static String purposeCodes ="ADULT"; //乘客类型(成人:ADULT,学生:0X00) 40 | 41 | private static String trainNo = "630000K2260L"; //列车号 42 | private static String startTrainDate = "2019-01-08"; //列车起始站发成日期? 43 | 44 | private static String startStationNo = "24"; //出发站序 45 | private static String endStationNo = "25"; //到达站序 46 | private static String seat_types = "1413"; //车票列表倒数第三个参数 47 | 48 | private static String baseUrl = "https://kyfw.12306.cn"; 49 | private static String publicName = "/otn"; 50 | private static String leftTicketUrl ="leftTicket/queryA"; 51 | 52 | //注:12306的查询接口经常改变(可能一天一改),其变动的主要规律为:https://kyfw.12306.cn/otn/leftTicket/query[A-Z],就是最后一个字母做变动。 53 | // 因此,如果程序查询出异常,很大可能就是接口改变了,重新抓下查询接口即可。 54 | private static String getTicketListUrlFmt = baseUrl + publicName + "/%s?" + 55 | "leftTicketDTO.train_date=%s&" + 56 | "leftTicketDTO.from_station=%s&" + 57 | "leftTicketDTO.to_station=%s&" + 58 | "purpose_codes=%s"; 59 | 60 | 61 | private static String getTicketPriceUrlFmt = baseUrl + publicName + "/leftTicket/queryTicketPrice?" + 62 | "train_no=%s&" + 63 | "from_station_no=%s&" + 64 | "to_station_no=%s&" + 65 | "seat_types=%s&" + 66 | "train_date=%s"; 67 | 68 | private static String getTicketLineUrlFmt = baseUrl + publicName + "/czxx/queryByTrainNo?" + 69 | "train_no=%s&" + 70 | "from_station_telecode=%s&" + 71 | "to_station_telecode=%s&" + 72 | "depart_date=%s"; 73 | 74 | @Test 75 | public void getTrainList() 76 | { 77 | String getTicketListUrl = String.format(getTicketListUrlFmt, leftTicketUrl, fromDate, fromStation, toStation, purposeCodes); 78 | JSONObject ret = JsonUtil.parseObject(HttpUtil.request(getTicketListUrl, Connection.Method.GET, null)); 79 | System.out.println(JSON.toJSONString(ret, true)); 80 | 81 | // 接口地址变化,获取新地址重新请求 可通过302错误码判断 302 redirect: 302 代表暂时性转移(Temporarily Moved ) 82 | if (ret.containsKey("c_url")) { 83 | leftTicketUrl = ret.getString("c_url"); 84 | getTicketListUrl = String.format(getTicketListUrlFmt, leftTicketUrl, fromDate, fromStation, toStation, purposeCodes); 85 | ret = JsonUtil.parseObject(HttpUtil.request(getTicketListUrl, Connection.Method.GET, null)); 86 | System.out.println(JSON.toJSONString(ret, true)); 87 | } 88 | 89 | JSONObject data = ret.getJSONObject("data"); 90 | JSONObject map = data.getJSONObject("datamap"); //站点代码和名字映射 91 | JSONArray result = data.getJSONArray("result"); 92 | 93 | for (int i = 0; i< result.size(); i++) { 94 | String train = result.getString(i); 95 | List trainItem = Splitter.on("|").splitToList(train); 96 | 97 | String secretStr = trainItem.get(0); //? 98 | String buttonTextInfo = trainItem.get(1); //按钮名字:预订 99 | 100 | String trainNo = trainItem.get(2); //列车号 101 | String trainCode = trainItem.get(3); //车次 102 | 103 | String startStationCode = trainItem.get(4); // 起始站代码 104 | String endStationCode = trainItem.get(5); // 结束站代码 105 | 106 | String fromStationCode = trainItem.get(6); //出发站代码 107 | String toStationCode = trainItem.get(7); //到达站代码 108 | String fromStationName = map.getString(fromStationCode); //出发站 109 | String toStationName = map.getString(toStationCode); //到达站 110 | 111 | String startTime = trainItem.get(8); //出发时刻 112 | String arriveTime = trainItem.get(9); //到达时刻 113 | String runTime = trainItem.get(10); //历时 114 | 115 | String canWebBuy = trainItem.get(11); //是否能购买:Y 可以 N 不可 IS_TIME_NOT_BUY 列车运行图调整,暂停发售/列车停运 116 | String ypInfo = trainItem.get(12); //? 117 | 118 | String startTrainDate = trainItem.get(13); //列车起始站发成日期 119 | 120 | String trainSeatFeature = trainItem.get(14); //? 121 | String locationCode = trainItem.get(15); //? 122 | String fromStationNo = trainItem.get(16); //出发站站序(对应火车经停信息中的站序)01表示始发站,大于1则表示过站 123 | String toStationNo = trainItem.get(17); //到达站站序(对应火车经停信息中的站序) 124 | String isSupportCard = trainItem.get(18); //可凭二代身份证直接进出站 1 可以 0 不可以 125 | String controlledTrainFlag = trainItem.get(19); //? 126 | 127 | String ggNum = StringUtils.isNotEmpty(trainItem.get(20)) ? trainItem.get(20) : "--"; //? 128 | String grNum = StringUtils.isNotEmpty(trainItem.get(21)) ? trainItem.get(21) : "--"; // 高级软卧 129 | String qtNum = StringUtils.isNotEmpty(trainItem.get(22)) ? trainItem.get(22) : "--"; // 其他 130 | String rwNum = StringUtils.isNotEmpty(trainItem.get(23)) ? trainItem.get(23) : "--"; // 软卧,一等卧 131 | String rzNum = StringUtils.isNotEmpty(trainItem.get(24)) ? trainItem.get(24) : "--"; // 软座 132 | String tzNum = StringUtils.isNotEmpty(trainItem.get(25)) ? trainItem.get(25) : "--"; //? 特等座? 133 | String wzNum = StringUtils.isNotEmpty(trainItem.get(26)) ? trainItem.get(26) : "--"; // 无座 134 | String ybNum = StringUtils.isNotEmpty(trainItem.get(27)) ? trainItem.get(27) : "--"; //? 135 | String ywNum = StringUtils.isNotEmpty(trainItem.get(28)) ? trainItem.get(28) : "--"; // 硬卧,二等卧 136 | String yzNum = StringUtils.isNotEmpty(trainItem.get(29)) ? trainItem.get(29) : "--"; // 硬座 137 | String edzNum = StringUtils.isNotEmpty(trainItem.get(30)) ? trainItem.get(30) : "--"; // 二等座 138 | String ydzNum = StringUtils.isNotEmpty(trainItem.get(31)) ? trainItem.get(31) : "--"; // 一等座 139 | String swzNum = StringUtils.isNotEmpty(trainItem.get(32)) ? trainItem.get(32) : "--"; // 商务座特等座 140 | String srrbNum = StringUtils.isNotEmpty(trainItem.get(33)) ? trainItem.get(33) : "--"; // 动卧 141 | 142 | String yp_ex = trainItem.get(34); //? 查询车票价格时的seat_types字段 143 | String seatTypes = trainItem.get(35); //? 144 | String exchangeTrainFlag = trainItem.get(36); //? 145 | 146 | System.out.println("列车号:" + trainCode); 147 | System.out.println("出发站:" + fromStationName); 148 | System.out.println("到达站:" + toStationName); 149 | System.out.println("出发时间:" + startTime); 150 | System.out.println("到达时间:" + arriveTime); 151 | System.out.println("历时:" + runTime); 152 | System.out.println("列车起始站发车日期:" + startTrainDate); 153 | 154 | System.out.println("商务座特等座:" + swzNum); 155 | System.out.println("一等座:" + ydzNum); 156 | System.out.println("二等座:" + edzNum); 157 | System.out.println("高级软卧:" + grNum); 158 | System.out.println("软卧:" + rwNum); 159 | System.out.println("动卧:" + srrbNum); 160 | System.out.println("硬卧:" + ywNum); 161 | System.out.println("软座:" + rzNum); 162 | System.out.println("硬座:" + yzNum); 163 | System.out.println("无座:" + wzNum); 164 | System.out.println("其他:" + qtNum); 165 | System.out.println("备注:" + buttonTextInfo); 166 | System.out.println("\n"); 167 | } 168 | 169 | } 170 | 171 | 172 | @Test 173 | public void getTrainLine() { 174 | String getTicketLineUrl = String.format(getTicketLineUrlFmt, trainNo, fromStation, toStation, startTrainDate); 175 | JSONObject ret = JsonUtil.parseObject(HttpUtil.request(getTicketLineUrl, Connection.Method.GET, null)); 176 | System.out.println(JSON.toJSONString(ret, true)); 177 | 178 | JSONObject result = new JSONObject(); 179 | 180 | JSONArray stops = ret.getJSONObject("data").getJSONArray("data"); 181 | 182 | JSONObject stopInfoFirst = stops.getJSONObject(0); 183 | String startStationName = stopInfoFirst.getString("start_station_name"); //出发城市 184 | String endStationName = stopInfoFirst.getString("end_station_name"); //到达城市 185 | String stationTrainCode = stopInfoFirst.getString("station_train_code"); //车次号 186 | String trainClassName = stopInfoFirst.getString("train_class_name"); //车次类型, 快速等 187 | String serviceType = stopInfoFirst.getString("service_type"); //服务类型 0表示无空调 其他表示有空调 188 | String serviceName = "0".equals(serviceType) ? "无空调" : "有空调"; 189 | 190 | result.put("StartStationName", startStationName); 191 | result.put("EndStationName", endStationName); 192 | result.put("StationTrainCode", stationTrainCode); 193 | result.put("TrainClassName", trainClassName); 194 | result.put("ServiceType", serviceType); 195 | result.put("ServiceName", serviceName); 196 | 197 | JSONArray resultStops = new JSONArray(); 198 | for (int i = 0; i < stops.size(); i++) { 199 | JSONObject stopInfo = stops.getJSONObject(i); 200 | String startTime = stopInfo.getString("start_time"); //出发时间(格式 HH:mm) 201 | String arriveTime = stopInfo.getString("arrive_time"); //到达时间(格式 HH:mm 或者----) 202 | String stationName = stopInfo.getString("station_name"); //站名 203 | String stopoverTime = stopInfo.getString("stopover_time"); //停留时间(分钟) 可能为---- 204 | String stationNo = stopInfo.getString("station_no"); //站序(01开始) 205 | Boolean isEnabled = stopInfo.getBoolean("isEnabled"); //是否是我们搜索的出行站和到达站 false不是 true是 206 | 207 | JSONObject resultStopInfo = new JSONObject(); 208 | resultStopInfo.put("StartTime", startTime); 209 | resultStopInfo.put("ArriveTime", arriveTime); 210 | resultStopInfo.put("StationName", stationName); 211 | resultStopInfo.put("StopoverTime", stopoverTime); 212 | resultStopInfo.put("StationNo", stationNo); 213 | resultStopInfo.put("IsEnabled", isEnabled); 214 | resultStops.add(resultStopInfo); 215 | } 216 | result.put("Stops", resultStops); 217 | 218 | System.out.println(JSON.toJSONString(result, true)); 219 | 220 | } 221 | 222 | @Test 223 | public void queryTicketPrice() { 224 | String getTicketPriceUrl = String.format(getTicketPriceUrlFmt, trainNo, startStationNo, endStationNo, seat_types, fromDate); 225 | JSONObject ret = JsonUtil.parseObject(HttpUtil.request(getTicketPriceUrl, Connection.Method.GET, null)); 226 | System.out.println(JSON.toJSONString(ret, true)); 227 | 228 | JSONObject data = ret.getJSONObject("data"); 229 | 230 | // A9 商务座/特等座 231 | // M 一等座 232 | // O 二等座 233 | // A6 高级软卧 234 | // A4 软卧/一等卧 235 | // F 动卧 236 | // A3 硬卧/二等卧 237 | // A2 软座 (广州到汉口) 238 | // A1 硬座 239 | // WZ 无座 240 | Map setType = Maps.newHashMap(); 241 | setType.put("swz", "A9"); 242 | setType.put("ydz", "M"); 243 | setType.put("edz", "O"); 244 | setType.put("gr", "A6"); 245 | setType.put("rw", "A4"); 246 | setType.put("dw", "F"); 247 | setType.put("yw", "A3"); 248 | setType.put("rz", "A2"); 249 | setType.put("yz", "A1"); 250 | setType.put("wz", "WZ"); 251 | 252 | BigDecimal swzPrice = processPrice(data.getString(setType.get("swz"))); 253 | BigDecimal ydzPrice = processPrice(data.getString(setType.get("ydz"))); 254 | BigDecimal edzPrice = processPrice(data.getString(setType.get("edz"))); 255 | BigDecimal grPrice = processPrice(data.getString(setType.get("gr"))); 256 | BigDecimal rwPrice = processPrice(data.getString(setType.get("rw"))); 257 | BigDecimal dwPrice = processPrice(data.getString(setType.get("dw"))); 258 | BigDecimal ywPrice = processPrice(data.getString(setType.get("yw"))); 259 | BigDecimal rzPrice = processPrice(data.getString(setType.get("rz"))); 260 | BigDecimal yzPrice = processPrice(data.getString(setType.get("yz"))); 261 | BigDecimal wzPrice = processPrice(data.getString(setType.get("wz"))); 262 | 263 | System.out.println(swzPrice); 264 | System.out.println(ydzPrice); 265 | System.out.println(edzPrice); 266 | System.out.println(grPrice); 267 | System.out.println(grPrice); 268 | System.out.println(rwPrice); 269 | System.out.println(dwPrice); 270 | System.out.println(ywPrice); 271 | System.out.println(rzPrice); 272 | System.out.println(yzPrice); 273 | System.out.println(wzPrice); 274 | 275 | } 276 | 277 | private BigDecimal processPrice(String price) { 278 | BigDecimal ret = null; 279 | if (StringUtils.isNotEmpty(price) && price.startsWith("¥")) { 280 | ret = new BigDecimal(price.substring(1, price.length())); 281 | } 282 | return ret; 283 | } 284 | 285 | } 286 | -------------------------------------------------------------------------------- /src/main/java/com/sinosun/train/service/TrainTicketService.java: -------------------------------------------------------------------------------- 1 | package com.sinosun.train.service; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.google.common.base.Splitter; 6 | import com.google.common.collect.Lists; 7 | import com.sinosun.train.datamap.SeatTypeMap; 8 | import com.sinosun.train.datamap.TrainCodeTrainNoMap; 9 | import com.sinosun.train.enums.train.PassengerType; 10 | import com.sinosun.train.model.request.GetTicketListRequest; 11 | import com.sinosun.train.model.request.GetTrainLineRequest; 12 | import com.sinosun.train.model.response.*; 13 | import com.sinosun.train.model.vo.TicketPrice; 14 | import com.sinosun.train.utils.TrainHelper; 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.joda.time.DateTime; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.stereotype.Service; 21 | import org.springframework.util.CollectionUtils; 22 | 23 | import java.math.BigDecimal; 24 | import java.util.Date; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | /** 29 | * Created on 2019/1/10 21:00. 30 | * 31 | * @author caogu 32 | */ 33 | @Service 34 | public class TrainTicketService { 35 | private static final Logger logger = LoggerFactory.getLogger(TrainTicketService.class); 36 | 37 | @Autowired 38 | private TrainStationService trainStationService; 39 | 40 | private static final String DATE_FORMAT = "yyyy-MM-dd"; 41 | 42 | private static String baseUrl = "https://kyfw.12306.cn"; 43 | private static String publicName = "/otn"; 44 | private static String leftTicketUrl = "leftTicket/queryA"; 45 | private static String getTicketListUrlFmt = baseUrl + publicName + 46 | "/%s?" + 47 | "leftTicketDTO.train_date=%s&" + 48 | "leftTicketDTO.from_station=%s&" + 49 | "leftTicketDTO.to_station=%s&" + 50 | "purpose_codes=%s"; 51 | 52 | 53 | private static String getTicketPriceUrlFmt = baseUrl + publicName + 54 | "/leftTicket/queryTicketPrice?" + 55 | "train_no=%s&" + 56 | "from_station_no=%s&" + 57 | "to_station_no=%s&" + 58 | "seat_types=%s&" + 59 | "train_date=%s"; 60 | 61 | private static String getTicketLineUrlFmt = baseUrl + publicName + 62 | "/czxx/queryByTrainNo?" + 63 | "train_no=%s&" + 64 | "from_station_telecode=%s&" + 65 | "to_station_telecode=%s&" + 66 | "depart_date=%s"; 67 | 68 | 69 | public TicketListResult getTicketList(GetTicketListRequest requestBody) { 70 | requestBody.validate(); 71 | return new TicketListResult(new TicketList(getTicketListFrom12306(requestBody))); 72 | } 73 | 74 | public TrainLineResult getTrainLine(GetTrainLineRequest requestBody) { 75 | requestBody.validate(); 76 | String trainNo = TrainCodeTrainNoMap.getTrainNo(requestBody.getTrainCode()); 77 | // 在获取不到trainNo时,trainCode必须有值 78 | if (StringUtils.isEmpty(trainNo)) { 79 | TrainCodeResult trainCodeResult = trainStationService.getAllTrainCode(null); 80 | trainNo = (String) trainCodeResult.getResult().get(requestBody.getTrainCode()); 81 | } 82 | String fromDate = convertFromDate(requestBody.getFromDate()); 83 | return new TrainLineResult(getTrainLineFrom12306(trainNo, fromDate, requestBody.getFromStationCode(), requestBody.getToStationCode())); 84 | } 85 | 86 | 87 | /** 88 | * 从12306获取车票列表,先请求一次,若URL发生跳转则用新地址继续请求 89 | * @param requestBody 请求 90 | * @return 车票列表 91 | */ 92 | private List getTicketListFrom12306(GetTicketListRequest requestBody) { 93 | JSONObject ret12306 = TrainHelper.requestTo12306(getTicketListUrl(requestBody)); 94 | // 若接口地址变化,获取新地址重新请求 可通过302错误码判断 代表暂时性转移(Temporarily Moved ) redirect 95 | if (ret12306.containsKey("c_url")) { 96 | logger.warn("查询火车票接口发生跳转:{}", ret12306); 97 | leftTicketUrl = ret12306.getString("c_url"); 98 | ret12306 = TrainHelper.requestTo12306(getTicketListUrl(requestBody)); 99 | } 100 | 101 | JSONObject data = ret12306.getJSONObject("data"); 102 | return buildTicketList(requestBody, data); 103 | } 104 | 105 | /** 106 | * Date类型转为yyyy-MM-dd字符串 107 | * @param fromDate Date类型时间 108 | * @return yyyy-MM-dd字符串 109 | */ 110 | private String convertFromDate(Date fromDate) { 111 | return new DateTime(fromDate).toString(DATE_FORMAT); 112 | } 113 | 114 | /** 115 | * 12306车票列表信息,转为为平台的车票列表 116 | * @param requestBody 请求 117 | * @param data 12306车票列表信息 118 | * @return 平台的车票列表 119 | */ 120 | private List buildTicketList(GetTicketListRequest requestBody, JSONObject data) { 121 | List ret = Lists.newArrayList(); 122 | 123 | //站点代码和名字映射 124 | JSONObject map = data.getJSONObject("map"); 125 | String flag = data.getString("flag"); 126 | JSONArray result = data.getJSONArray("result"); 127 | if ("1".equals(flag)) { 128 | for (int i = 0; i < result.size(); i++) { 129 | String train = result.getString(i); 130 | List trainItem = Splitter.on("|").splitToList(train); 131 | 132 | String secretStr = trainItem.get(0); //? 133 | String buttonTextInfo = trainItem.get(1); //按钮名字:预订 134 | 135 | String trainNo = trainItem.get(2); //列车号 136 | String trainCode = trainItem.get(3); //车次 137 | 138 | String startStationCode = trainItem.get(4); // 起始站代码 139 | String endStationCode = trainItem.get(5); // 结束站代码 140 | 141 | String fromStationCode = trainItem.get(6); //出发站代码 142 | String toStationCode = trainItem.get(7); //到达站代码 143 | String fromStationName = map.getString(fromStationCode); //出发站 144 | String toStationName = map.getString(toStationCode); //到达站 145 | 146 | String startTime = trainItem.get(8); //出发时刻 147 | String arriveTime = trainItem.get(9); //到达时刻 148 | String runTime = trainItem.get(10); //历时 149 | 150 | String canWebBuy = trainItem.get(11); //是否能购买:Y 可以 N 不可 IS_TIME_NOT_BUY 列车运行图调整,暂停发售/列车停运 151 | String ypInfo = trainItem.get(12); //? 152 | 153 | String startTrainDate = trainItem.get(13); //列车起始站发成日期 154 | 155 | String trainSeatFeature = trainItem.get(14); //? 156 | String locationCode = trainItem.get(15); //? 157 | String fromStationNo = trainItem.get(16); //出发站站序(对应火车经停信息中的站序)01表示始发站,大于1则表示过站 158 | String toStationNo = trainItem.get(17); //到达站站序(对应火车经停信息中的站序) 159 | String isSupportCard = trainItem.get(18); //可凭二代身份证直接进出站 1 可以 0 不可以 160 | String controlledTrainFlag = trainItem.get(19); //? 161 | 162 | String ggNum = StringUtils.isNotEmpty(trainItem.get(20)) ? trainItem.get(20) : "0"; //? 163 | String grNum = StringUtils.isNotEmpty(trainItem.get(21)) ? trainItem.get(21) : "0"; // 高级软卧 164 | String qtNum = StringUtils.isNotEmpty(trainItem.get(22)) ? trainItem.get(22) : "0"; // 其他 165 | String rwNum = StringUtils.isNotEmpty(trainItem.get(23)) ? trainItem.get(23) : "0"; // 软卧/一等卧 166 | String rzNum = StringUtils.isNotEmpty(trainItem.get(24)) ? trainItem.get(24) : "0"; // 软座 167 | String tzNum = StringUtils.isNotEmpty(trainItem.get(25)) ? trainItem.get(25) : "0"; //? 特等座? 168 | String wzNum = StringUtils.isNotEmpty(trainItem.get(26)) ? trainItem.get(26) : "0"; // 无座 169 | String ybNum = StringUtils.isNotEmpty(trainItem.get(27)) ? trainItem.get(27) : "0"; //? 170 | String ywNum = StringUtils.isNotEmpty(trainItem.get(28)) ? trainItem.get(28) : "0"; // 硬卧/二等卧 171 | String yzNum = StringUtils.isNotEmpty(trainItem.get(29)) ? trainItem.get(29) : "0"; // 硬座 172 | String edzNum = StringUtils.isNotEmpty(trainItem.get(30)) ? trainItem.get(30) : "0"; // 二等座 173 | String ydzNum = StringUtils.isNotEmpty(trainItem.get(31)) ? trainItem.get(31) : "0"; // 一等座 174 | String swzNum = StringUtils.isNotEmpty(trainItem.get(32)) ? trainItem.get(32) : "0"; // 商务座/特等座 175 | String srrbNum = StringUtils.isNotEmpty(trainItem.get(33)) ? trainItem.get(33) : "0"; // 动卧 176 | 177 | String ypEx = trainItem.get(34); //? 查询车票价格时的seat_types字段 178 | String seatTypes = trainItem.get(35); //? 179 | String exchangeTrainFlag = trainItem.get(36); //? 180 | 181 | logger.info("列车号:{} 出发站:{} 达站:{} 出发时间:{} 到达时间:{} 历时:{} 列车起始站发车日期:{} " + 182 | "商务座特等座:{} 一等座:{} 二等座:{} 高级软卧:{} 软卧:{} 动卧:{} 硬卧:{} 软座:{} 硬座:{} 无座:{} 其他:{} 备注:{}", 183 | trainCode, fromStationName, toStationName, startTime, arriveTime, runTime, startTrainDate, 184 | swzNum, ydzNum, edzNum, grNum, rwNum, srrbNum, ywNum, rzNum, yzNum, wzNum, qtNum, buttonTextInfo); 185 | 186 | // 缓存车次代码--站点代码映射 187 | TrainCodeTrainNoMap.put(trainCode, trainNo); 188 | String fromDate = convertFromDate(requestBody.getFromDate()); 189 | 190 | // TicketPrice ticketPrice = queryTicketPrice(trainNo, fromDate, fromStationNo, toStationNo, ypEx); 191 | // TrainLine trainLine = getTrainLineFrom12306(trainNo,fromDate, fromStationCode, toStationCode); 192 | 193 | TicketPrice ticketPrice = new TicketPrice(); 194 | 195 | Ticket ticket = new Ticket(); 196 | ticket.setTrainNo(trainNo); 197 | ticket.setTrainCode(trainCode); 198 | ticket.setTrainType(getTrainType(trainCode)); 199 | ticket.setFromStation(fromStationName); 200 | ticket.setToStation(toStationName); 201 | // ticket.setFromStationType(getStationTypeName(fromStationNo, trainLine)); 202 | // ticket.setToStationType(getStationTypeName(toStationNo, trainLine)); 203 | ticket.setFromTime(startTime); 204 | ticket.setToTime(arriveTime); 205 | ticket.setRunTime(runTime); 206 | ticket.setCanBook(getCanBook(buttonTextInfo, canWebBuy)); 207 | 208 | ticket.setSwzNum(getTicketNum(swzNum)); 209 | ticket.setSwzPrice(ticketPrice.getSwzPrice()); 210 | ticket.setYdzNum(getTicketNum(ydzNum)); 211 | ticket.setYdzPrice(ticketPrice.getYdzPrice()); 212 | ticket.setEdzNum(getTicketNum(edzNum)); 213 | ticket.setEdzPrice(ticketPrice.getEdzPrice()); 214 | ticket.setGjrwNum(getTicketNum(grNum)); 215 | ticket.setGjrwPrice(ticketPrice.getGjrwPrice()); 216 | ticket.setRwNum(getTicketNum(rwNum)); 217 | ticket.setRwPrice(ticketPrice.getRwPrice()); 218 | ticket.setDwNum(getTicketNum(srrbNum)); 219 | ticket.setDwPrice(ticketPrice.getDwPrice()); 220 | ticket.setYwNum(getTicketNum(ywNum)); 221 | ticket.setYwPrice(ticketPrice.getYwPrice()); 222 | ticket.setRzNum(getTicketNum(rzNum)); 223 | ticket.setRzPrice(ticketPrice.getRzPrice()); 224 | ticket.setYzNum(getTicketNum(yzNum)); 225 | ticket.setYzPrice(ticketPrice.getYzPrice()); 226 | ticket.setWzNum(getTicketNum(wzNum)); 227 | ticket.setWzPrice(ticketPrice.getWzPrice()); 228 | ticket.setQtNum(getTicketNum(qtNum)); 229 | ticket.setQtPrice(ticketPrice.getQtPrice()); 230 | ret.add(ticket); 231 | } 232 | } 233 | 234 | return ret; 235 | } 236 | 237 | /** 238 | * 查询列车经停信息 239 | * @param trainNo 列车号 注意区分车次代码 240 | * @param fromDate 出发日期 241 | * @param fromStationCode 出发站点代码 242 | * @param toStationCode 到达站点代码 243 | * @return 经停信息 244 | */ 245 | private TrainLine getTrainLineFrom12306(String trainNo, String fromDate, String fromStationCode, String toStationCode) { 246 | String getTicketLineUrl = String.format(getTicketLineUrlFmt, trainNo, fromStationCode, toStationCode, fromDate); 247 | JSONObject ret12306 = TrainHelper.requestTo12306(getTicketLineUrl); 248 | JSONArray stops = ret12306.getJSONObject("data").getJSONArray("data"); 249 | 250 | TrainLine trainLine = new TrainLine(); 251 | if (!CollectionUtils.isEmpty(stops)) { 252 | JSONObject stopInfoFirst = stops.getJSONObject(0); 253 | String startStationName = stopInfoFirst.getString("start_station_name"); //出发城市 254 | String endStationName = stopInfoFirst.getString("end_station_name"); //到达城市 255 | String stationTrainCode = stopInfoFirst.getString("station_train_code"); //车次号 256 | String trainClassName = stopInfoFirst.getString("train_class_name"); //车次类型, 快速等 257 | String serviceType = stopInfoFirst.getString("service_type"); //服务类型 0表示无空调 其他表示有空调 258 | String serviceName = "0".equals(serviceType) ? "无空调" : "有空调"; 259 | 260 | List resultStops = Lists.newArrayList(); 261 | for (int i = 0; i < stops.size(); i++) { 262 | Stop stop = new Stop(); 263 | JSONObject stopInfo = stops.getJSONObject(i); 264 | String startTime = stopInfo.getString("start_time"); //出发时间(格式 HH:mm) 265 | String arriveTime = stopInfo.getString("arrive_time"); //到达时间(格式 HH:mm 或者----) 266 | String stationName = stopInfo.getString("station_name"); //站名 267 | String stopoverTime = stopInfo.getString("stopover_time"); //停留时间(分钟) 可能为---- 268 | String stationNo = stopInfo.getString("station_no"); //站序(01开始) 269 | Boolean isSearchStation = stopInfo.getBoolean("isEnabled"); //是否是我们搜索的出行站和到达站 false不是 true是 270 | 271 | stop.setStartTime(startTime); 272 | stop.setArriveTime(arriveTime); 273 | stop.setStationName(stationName); 274 | stop.setStopoverTime(stopoverTime); 275 | stop.setStationNo(stationNo); 276 | stop.setIsSearchStation(isSearchStation); 277 | resultStops.add(stop); 278 | } 279 | 280 | trainLine.setTrainCode(stationTrainCode); 281 | trainLine.setStartStationName(startStationName); 282 | trainLine.setEndStationName(endStationName); 283 | trainLine.setTrainClassName(trainClassName); 284 | trainLine.setServiceName(serviceName); 285 | trainLine.setStops(resultStops); 286 | return trainLine; 287 | } 288 | 289 | return trainLine; 290 | } 291 | 292 | /** 293 | * 查询车票的价格 294 | * @param trainNo 列车号 注意区分车次代码 295 | * @param fromDate 出发日期 296 | * @param fromStationNo 出发站序 297 | * @param toStationNo 到达站序 298 | * @param seatTypes ? 查询车票列表时的ypEx字段 299 | * @return 所有票价 300 | */ 301 | private TicketPrice queryTicketPrice(String trainNo, String fromDate, String fromStationNo, String toStationNo, String seatTypes) { 302 | String getTicketPriceUrl = String.format(getTicketPriceUrlFmt, trainNo, fromStationNo, toStationNo, seatTypes, fromDate); 303 | JSONObject ret12306 = TrainHelper.requestTo12306(getTicketPriceUrl); 304 | JSONObject data = ret12306.getJSONObject("data"); 305 | 306 | Map seatTypeMap = SeatTypeMap.getSeatTypeMap(); 307 | BigDecimal swzPrice = processPrice(data.getString(seatTypeMap.get("swz"))); 308 | BigDecimal ydzPrice = processPrice(data.getString(seatTypeMap.get("ydz"))); 309 | BigDecimal edzPrice = processPrice(data.getString(seatTypeMap.get("edz"))); 310 | BigDecimal grPrice = processPrice(data.getString(seatTypeMap.get("gr"))); 311 | BigDecimal rwPrice = processPrice(data.getString(seatTypeMap.get("rw"))); 312 | BigDecimal dwPrice = processPrice(data.getString(seatTypeMap.get("dw"))); 313 | BigDecimal ywPrice = processPrice(data.getString(seatTypeMap.get("yw"))); 314 | BigDecimal rzPrice = processPrice(data.getString(seatTypeMap.get("rz"))); 315 | BigDecimal yzPrice = processPrice(data.getString(seatTypeMap.get("yz"))); 316 | BigDecimal wzPrice = processPrice(data.getString(seatTypeMap.get("wz"))); 317 | 318 | logger.info("商务座/特等座价格:{} 一等座价格:{} 二等座价格:{} 高级软卧价格:{} 软卧价格:{} 动卧价格:{} 硬卧价格:{} 软座价格:{} 硬座价格:{} 无座价格:{}", 319 | swzPrice, ydzPrice, edzPrice, grPrice, rwPrice, dwPrice, ywPrice, rzPrice, yzPrice, wzPrice); 320 | 321 | TicketPrice ticketPrice = new TicketPrice(); 322 | ticketPrice.setSwzPrice(swzPrice); 323 | ticketPrice.setYdzPrice(ydzPrice); 324 | ticketPrice.setEdzPrice(edzPrice); 325 | ticketPrice.setGjrwPrice(grPrice); 326 | ticketPrice.setRwPrice(rwPrice); 327 | ticketPrice.setDwPrice(dwPrice); 328 | ticketPrice.setYwPrice(ywPrice); 329 | ticketPrice.setRzPrice(rzPrice); 330 | ticketPrice.setYzPrice(yzPrice); 331 | ticketPrice.setWzPrice(wzPrice); 332 | return ticketPrice; 333 | } 334 | 335 | /** 336 | * 获取站点类型 始|终|过 337 | * @param stationNo 站序(对应火车经停信息中的站序)01表示始发站,大于1则表示过站 338 | * @param trainLine 火车经停信息 339 | * @return 站点类型 340 | */ 341 | private String getStationTypeName(String stationNo, TrainLine trainLine) { 342 | String ret = "过"; 343 | if (Integer.parseInt(stationNo) == 1) { 344 | ret = "始"; 345 | } else if (Integer.parseInt(stationNo) == trainLine.getStops().size()) { 346 | ret = "终"; 347 | } else { 348 | ret = "过"; 349 | } 350 | return ret; 351 | } 352 | 353 | /** 354 | * 价格去掉前置¥ 355 | * @param price 原始价格 356 | * @return 去掉¥的价格 357 | */ 358 | private BigDecimal processPrice(String price) { 359 | BigDecimal ret = null; 360 | if (StringUtils.isNotEmpty(price) && price.startsWith("¥")) { 361 | ret = new BigDecimal(price.substring(1)); 362 | } 363 | return ret; 364 | } 365 | 366 | /** 367 | * 获取可预订的座位数 368 | * 有|表示充足 --|表示无此类型的座位 数字表示剩余座位数 369 | * @param num 12306返回的座位数 370 | * @return 转化后的座位数 371 | */ 372 | private String getTicketNum(String num) { 373 | return "无".equals(num) ? "0" : num; 374 | } 375 | 376 | /** 377 | * 判断车票是否可预订 378 | * @param buttonTextInfo 预订两字 379 | * @param canWebBuy 是否能购买:Y 可以 N 不可 IS_TIME_NOT_BUY 列车运行图调整,暂停发售/列车停运 380 | * @return 是否可预订 381 | */ 382 | private boolean getCanBook(String buttonTextInfo, String canWebBuy) { 383 | return "预订".endsWith(buttonTextInfo) && "Y".equals(canWebBuy); 384 | } 385 | 386 | /** 387 | * 获取列车类别 388 | * G高铁 C城际 D动车 Z直达 T特快 K快速 O其他 389 | * @param trainCode 列车车次 390 | * @return 列车类别 391 | */ 392 | private String getTrainType(String trainCode) { 393 | String trainType = "O"; 394 | if (Character.isLetter(trainCode.charAt(0))) { 395 | trainType = String.valueOf(trainCode.charAt(0)); 396 | } 397 | return trainType; 398 | } 399 | 400 | /** 401 | * 获取查询车票的url 402 | * @param requestBody 请求参数 403 | * @return 完成URL 404 | */ 405 | private String getTicketListUrl(GetTicketListRequest requestBody) { 406 | String fromDate = new DateTime(requestBody.getFromDate()).toString(DATE_FORMAT); 407 | String passengerType = requestBody.getIsStudent() ? PassengerType.STUDENT.value() : PassengerType.ADULT.value(); 408 | return String.format(getTicketListUrlFmt, leftTicketUrl, fromDate, requestBody.getFromStationCode(), 409 | requestBody.getToStationCode(), passengerType); 410 | } 411 | 412 | } 413 | --------------------------------------------------------------------------------