├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── db
├── init.sql
└── ticket.db
├── docker
├── .env
└── docker-compose.yml
├── pom.xml
├── python
├── 12306.image.model.h5
├── LICENSE
├── README.md
├── __pycache__
│ ├── mlearn_for_image.cpython-36.pyc
│ └── pretreatment.cpython-36.pyc
├── baidu.py
├── category_images.py
├── main.py
├── mlearn.py
├── mlearn_for_image.py
├── model.h5
├── pretreatment.py
├── requirements.txt
├── run.sh
├── texts.txt
└── verify_image_hash.py
├── src
├── main
│ ├── java
│ │ └── com
│ │ │ └── qianxunclub
│ │ │ └── ticket
│ │ │ ├── TicketApplication.java
│ │ │ ├── config
│ │ │ ├── ApiConfig.java
│ │ │ ├── Config.java
│ │ │ ├── CookiesConfig.java
│ │ │ ├── NoticeConfig.java
│ │ │ ├── SwaggerConfig.java
│ │ │ └── UserConfig.java
│ │ │ ├── constant
│ │ │ ├── SeatLevelEnum.java
│ │ │ └── StatusEnum.java
│ │ │ ├── controller
│ │ │ └── IndexController.java
│ │ │ ├── filter
│ │ │ └── LogFilter.java
│ │ │ ├── ip
│ │ │ ├── AddIpThread.java
│ │ │ ├── CheckIpThread.java
│ │ │ └── PingIpThread.java
│ │ │ ├── model
│ │ │ ├── BuyTicketInfoModel.java
│ │ │ ├── LogdeviceModel.java
│ │ │ ├── LoggerMessage.java
│ │ │ ├── NoticeModel.java
│ │ │ ├── PassengerModel.java
│ │ │ ├── Result.java
│ │ │ ├── SeatModel.java
│ │ │ ├── TicketModel.java
│ │ │ ├── UserModel.java
│ │ │ ├── UserTicketStore.java
│ │ │ ├── request
│ │ │ │ ├── PassengerRequest.java
│ │ │ │ └── TicketRequest.java
│ │ │ └── response
│ │ │ │ ├── PassengerResponse.java
│ │ │ │ └── TicketInfoResponse.java
│ │ │ ├── repository
│ │ │ ├── dao
│ │ │ │ ├── IpsDao.java
│ │ │ │ ├── ProxyIpDao.java
│ │ │ │ └── TicketDao.java
│ │ │ ├── entity
│ │ │ │ ├── Ips.java
│ │ │ │ ├── ProxyIp.java
│ │ │ │ └── Ticket.java
│ │ │ └── mapper
│ │ │ │ ├── IpsMapper.java
│ │ │ │ ├── ProxyIpMapper.java
│ │ │ │ └── TicketMapper.java
│ │ │ ├── service
│ │ │ ├── ApiRequestService.java
│ │ │ ├── IpsService.java
│ │ │ ├── NoticeService.java
│ │ │ ├── ProxyIpService.java
│ │ │ ├── TicketService.java
│ │ │ └── WeChatNotice.java
│ │ │ ├── ticket
│ │ │ ├── BuyTicket.java
│ │ │ ├── DoHandle.java
│ │ │ ├── Login.java
│ │ │ ├── PassengerService.java
│ │ │ ├── QueryTicket.java
│ │ │ ├── Station.java
│ │ │ └── Task.java
│ │ │ └── util
│ │ │ ├── ApplicationContextHelper.java
│ │ │ ├── CaptchaImageForPy.java
│ │ │ ├── CommonUtil.java
│ │ │ ├── CookieUtil.java
│ │ │ ├── HttpUtil.java
│ │ │ ├── LogUtil.java
│ │ │ ├── LogdeviceUtil.java
│ │ │ └── StaticUtil.java
│ └── resources
│ │ ├── application-cookie.yml
│ │ ├── application-sms.yml
│ │ ├── application-user.yml
│ │ ├── application.yml
│ │ └── logback.xml
└── test
│ └── java
│ └── com
│ └── qianxunclub
│ └── ticket
│ ├── GetLogdeviceTest.java
│ ├── GetPassengerTest.java
│ ├── LoginTest.java
│ ├── NoticeTest.java
│ ├── TimeTest.java
│ ├── YzmTest.java
│ └── constant
│ └── Constant.java
└── temp
└── index.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | /target/
3 | !.mvn/wrapper/maven-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 |
20 | ### NetBeans ###
21 | /nbproject/private/
22 | /nbbuild/
23 | /dist/
24 | /nbdist/
25 | /.nb-gradle/
26 | /build/
27 |
28 | ### VS Code ###
29 | .vscode/
30 | python/venv/
31 | temp
32 | log
33 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM centos:7
2 | RUN yum install -y git vim wget curl java-1.8.0-openjdk.x86_64 maven python36-setuptools python36-pip
3 | RUN git clone https://github.com/qianxunclub/ticket.git
4 | RUN cd ticket && mvn clean package
5 | RUN cp ticket/target/ticket-0.0.1-SNAPSHOT.jar app.jar
6 | RUN cp -R ticket/python python
7 | RUN cd python && python3 -m venv venv
8 | RUN cd python && source venv/bin/activate && pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
9 | VOLUME /tmp
10 | ENTRYPOINT ["java","-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # 12306抢票
3 | 这是一个牛逼的全自动购票系统,该系统为 `Spring Boot` 编写的后端服务,就不需要天天盯着 `12306` 官网查询余票了,用起来很爽,保证不收集任何敏感信息,真的。
4 |
5 | > 马云 Gitee:https://gitee.com/qianxunclub/ticket
6 | > GitHub:https://github.com/qianxunclub/ticket
7 | # 功能介绍
8 | - 自动识别验证码
9 | - 多账号同步购票
10 | - 动态添加抢票账号
11 | - 定时刷新监控余票
12 | - 设置多座位优先抢票
13 | - 自动下单
14 | - 下单成功短息通知
15 | - 下单成功微信通知
16 | - 接口文档:http://localhost:9998/swagger-ui.html
17 | - 配置代理
18 | - 支持 docker 部署
19 | - 支持Mac、Linux、Windows
20 |
21 | # 说明
22 | - [配置文件说明](https://github.com/qianxunclub/ticket/wiki/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)
23 | - [开发环境配置](https://github.com/qianxunclub/ticket/wiki/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE)
24 | - [docker 使用](https://github.com/qianxunclub/ticket/wiki/docker-%E7%8E%AF%E5%A2%83)
25 |
26 | # 在线接口文档
27 | 项目集成了 `swagger` ,可以在线直接调用接口使用。
28 | 项目启动成功后,输入地址:
29 | ```
30 | http://localhost:9998/swagger-ui.html
31 | ```
32 |
33 | # 开发者讨论
34 | 有什么好建议或者想法的,可以添加QQ群一起讨论:852214454
35 |
36 | # 欢迎提交 PR 升级
37 |
--------------------------------------------------------------------------------
/db/init.sql:
--------------------------------------------------------------------------------
1 | -- auto-generated definition
2 | create table ticket
3 | (
4 | id INTEGER
5 | constraint ticket_pk primary key autoincrement,
6 | date varchar not null,
7 | "from" varchar not null,
8 | "to" varchar not null,
9 | train_number varchar not null,
10 | passenger_code varchar not null,
11 | mobile varchar not null,
12 | real_name varchar,
13 | seat varchar not null,
14 | username varchar not null,
15 | password varchar not null,
16 | server_sckey varchar not null
17 | );
18 |
19 |
20 | create table ips
21 | (
22 | id INTEGER
23 | constraint ips_pk primary key autoincrement,
24 | ip varchar not null
25 | );
26 |
27 |
28 | create table proxy_ip
29 | (
30 | id INTEGER
31 | constraint ips_pk primary key autoincrement,
32 | ip varchar not null,
33 | port integer not null
34 | );
35 |
--------------------------------------------------------------------------------
/db/ticket.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianxunclub/ticket/143a48c5f014242ed7c5dcf19cc5396ffddc698d/db/ticket.db
--------------------------------------------------------------------------------
/docker/.env:
--------------------------------------------------------------------------------
1 | # 日志级别
2 | logging_level_com.qianxunclub=
3 | # 代理配置
4 | config_enableProxy=
5 | config_proxyHost=
6 | config_proxyPort=
7 | # 随机查询时间,取值:min-max,范围随机
8 | config_queryTicketSleepTime_min=
9 | config_queryTicketSleepTime_max=
10 | # 短信配置
11 | notice_accessKeyId=
12 | notice_accessSecret=
13 | notice_templateCode=
14 | notice_signName=
15 |
16 | # 乘客配置
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2.2'
2 | services:
3 | ticket:
4 | image: ${TICKET_IMAGE:-960339491/ticket:latest}
5 | ports:
6 | - "9998:9998"
7 | - "5005:5005"
8 | volumes:
9 | - ../db/ticket.db:/db/ticket.db
10 | - ../temp:/temp
11 | - ../src/main/resources/application.yml:/application.yml
12 | - ../src/main/resources/application-cookie.yml:/application-cookie.yml
13 | - ../src/main/resources/application-sms.yml:/application-sms.yml
14 | - ../src/main/resources/application-user.yml:/application-user.yml
15 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.1.5.RELEASE
9 |
10 |
11 | com.qianxunclub
12 | ticket
13 | 0.0.1-SNAPSHOT
14 | ticket
15 | Demo project for Spring Boot
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-web
30 |
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-starter-test
35 | test
36 |
37 |
38 |
39 | org.apache.httpcomponents
40 | httpclient
41 | 4.5.13
42 |
43 |
44 |
45 | org.projectlombok
46 | lombok
47 | 1.18.8
48 | provided
49 |
50 |
51 |
52 | com.google.code.gson
53 | gson
54 | 2.8.9
55 |
56 |
57 |
58 | commons-lang
59 | commons-lang
60 | 2.6
61 |
62 |
63 |
64 | org.apache.httpcomponents
65 | httpmime
66 | 4.5.3
67 |
68 |
69 |
70 | com.aliyun
71 | aliyun-java-sdk-core
72 | 4.0.3
73 |
74 |
75 |
76 | org.xerial
77 | sqlite-jdbc
78 | 3.41.2.2
79 |
80 |
81 |
82 | com.baomidou
83 | mybatis-plus-boot-starter
84 | 3.1.2
85 |
86 |
87 |
88 | io.springfox
89 | springfox-swagger2
90 | 2.9.2
91 |
92 |
93 |
94 | io.springfox
95 | springfox-swagger-ui
96 | 2.9.2
97 |
98 |
99 |
100 | com.google.guava
101 | guava
102 | [30.0-jre,)
103 |
104 |
105 |
106 | net.sourceforge.htmlunit
107 | htmlunit
108 | 2.37.0
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | nexus-aliyun
118 | Nexus aliyun
119 | http://maven.aliyun.com/nexus/content/groups/public
120 |
121 |
122 |
123 |
124 |
125 |
126 | org.springframework.boot
127 | spring-boot-maven-plugin
128 |
129 |
130 | org.apache.maven.plugins
131 | maven-javadoc-plugin
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-surefire-plugin
136 |
137 | true
138 |
139 |
140 |
141 | com.spotify
142 | docker-maven-plugin
143 | 1.2.1
144 |
145 | 960339491/ticket:latest
146 | ${project.basedir}
147 |
148 |
149 | ${project.build.directory}
150 | ${project.build.finalName}.jar
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/python/12306.image.model.h5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianxunclub/ticket/143a48c5f014242ed7c5dcf19cc5396ffddc698d/python/12306.image.model.h5
--------------------------------------------------------------------------------
/python/LICENSE:
--------------------------------------------------------------------------------
1 | The Artistic License 2.0
2 |
3 | Copyright (c) 2015 zhaipro
4 |
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | This license establishes the terms under which a given free software
11 | Package may be copied, modified, distributed, and/or redistributed.
12 | The intent is that the Copyright Holder maintains some artistic
13 | control over the development of that Package while still keeping the
14 | Package available as open source and free software.
15 |
16 | You are always permitted to make arrangements wholly outside of this
17 | license directly with the Copyright Holder of a given Package. If the
18 | terms of this license do not permit the full use that you propose to
19 | make of the Package, you should contact the Copyright Holder and seek
20 | a different licensing arrangement.
21 |
22 | Definitions
23 |
24 | "Copyright Holder" means the individual(s) or organization(s)
25 | named in the copyright notice for the entire Package.
26 |
27 | "Contributor" means any party that has contributed code or other
28 | material to the Package, in accordance with the Copyright Holder's
29 | procedures.
30 |
31 | "You" and "your" means any person who would like to copy,
32 | distribute, or modify the Package.
33 |
34 | "Package" means the collection of files distributed by the
35 | Copyright Holder, and derivatives of that collection and/or of
36 | those files. A given Package may consist of either the Standard
37 | Version, or a Modified Version.
38 |
39 | "Distribute" means providing a copy of the Package or making it
40 | accessible to anyone else, or in the case of a company or
41 | organization, to others outside of your company or organization.
42 |
43 | "Distributor Fee" means any fee that you charge for Distributing
44 | this Package or providing support for this Package to another
45 | party. It does not mean licensing fees.
46 |
47 | "Standard Version" refers to the Package if it has not been
48 | modified, or has been modified only in ways explicitly requested
49 | by the Copyright Holder.
50 |
51 | "Modified Version" means the Package, if it has been changed, and
52 | such changes were not explicitly requested by the Copyright
53 | Holder.
54 |
55 | "Original License" means this Artistic License as Distributed with
56 | the Standard Version of the Package, in its current version or as
57 | it may be modified by The Perl Foundation in the future.
58 |
59 | "Source" form means the source code, documentation source, and
60 | configuration files for the Package.
61 |
62 | "Compiled" form means the compiled bytecode, object code, binary,
63 | or any other form resulting from mechanical transformation or
64 | translation of the Source form.
65 |
66 |
67 | Permission for Use and Modification Without Distribution
68 |
69 | (1) You are permitted to use the Standard Version and create and use
70 | Modified Versions for any purpose without restriction, provided that
71 | you do not Distribute the Modified Version.
72 |
73 |
74 | Permissions for Redistribution of the Standard Version
75 |
76 | (2) You may Distribute verbatim copies of the Source form of the
77 | Standard Version of this Package in any medium without restriction,
78 | either gratis or for a Distributor Fee, provided that you duplicate
79 | all of the original copyright notices and associated disclaimers. At
80 | your discretion, such verbatim copies may or may not include a
81 | Compiled form of the Package.
82 |
83 | (3) You may apply any bug fixes, portability changes, and other
84 | modifications made available from the Copyright Holder. The resulting
85 | Package will still be considered the Standard Version, and as such
86 | will be subject to the Original License.
87 |
88 |
89 | Distribution of Modified Versions of the Package as Source
90 |
91 | (4) You may Distribute your Modified Version as Source (either gratis
92 | or for a Distributor Fee, and with or without a Compiled form of the
93 | Modified Version) provided that you clearly document how it differs
94 | from the Standard Version, including, but not limited to, documenting
95 | any non-standard features, executables, or modules, and provided that
96 | you do at least ONE of the following:
97 |
98 | (a) make the Modified Version available to the Copyright Holder
99 | of the Standard Version, under the Original License, so that the
100 | Copyright Holder may include your modifications in the Standard
101 | Version.
102 |
103 | (b) ensure that installation of your Modified Version does not
104 | prevent the user installing or running the Standard Version. In
105 | addition, the Modified Version must bear a name that is different
106 | from the name of the Standard Version.
107 |
108 | (c) allow anyone who receives a copy of the Modified Version to
109 | make the Source form of the Modified Version available to others
110 | under
111 |
112 | (i) the Original License or
113 |
114 | (ii) a license that permits the licensee to freely copy,
115 | modify and redistribute the Modified Version using the same
116 | licensing terms that apply to the copy that the licensee
117 | received, and requires that the Source form of the Modified
118 | Version, and of any works derived from it, be made freely
119 | available in that license fees are prohibited but Distributor
120 | Fees are allowed.
121 |
122 |
123 | Distribution of Compiled Forms of the Standard Version
124 | or Modified Versions without the Source
125 |
126 | (5) You may Distribute Compiled forms of the Standard Version without
127 | the Source, provided that you include complete instructions on how to
128 | get the Source of the Standard Version. Such instructions must be
129 | valid at the time of your distribution. If these instructions, at any
130 | time while you are carrying out such distribution, become invalid, you
131 | must provide new instructions on demand or cease further distribution.
132 | If you provide valid instructions or cease distribution within thirty
133 | days after you become aware that the instructions are invalid, then
134 | you do not forfeit any of your rights under this license.
135 |
136 | (6) You may Distribute a Modified Version in Compiled form without
137 | the Source, provided that you comply with Section 4 with respect to
138 | the Source of the Modified Version.
139 |
140 |
141 | Aggregating or Linking the Package
142 |
143 | (7) You may aggregate the Package (either the Standard Version or
144 | Modified Version) with other packages and Distribute the resulting
145 | aggregation provided that you do not charge a licensing fee for the
146 | Package. Distributor Fees are permitted, and licensing fees for other
147 | components in the aggregation are permitted. The terms of this license
148 | apply to the use and Distribution of the Standard or Modified Versions
149 | as included in the aggregation.
150 |
151 | (8) You are permitted to link Modified and Standard Versions with
152 | other works, to embed the Package in a larger work of your own, or to
153 | build stand-alone binary or bytecode versions of applications that
154 | include the Package, and Distribute the result without restriction,
155 | provided the result does not expose a direct interface to the Package.
156 |
157 |
158 | Items That are Not Considered Part of a Modified Version
159 |
160 | (9) Works (including, but not limited to, modules and scripts) that
161 | merely extend or make use of the Package, do not, by themselves, cause
162 | the Package to be a Modified Version. In addition, such works are not
163 | considered parts of the Package itself, and are not subject to the
164 | terms of this license.
165 |
166 |
167 | General Provisions
168 |
169 | (10) Any use, modification, and distribution of the Standard or
170 | Modified Versions is governed by this Artistic License. By using,
171 | modifying or distributing the Package, you accept this license. Do not
172 | use, modify, or distribute the Package, if you do not accept this
173 | license.
174 |
175 | (11) If your Modified Version has been derived from a Modified
176 | Version made by someone other than you, you are nevertheless required
177 | to ensure that your Modified Version complies with the requirements of
178 | this license.
179 |
180 | (12) This license does not grant you the right to use any trademark,
181 | service mark, tradename, or logo of the Copyright Holder.
182 |
183 | (13) This license includes the non-exclusive, worldwide,
184 | free-of-charge patent license to make, have made, use, offer to sell,
185 | sell, import and otherwise transfer the Package with respect to any
186 | patent claims licensable by the Copyright Holder that are necessarily
187 | infringed by the Package. If you institute patent litigation
188 | (including a cross-claim or counterclaim) against any party alleging
189 | that the Package constitutes direct or contributory patent
190 | infringement, then this Artistic License to you shall terminate on the
191 | date that such litigation is filed.
192 |
193 | (14) Disclaimer of Warranty:
194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
195 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
202 |
203 |
--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------
1 | # easy12306
2 |
3 | 两个必要的数据集:
4 |
5 | 1. 文字识别,model.h5
6 | 2. 图片识别,12306.image.model.h5
7 |
8 | 识别器数据的下载地址:
9 |
10 | 1. ~[百度网盘](https://pan.baidu.com/s/1OsBIBM4rl8EnpZt7VYiD9g)~
11 | 1. https://drive.google.com/drive/folders/1GDCQyaHr36c7y1H-19pOKjc_EdAI1wn0
12 |
13 | `python3 main.py `
14 |
15 | 我把设计思路写在维基中了:https://github.com/zhaipro/easy12306/wiki
16 |
17 | ### 如何?
18 |
19 | 
20 |
21 | ```
22 | ~$ python3 main.py 2.jpg 2> /dev/null
23 | 电子秤
24 | 风铃 # 要找的是以上两样东西
25 | 0 0 电子秤 # 第一行第一列就是电子秤
26 | 0 1 绿豆
27 | 0 2 蒸笼
28 | 0 3 蒸笼
29 | 1 0 风铃
30 | 1 1 电子秤
31 | 1 2 网球拍
32 | 1 3 网球拍
33 | ```
34 |
35 | 识别前所未见的图片
36 |
37 | 
38 |
39 | 具体的编号:[texts.txt](./texts.txt)
40 |
41 | ```
42 | ~$ python3 mlearn_for_image.py 8.jpg
43 | [0.8991613] # 可信度
44 | [0] # 0 表示的就是打字机
45 | ```
46 |
47 | ### 在线体验
48 |
49 | 识别验证码,暂不识别多标签。
50 |
51 | http://shell.teachx.cn:12306/
52 |
53 | 
54 |
55 | 识别单个图片,可任意尺寸(总之由cv2简单的将其转为指定尺寸)。
56 |
57 | http://shell.teachx.cn:5000/
58 |
59 | 
60 |
--------------------------------------------------------------------------------
/python/__pycache__/mlearn_for_image.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianxunclub/ticket/143a48c5f014242ed7c5dcf19cc5396ffddc698d/python/__pycache__/mlearn_for_image.cpython-36.pyc
--------------------------------------------------------------------------------
/python/__pycache__/pretreatment.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianxunclub/ticket/143a48c5f014242ed7c5dcf19cc5396ffddc698d/python/__pycache__/pretreatment.cpython-36.pyc
--------------------------------------------------------------------------------
/python/baidu.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import base64
3 |
4 | import cv2
5 | import numpy as np
6 | import requests
7 |
8 |
9 | AK = 'unknown'
10 | SK = 'unknown'
11 |
12 |
13 | def get_token(ak, sk):
14 | # http://ai.baidu.com/docs#/Auth/top
15 | url = 'https://aip.baidubce.com/oauth/2.0/token'
16 | params = {
17 | 'grant_type': 'client_credentials',
18 | 'client_id': ak,
19 | 'client_secret': sk,
20 | }
21 | r = requests.post(url, params=params)
22 | return r.json()['access_token']
23 |
24 |
25 | TOKEN = get_token(AK, SK)
26 |
27 |
28 | def ocr(img):
29 | # 文件名
30 | if isinstance(img, str):
31 | img = open(img, 'rb').read()
32 | # 或cv2图像
33 | elif isinstance(img, np.ndarray):
34 | _, img = cv2.imencode('.jpg', img)
35 | # https://ai.baidu.com/docs#/OCR-API/e1bd77f3
36 | url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic'
37 | params = {'access_token': TOKEN}
38 | headers = {'Content-Type': 'application/x-www-form-urlencoded'}
39 | img = base64.b64encode(img)
40 | data = {'image': img}
41 | r = requests.post(url, data=data, params=params, headers=headers)
42 | # 该项目只需要一个词
43 | return r.json()['words_result'][0]['words']
44 |
45 |
46 | def main():
47 | import sys
48 | from pretreatment import load_data
49 | texts = load_data()
50 | fp = open('texts.log', 'w', encoding='utf-8')
51 | for idx, text in enumerate(texts):
52 | try:
53 | text = ocr(text)
54 | print(idx, text, file=fp)
55 | print(idx, text)
56 | except Exception as e:
57 | print(e, file=sys.stderr)
58 |
59 |
60 | if __name__ == '__main__':
61 | main()
62 |
--------------------------------------------------------------------------------
/python/category_images.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import mlearn
4 | from pretreatment import load_data
5 |
6 |
7 | def learn():
8 | texts, imgs = load_data()
9 | labels = mlearn.predict(texts)
10 | labels = labels.argmax(axis=1)
11 | imgs.dtype = np.uint64
12 | imgs.shape = (-1, 8)
13 | unique_imgs = np.unique(imgs)
14 | print(unique_imgs.shape)
15 | imgs_labels = []
16 | for img in unique_imgs:
17 | idxs = np.where(imgs == img)[0]
18 | counts = np.bincount(labels[idxs], minlength=80)
19 | imgs_labels.append(counts)
20 | np.savez('images.npz', images=unique_imgs, labels=imgs_labels)
21 |
22 |
23 | if __name__ == '__main__':
24 | learn()
25 |
--------------------------------------------------------------------------------
/python/main.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import sys
3 |
4 | import cv2
5 | import numpy as np
6 | from keras import models
7 |
8 | import pretreatment
9 | from mlearn_for_image import preprocess_input
10 |
11 |
12 | def get_text(img, offset=0):
13 | text = pretreatment.get_text(img, offset)
14 | text = cv2.cvtColor(text, cv2.COLOR_BGR2GRAY)
15 | text = text / 255.0
16 | h, w = text.shape
17 | text.shape = (1, h, w, 1)
18 | return text
19 |
20 |
21 | def main(fn):
22 | # 读取并预处理验证码
23 | img = cv2.imread(fn)
24 | text = get_text(img)
25 | imgs = np.array(list(pretreatment._get_imgs(img)))
26 | imgs = preprocess_input(imgs)
27 |
28 | # 识别文字
29 | model = models.load_model('model.h5')
30 | label = model.predict(text)
31 | label = label.argmax()
32 | fp = open('texts.txt', encoding='utf-8')
33 | texts = [text.rstrip('\n') for text in fp]
34 | text = texts[label]
35 | print(text)
36 | # 获取下一个词
37 | # 根据第一个词的长度来定位第二个词的位置
38 | if len(text) == 1:
39 | offset = 27
40 | elif len(text) == 2:
41 | offset = 47
42 | else:
43 | offset = 60
44 | text = get_text(img, offset=offset)
45 | if text.mean() < 0.95:
46 | label = model.predict(text)
47 | label = label.argmax()
48 | text = texts[label]
49 | print(text)
50 |
51 | # 加载图片分类器
52 | model = models.load_model('12306.image.model.h5')
53 | labels = model.predict(imgs)
54 | labels = labels.argmax(axis=1)
55 | for pos, label in enumerate(labels):
56 | print(pos // 4, pos % 4, texts[label])
57 |
58 |
59 | if __name__ == '__main__':
60 | main(sys.argv[1])
61 |
--------------------------------------------------------------------------------
/python/mlearn.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import pathlib
3 |
4 | import cv2
5 | import matplotlib.pyplot as plt
6 | import numpy as np
7 | from keras import backend as K
8 | from keras import layers
9 | from keras import models
10 | from keras.callbacks import ReduceLROnPlateau
11 | from keras.utils import to_categorical
12 |
13 |
14 | def load_data(fn='texts.npz', to=False):
15 | data = np.load(fn)
16 | texts, labels = data['texts'], data['labels']
17 | texts = texts / 255.0
18 | _, h, w = texts.shape
19 | texts.shape = (-1, h, w, 1)
20 | if to:
21 | labels = to_categorical(labels)
22 | n = int(texts.shape[0] * 0.9) # 90%用于训练,10%用于测试
23 | return (texts[:n], labels[:n]), (texts[n:], labels[n:])
24 |
25 |
26 | def savefig(history, fn='loss.jpg', start=2):
27 | # 忽略起点
28 | loss = history.history['loss'][start - 1:]
29 | val_loss = history.history['val_loss'][start - 1:]
30 | epochs = list(range(start, len(loss) + start))
31 | plt.plot(epochs, loss, 'bo', label='Training loss')
32 | plt.plot(epochs, val_loss, 'b', label='Validation loss')
33 | plt.title('Training and validation loss')
34 | plt.xlabel('Epochs')
35 | plt.ylabel('Loss')
36 | plt.legend()
37 | plt.savefig(fn)
38 |
39 |
40 | def main():
41 | (train_x, train_y), (test_x, test_y) = load_data()
42 | model = models.Sequential([
43 | layers.Conv2D(64, (3, 3), padding='same', activation='relu', input_shape=(None, None, 1)),
44 | layers.MaxPooling2D(), # 19 -> 9
45 | layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
46 | layers.MaxPooling2D(), # 9 -> 4
47 | layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
48 | layers.MaxPooling2D(), # 4 -> 2
49 | layers.GlobalAveragePooling2D(),
50 | layers.Dropout(0.25),
51 | layers.Dense(64, activation='relu'),
52 | layers.Dense(80, activation='softmax'),
53 | ])
54 | model.summary()
55 | model.compile(optimizer='rmsprop',
56 | loss='sparse_categorical_crossentropy',
57 | metrics=['accuracy'])
58 | # 当标准评估停止提升时,降低学习速率
59 | reduce_lr = ReduceLROnPlateau(verbose=1)
60 | history = model.fit(train_x, train_y, epochs=100,
61 | validation_data=(test_x, test_y),
62 | callbacks=[reduce_lr])
63 | savefig(history, start=10)
64 | model.save('model.v1.0.h5', include_optimizer=False)
65 |
66 |
67 | def load_data_v2():
68 | (train_x, train_y), (test_x, test_y) = load_data(to=True)
69 | # 这里是统计学数据
70 | (train_v2_x, train_v2_y), (test_v2_x, test_v2_y) = load_data('texts.v2.npz')
71 | # 合并
72 | train_x = np.concatenate((train_x, train_v2_x))
73 | train_y = np.concatenate((train_y, train_v2_y))
74 | test_x = np.concatenate((test_x, test_v2_x))
75 | test_y = np.concatenate((test_y, test_v2_y))
76 | return (train_x, train_y), (test_x, test_y)
77 |
78 |
79 | def acc(y_true, y_pred):
80 | return K.cast(K.equal(K.argmax(y_true + y_pred, axis=-1),
81 | K.argmax(y_pred, axis=-1)),
82 | K.floatx())
83 |
84 |
85 | def main_v19(): # 1.9
86 | (train_x, train_y), (test_x, test_y) = load_data_v2()
87 | model = models.load_model('model.v1.0.h5')
88 | model.compile(optimizer='RMSprop',
89 | loss='categorical_hinge',
90 | metrics=[acc])
91 | reduce_lr = ReduceLROnPlateau(verbose=1)
92 | history = model.fit(train_x, train_y, epochs=100,
93 | validation_data=(test_x, test_y),
94 | callbacks=[reduce_lr])
95 | savefig(history)
96 | model.save('model.v1.9.h5', include_optimizer=False)
97 |
98 |
99 | def main_v20():
100 | (train_x, train_y), (test_x, test_y) = load_data()
101 | model = models.Sequential([
102 | layers.Conv2D(64, (3, 3), activation='relu', padding='same', input_shape=(None, None, 1)),
103 | layers.MaxPooling2D(), # 19 -> 9
104 | layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
105 | layers.MaxPooling2D(), # 9 -> 4
106 | layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
107 | layers.MaxPooling2D(), # 4 -> 2
108 | layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
109 | layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
110 | layers.MaxPooling2D(), # 2 -> 1
111 | layers.GlobalAveragePooling2D(),
112 | layers.Dropout(0.25),
113 | layers.Dense(64, activation='relu'),
114 | layers.Dense(80, activation='softmax'),
115 | ])
116 | model.summary()
117 | model.compile(optimizer='rmsprop',
118 | loss='sparse_categorical_crossentropy',
119 | metrics=['accuracy'])
120 | model.fit(train_x, train_y, epochs=10,
121 | validation_data=(test_x, test_y))
122 | (train_x, train_y), (test_x, test_y) = load_data_v2()
123 | model.compile(optimizer='rmsprop',
124 | loss='categorical_hinge',
125 | metrics=[acc])
126 | reduce_lr = ReduceLROnPlateau(verbose=1)
127 | history = model.fit(train_x, train_y, epochs=100,
128 | validation_data=(test_x, test_y),
129 | callbacks=[reduce_lr])
130 | savefig(history)
131 | # 保存,并扔掉优化器
132 | model.save('model.v2.0.h5', include_optimizer=False)
133 |
134 |
135 | def predict(texts):
136 | model = models.load_model('model.h5')
137 | texts = texts / 255.0
138 | _, h, w = texts.shape
139 | texts.shape = (-1, h, w, 1)
140 | labels = model.predict(texts)
141 | return labels
142 |
143 |
144 | def _predict():
145 | texts = np.load('data.npy')
146 | labels = predict(texts)
147 | np.save('labels.npy', labels)
148 |
149 |
150 | def show():
151 | texts = np.load('data.npy')
152 | labels = np.load('labels.npy')
153 | labels = labels.argmax(axis=1)
154 | pathlib.Path('classify').mkdir(exist_ok=True)
155 | for idx, (text, label) in enumerate(zip(texts, labels)):
156 | # 使用聚类结果命名
157 | fn = f'classify/{label}.{idx}.jpg'
158 | cv2.imwrite(fn, text)
159 |
160 |
161 | if __name__ == '__main__':
162 | main()
163 | # main_v2()
164 | _predict()
165 | show()
166 |
--------------------------------------------------------------------------------
/python/mlearn_for_image.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import sys
3 |
4 | import cv2
5 | import numpy as np
6 | from keras import models
7 | from keras import layers
8 | from keras import optimizers
9 | from keras.applications import VGG16
10 | from keras.callbacks import ReduceLROnPlateau
11 | from keras.preprocessing.image import ImageDataGenerator
12 |
13 |
14 | def preprocess_input(x):
15 | x = x.astype('float32')
16 | # 我是用cv2来读取的图片,其已经是BGR格式了
17 | mean = [103.939, 116.779, 123.68]
18 | x -= mean
19 | return x
20 |
21 |
22 | def load_data():
23 | # 这是统计学专家提供的训练集
24 | data = np.load('captcha.npz')
25 | train_x, train_y = data['images'], data['labels']
26 | train_x = preprocess_input(train_x)
27 | # 由于是统计得来的信息,所以在此给定可信度
28 | sample_weight = train_y.max(axis=1) / np.sqrt(train_y.sum(axis=1))
29 | sample_weight /= sample_weight.mean()
30 | train_y = train_y.argmax(axis=1)
31 |
32 | # 这是人工提供的验证集
33 | data = np.load('captcha.test.npz')
34 | test_x, test_y = data['images'], data['labels']
35 | test_x = preprocess_input(test_x)
36 | return (train_x, train_y, sample_weight), (test_x, test_y)
37 |
38 |
39 | def learn():
40 | (train_x, train_y, sample_weight), (test_x, test_y) = load_data()
41 | datagen = ImageDataGenerator(horizontal_flip=True,
42 | vertical_flip=True)
43 | train_generator = datagen.flow(train_x, train_y, sample_weight=sample_weight)
44 | base = VGG16(weights='imagenet', include_top=False, input_shape=(None, None, 3))
45 | for layer in base.layers[:-4]:
46 | layer.trainable = False
47 | model = models.Sequential([
48 | base,
49 | layers.BatchNormalization(),
50 | layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
51 | layers.GlobalAveragePooling2D(),
52 | layers.BatchNormalization(),
53 | layers.Dense(64, activation='relu'),
54 | layers.BatchNormalization(),
55 | layers.Dropout(0.20),
56 | layers.Dense(80, activation='softmax')
57 | ])
58 | model.compile(optimizer=optimizers.RMSprop(lr=1e-5),
59 | loss='sparse_categorical_crossentropy',
60 | metrics=['accuracy'])
61 | model.summary()
62 | reduce_lr = ReduceLROnPlateau(verbose=1)
63 | model.fit_generator(train_generator, epochs=400,
64 | steps_per_epoch=100,
65 | validation_data=(test_x[:800], test_y[:800]),
66 | callbacks=[reduce_lr])
67 | result = model.evaluate(test_x, test_y)
68 | print(result)
69 | model.save('12306.image.model.h5', include_optimizer=False)
70 |
71 |
72 | def predict(imgs):
73 | imgs = preprocess_input(imgs)
74 | model = models.load_model('12306.image.model.h5')
75 | labels = model.predict(imgs)
76 | return labels
77 |
78 |
79 | def _predict(fn):
80 | imgs = cv2.imread(fn)
81 | imgs = cv2.resize(imgs, (67, 67))
82 | imgs.shape = (-1, 67, 67, 3)
83 | labels = predict(imgs)
84 | print(labels.max(axis=1))
85 | print(labels.argmax(axis=1))
86 |
87 |
88 | if __name__ == '__main__':
89 | if len(sys.argv) >= 2:
90 | _predict(sys.argv[1])
91 | else:
92 | learn()
93 |
--------------------------------------------------------------------------------
/python/model.h5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qianxunclub/ticket/143a48c5f014242ed7c5dcf19cc5396ffddc698d/python/model.h5
--------------------------------------------------------------------------------
/python/pretreatment.py:
--------------------------------------------------------------------------------
1 | #! env python
2 | # coding: utf-8
3 | # 功能:对图像进行预处理,将文字部分单独提取出来
4 | # 并存放到ocr目录下
5 | # 文件名为原验证码文件的文件名
6 | import hashlib
7 | import os
8 | import pathlib
9 |
10 | import cv2
11 | import numpy as np
12 | import requests
13 | import scipy.fftpack
14 | import json
15 | import base64
16 |
17 | PATH = 'imgs'
18 |
19 |
20 | def download_image():
21 | # 抓取验证码
22 | # 存放到指定path下
23 | # 文件名为图像的MD5
24 | url = 'https://kyfw.12306.cn/passport/captcha/captcha-image64'
25 | r = requests.get(url)
26 | fn = hashlib.md5(r.content).hexdigest()
27 | img_str = json.loads(r.content)['image']
28 | with open(f'{PATH}/{fn}.jpg', 'wb') as fp:
29 | fp.write(base64.b64decode(img_str))
30 |
31 |
32 | def download_images():
33 | pathlib.Path(PATH).mkdir(exist_ok=True)
34 | for idx in range(40000):
35 | download_image()
36 | print(idx)
37 |
38 |
39 | def get_text(img, offset=0):
40 | # 得到图像中的文本部分
41 | return img[3:22, 120 + offset:177 + offset]
42 |
43 |
44 | def avhash(im):
45 | im = cv2.resize(im, (8, 8), interpolation=cv2.INTER_CUBIC)
46 | avg = im.mean()
47 | im = im > avg
48 | im = np.packbits(im)
49 | return im
50 |
51 |
52 | def phash(im):
53 | im = cv2.resize(im, (32, 32), interpolation=cv2.INTER_CUBIC)
54 | im = scipy.fftpack.dct(scipy.fftpack.dct(im, axis=0), axis=1)
55 | im = im[:8, :8]
56 | med = np.median(im)
57 | im = im > med
58 | im = np.packbits(im)
59 | return im
60 |
61 |
62 | def _get_imgs(img):
63 | interval = 5
64 | length = 67
65 | for x in range(40, img.shape[0] - length, interval + length):
66 | for y in range(interval, img.shape[1] - length, interval + length):
67 | yield img[x:x + length, y:y + length]
68 |
69 |
70 | def get_imgs(img):
71 | imgs = []
72 | for img in _get_imgs(img):
73 | imgs.append(phash(img))
74 | return imgs
75 |
76 |
77 | def pretreat():
78 | if len(os.listdir(PATH)) < 40000:
79 | download_images()
80 | texts, imgs = [], []
81 | for img in os.listdir(PATH):
82 | img = os.path.join(PATH, img)
83 | img = cv2.imread(img, cv2.IMREAD_GRAYSCALE)
84 | texts.append(get_text(img))
85 | imgs.append(get_imgs(img))
86 | return texts, imgs
87 |
88 |
89 | def load_data(path='./data/data.npz'):
90 | if not os.path.isfile(path):
91 | texts, imgs = pretreat()
92 | np.savez(path, texts=texts, images=imgs)
93 | f = np.load(path)
94 | return f['texts'], f['images']
95 |
96 |
97 | if __name__ == '__main__':
98 | texts, imgs = load_data()
99 | print(texts.shape)
100 | print(imgs.shape)
101 | imgs = imgs.reshape(-1, 8)
102 | print(np.unique(imgs, axis=0).shape)
103 |
--------------------------------------------------------------------------------
/python/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | sklearn
3 | opencv-python
4 | keras>=2.2.4
5 | tensorflow
6 | matplotlib>=3.0.2
7 | numpy>=1.14.6
8 | scipy>=1.1.0
9 |
--------------------------------------------------------------------------------
/python/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd python && source venv/bin/activate && PYTHONIOENCODING=utf-8 python3 main.py $1 2>/dev/null
3 |
--------------------------------------------------------------------------------
/python/texts.txt:
--------------------------------------------------------------------------------
1 | 打字机
2 | 调色板
3 | 跑步机
4 | 毛线
5 | 老虎
6 | 安全帽
7 | 沙包
8 | 盘子
9 | 本子
10 | 药片
11 | 双面胶
12 | 龙舟
13 | 红酒
14 | 拖把
15 | 卷尺
16 | 海苔
17 | 红豆
18 | 黑板
19 | 热水袋
20 | 烛台
21 | 钟表
22 | 路灯
23 | 沙拉
24 | 海报
25 | 公交卡
26 | 樱桃
27 | 创可贴
28 | 牌坊
29 | 苍蝇拍
30 | 高压锅
31 | 电线
32 | 网球拍
33 | 海鸥
34 | 风铃
35 | 订书机
36 | 冰箱
37 | 话梅
38 | 排风机
39 | 锅铲
40 | 绿豆
41 | 航母
42 | 电子秤
43 | 红枣
44 | 金字塔
45 | 鞭炮
46 | 菠萝
47 | 开瓶器
48 | 电饭煲
49 | 仪表盘
50 | 棉棒
51 | 篮球
52 | 狮子
53 | 蚂蚁
54 | 蜡烛
55 | 茶盅
56 | 印章
57 | 茶几
58 | 啤酒
59 | 档案袋
60 | 挂钟
61 | 刺绣
62 | 铃铛
63 | 护腕
64 | 手掌印
65 | 锦旗
66 | 文具盒
67 | 辣椒酱
68 | 耳塞
69 | 中国结
70 | 蜥蜴
71 | 剪纸
72 | 漏斗
73 | 锣
74 | 蒸笼
75 | 珊瑚
76 | 雨靴
77 | 薯条
78 | 蜜蜂
79 | 日历
80 | 口哨
81 |
--------------------------------------------------------------------------------
/python/verify_image_hash.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import pathlib
3 |
4 | import cv2
5 | import numpy as np
6 | import scipy.fftpack
7 |
8 |
9 | def avhash(im):
10 | im = cv2.resize(im, (8, 8), interpolation=cv2.INTER_CUBIC)
11 | avg = im.mean()
12 | im = im > avg
13 | im = np.packbits(im)
14 | return im
15 |
16 |
17 | def phash(im):
18 | im = cv2.resize(im, (32, 32), interpolation=cv2.INTER_CUBIC)
19 | im = scipy.fftpack.dct(scipy.fftpack.dct(im, axis=0), axis=1)
20 | im = im[:8, :8]
21 | med = np.median(im)
22 | im = im > med
23 | im = np.packbits(im)
24 | return im
25 |
26 |
27 | def phash_simple(im):
28 | im = cv2.resize(im, (32, 32), interpolation=cv2.INTER_CUBIC)
29 | im = scipy.fftpack.dct(im)
30 | im = im[:8, 1:8 + 1]
31 | avg = im.mean()
32 | im = im > avg
33 | im = np.packbits(im)
34 | return im
35 |
36 |
37 | def dhash(im):
38 | im = cv2.resize(im, (8 + 1, 8), interpolation=cv2.INTER_CUBIC)
39 | im = im[:, 1:] > im[:, :-1]
40 | im = np.packbits(im)
41 | return im
42 |
43 |
44 | def dhash_vertical(im):
45 | im = cv2.resize(im, (8, 8 + 1), interpolation=cv2.INTER_CUBIC)
46 | im = im[1:, :] > im[:-1, :]
47 | im = np.packbits(im)
48 | return im
49 |
50 |
51 | def whash(image):
52 | """
53 | Wavelet Hash computation.
54 |
55 | based on https://www.kaggle.com/c/avito-duplicate-ads-detection/
56 | @image must be a PIL instance.
57 | """
58 | ll_max_level = int(np.log2(min(image.shape)))
59 | image_scale = 2**ll_max_level
60 |
61 | level = 3
62 | dwt_level = ll_max_level - level
63 |
64 | image = cv2.resize(image, (image_scale, image_scale))
65 | pixels = image / 255
66 |
67 | # Remove low level frequency LL(max_ll) if @remove_max_haar_ll using haar filter
68 | coeffs = pywt.wavedec2(pixels, 'haar', level = ll_max_level)
69 | coeffs[0][:] = 0
70 | pixels = pywt.waverec2(coeffs, 'haar')
71 |
72 | # Use LL(K) as freq, where K is log2(@hash_size)
73 | coeffs = pywt.wavedec2(pixels, 'haar', level = dwt_level)
74 | dwt_low = coeffs[0]
75 |
76 | # Substract median and compute hash
77 | med = np.median(dwt_low)
78 | diff = dwt_low > med
79 |
80 | diff = np.packbits(diff)
81 | return diff
82 |
83 |
84 | def verify(_hash):
85 | # 用验证集测试各哈希函数的效果
86 | data = np.load('captcha.npz')
87 | images, labels = data['images'], data['labels']
88 | print(images.shape)
89 | himages = {}
90 | for idx, (img, label) in enumerate(zip(images, labels)):
91 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
92 | img = _hash(img)
93 | img.dtype = np.uint64
94 | img = img[0]
95 | if himages.get(img, (label,))[0] != label:
96 | cv2.imwrite(f'errors/{idx}.{label}.jpg', images[idx])
97 | pre_label, pre_idx = himages[img]
98 | cv2.imwrite(f'errors/{idx}.{pre_label}.jpg', images[pre_idx])
99 | else:
100 | himages[img] = label, idx
101 | print(len(himages))
102 |
103 |
104 | if __name__ == '__main__':
105 | pathlib.Path('errors').mkdir(exist_ok=True)
106 | # verify(avhash)
107 | # 我觉得下面这个是最佳的
108 | verify(phash)
109 | # verify(phash_simple)
110 | # verify(dhash)
111 | # verify(dhash_vertical)
112 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/TicketApplication.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket;
2 |
3 | import com.qianxunclub.ticket.config.UserConfig;
4 | import com.qianxunclub.ticket.ip.AddIpThread;
5 | import com.qianxunclub.ticket.ip.CheckIpThread;
6 | import com.qianxunclub.ticket.model.BuyTicketInfoModel;
7 | import com.qianxunclub.ticket.service.ApiRequestService;
8 | import com.qianxunclub.ticket.service.IpsService;
9 | import com.qianxunclub.ticket.service.ProxyIpService;
10 | import com.qianxunclub.ticket.service.TicketService;
11 | import com.qianxunclub.ticket.ticket.DoHandle;
12 | import com.qianxunclub.ticket.model.UserTicketStore;
13 |
14 | import com.qianxunclub.ticket.ticket.Station;
15 | import lombok.extern.slf4j.Slf4j;
16 | import org.springframework.boot.autoconfigure.SpringBootApplication;
17 | import org.springframework.boot.builder.SpringApplicationBuilder;
18 | import org.springframework.context.ApplicationContext;
19 |
20 | import java.util.List;
21 |
22 |
23 | /**
24 | * @author zhangbin
25 | */
26 | @SpringBootApplication
27 | @Slf4j
28 | public class TicketApplication {
29 |
30 | public static void main(String[] args) {
31 | SpringApplicationBuilder builder = new SpringApplicationBuilder(TicketApplication.class);
32 | ApplicationContext applicationContext = builder.run(args);
33 |
34 | TicketApplication.init(applicationContext);
35 |
36 | }
37 |
38 | public static void init(ApplicationContext applicationContext) {
39 |
40 | // 初始化 IP
41 | IpsService ipsService = applicationContext.getBean(IpsService.class);
42 | ipsService.load();
43 | log.info("初始化Ip完成");
44 | // 初始化 代理
45 | ProxyIpService proxyIpservice = applicationContext.getBean(ProxyIpService.class);
46 | proxyIpservice.load();
47 | log.info("初始化代理Ip完成");
48 | // 初始化站点
49 | Station station = applicationContext.getBean(Station.class);
50 | ApiRequestService apiRequestService = applicationContext.getBean(ApiRequestService.class);
51 | station.load(apiRequestService);
52 | log.info("初始化站点信息完成");
53 | // 配置文件获取购票信息
54 | List buyTicketInfoModelList = applicationContext
55 | .getBean(UserConfig.class).getTicketInfo();
56 | buyTicketInfoModelList.forEach((buyTicketInfoModel) -> {
57 | if (buyTicketInfoModel != null) {
58 | UserTicketStore.add(buyTicketInfoModel);
59 | }
60 | });
61 | log.info("初始化从配置文件初始化购票信息完成");
62 | // 数据库获取取购票信息
63 | TicketService ticketService = applicationContext.getBean(TicketService.class);
64 | ticketService.getBuyTicketInfoModel().forEach(buyTicketInfoModel -> {
65 | if (buyTicketInfoModel != null) {
66 | UserTicketStore.add(buyTicketInfoModel);
67 | }
68 | });
69 | log.info("初始化从数据库初始化购票信息完成");
70 | DoHandle doHandle = applicationContext.getBean(DoHandle.class);
71 | doHandle.go();
72 | log.info("开始执行抢票任务");
73 | // 检查IP
74 | CheckIpThread checkIpThread = new CheckIpThread(ipsService);
75 | Thread checkIp = new Thread(checkIpThread);
76 | checkIp.start();
77 | log.info("检查IP完成");
78 | // 获取新IP
79 | AddIpThread addIpThread = new AddIpThread(ipsService);
80 | Thread addIp = new Thread(addIpThread);
81 | addIp.start();
82 | log.info("获取IP完成");
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/config/ApiConfig.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.config;
2 |
3 |
4 | import com.qianxunclub.ticket.util.StaticUtil;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.stereotype.Component;
8 |
9 | import lombok.Data;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-05-30 16:06
14 | * @description: TODO
15 | */
16 | @Data
17 | @Component
18 | @ConfigurationProperties(prefix = "api")
19 | public class ApiConfig {
20 |
21 | @Autowired
22 | private Config config;
23 |
24 | private String station;
25 |
26 | private String leftTicketBaseUrl;
27 |
28 | private String leftTicket;
29 |
30 | private String loginConfig;
31 |
32 | private String captchaImage;
33 |
34 | private String uamtkStatic;
35 |
36 | private String login;
37 |
38 | private String passengers;
39 |
40 | private String captchaCheck;
41 |
42 | private String uamtk;
43 |
44 | private String uamauthclient;
45 |
46 | private String submitOrderRequest;
47 |
48 | private String checkUser;
49 |
50 | private String initDc;
51 |
52 | private String getPassengerDTOs;
53 |
54 | private String checkOrderInfo;
55 |
56 | private String checkRandCodeAnsyn;
57 |
58 | private String confirmSingleForQueue;
59 |
60 | private String getQueueCount;
61 |
62 | private String queryOrderWaitTime;
63 |
64 | private String notice;
65 |
66 | private String init;
67 |
68 | private String serverWechat;
69 |
70 | public String getHost() {
71 | String host = StaticUtil.ip();
72 | if (host == null) {
73 | host = config.getBaseUrl();
74 | }
75 | return host;
76 | }
77 |
78 | public String getStation() {
79 | return this.getHost().concat(station);
80 | }
81 |
82 | public String getLeftTicket() {
83 | return this.getHost().concat(leftTicket);
84 | }
85 |
86 | public String getLoginConfig() {
87 | return this.getHost().concat(loginConfig);
88 | }
89 |
90 | public String getCaptchaImage() {
91 | return this.getHost().concat(captchaImage);
92 | }
93 |
94 | public String getUamtkStatic() {
95 | return this.getHost().concat(uamtkStatic);
96 | }
97 |
98 | public String getLogin() {
99 | return this.getHost().concat(login);
100 | }
101 |
102 | public String getPassengers() {
103 | return this.getHost().concat(passengers);
104 | }
105 |
106 | public String getCaptchaCheck() {
107 | return this.getHost().concat(captchaCheck);
108 | }
109 |
110 | public String getUamtk() {
111 | return this.getHost().concat(uamtk);
112 | }
113 |
114 | public String getUamauthclient() {
115 | return this.getHost().concat(uamauthclient);
116 | }
117 |
118 | public String getSubmitOrderRequest() {
119 | return this.getHost().concat(submitOrderRequest);
120 | }
121 |
122 | public String getCheckUser() {
123 | return this.getHost().concat(checkUser);
124 | }
125 |
126 | public String getInitDc() {
127 | return this.getHost().concat(initDc);
128 | }
129 |
130 | public String getGetPassengerDTOs() {
131 | return this.getHost().concat(getPassengerDTOs);
132 | }
133 |
134 | public String getCheckOrderInfo() {
135 | return this.getHost().concat(checkOrderInfo);
136 | }
137 |
138 | public String getCheckRandCodeAnsyn() {
139 | return this.getHost().concat(checkRandCodeAnsyn);
140 | }
141 |
142 | public String getConfirmSingleForQueue() {
143 | return this.getHost().concat(confirmSingleForQueue);
144 | }
145 |
146 | public String getGetQueueCount() {
147 | return this.getHost().concat(getQueueCount);
148 | }
149 |
150 | public String getQueryOrderWaitTime() {
151 | return this.getHost().concat(queryOrderWaitTime);
152 | }
153 |
154 | public String getInit() {
155 | return this.getHost().concat(init);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/config/Config.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.config;
2 |
3 | import com.qianxunclub.ticket.repository.entity.ProxyIp;
4 | import com.qianxunclub.ticket.util.StaticUtil;
5 | import java.util.Map;
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.stereotype.Component;
8 |
9 | import lombok.Data;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-05-30 15:57
14 | * @description: TODO
15 | */
16 | @Data
17 | @Component
18 | @ConfigurationProperties(prefix = "config")
19 | public class Config {
20 |
21 | private String host;
22 |
23 | private String baseUrl;
24 |
25 | private Boolean enableProxy;
26 |
27 | private String proxyHost;
28 |
29 | private Integer proxyPort;
30 |
31 | private Map queryTicketSleepTime;
32 |
33 | private String pythonPath;
34 |
35 | private ProxyIp proxyIp;
36 |
37 | public Integer querySleep() {
38 | return (int) (queryTicketSleepTime.get("min") + Math.random() * (
39 | queryTicketSleepTime.get("max") - queryTicketSleepTime.get("min") + 1));
40 | }
41 |
42 | public ProxyIp getProxyIp() {
43 | proxyIp = StaticUtil.proxyIp();
44 | if (proxyIp == null) {
45 | proxyIp = new ProxyIp();
46 | proxyIp.setIp(proxyHost);
47 | proxyIp.setPort(proxyPort);
48 | }
49 | return proxyIp;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/config/CookiesConfig.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | import lombok.Data;
7 |
8 | /**
9 | * @author zhangbin
10 | * @date 2019-06-04 11:22
11 | * @description: TODO
12 | */
13 | @Data
14 | @Component
15 | @ConfigurationProperties(prefix = "cookies")
16 | public class CookiesConfig {
17 | Boolean enable;
18 | String railExpiration;
19 | String railDeviceid;
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/config/NoticeConfig.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | import lombok.Data;
7 |
8 | /**
9 | * @author zhangbin
10 | * @date 2019-05-30 15:57
11 | * @description: TODO
12 | */
13 | @Data
14 | @Component
15 | @ConfigurationProperties(prefix = "notice")
16 | public class NoticeConfig {
17 |
18 | private String accessKeyId;
19 | private String accessSecret;
20 | private String templateCode;
21 | private String signName;
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/config/SwaggerConfig.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
9 |
10 | import lombok.Data;
11 | import springfox.documentation.builders.ApiInfoBuilder;
12 | import springfox.documentation.builders.RequestHandlerSelectors;
13 | import springfox.documentation.service.ApiInfo;
14 | import springfox.documentation.service.Contact;
15 | import springfox.documentation.spi.DocumentationType;
16 | import springfox.documentation.spring.web.plugins.Docket;
17 | import springfox.documentation.swagger2.annotations.EnableSwagger2;
18 |
19 | import static com.google.common.base.Predicates.not;
20 | import static springfox.documentation.builders.PathSelectors.regex;
21 |
22 | /**
23 | * @author zhangbin
24 | * @date 2019-07-19 10:57
25 | * @description: TODO
26 | */
27 | @Configuration
28 | @EnableSwagger2
29 | @Data
30 | @Component
31 | @ConfigurationProperties(prefix = "swagger")
32 | public class SwaggerConfig extends WebMvcConfigurerAdapter {
33 | private Boolean enabled;
34 |
35 | private String title;
36 |
37 | private String description;
38 |
39 | private String webBasePackage;
40 |
41 | private String author;
42 |
43 | private String url;
44 |
45 | private String email;
46 |
47 | @Bean
48 | public Docket createRestApi() {
49 | return new Docket(DocumentationType.SWAGGER_2)
50 | .enable(this.getEnabled())
51 | .apiInfo(apiInfo())
52 | .select()
53 | .apis(RequestHandlerSelectors.basePackage(this.getWebBasePackage()))
54 | .paths(not(regex("/error.*")))
55 | .build();
56 | }
57 |
58 | private ApiInfo apiInfo() {
59 | String author = this.getAuthor();
60 | String url = this.getUrl();
61 | String email = this.getEmail();
62 | return new ApiInfoBuilder()
63 | .title(this.getTitle())
64 | .description(this.getDescription())
65 | .contact(new Contact(author, url, email))
66 | .build();
67 | }
68 |
69 | @Override
70 | public void addResourceHandlers(ResourceHandlerRegistry registry) {
71 | registry.addResourceHandler("swagger-ui.html")
72 | .addResourceLocations("classpath:/META-INF/resources/");
73 |
74 | registry.addResourceHandler("/webjars/**")
75 | .addResourceLocations("classpath:/META-INF/resources/webjars/");
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/config/UserConfig.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.config;
2 |
3 | import com.qianxunclub.ticket.model.BuyTicketInfoModel;
4 |
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import lombok.Data;
12 |
13 | /**
14 | * @author zhangbin
15 | * @date 2019-07-05 13:56
16 | * @description: TODO
17 | */
18 | @Data
19 | @Component
20 | @ConfigurationProperties(prefix = "user")
21 | public class UserConfig {
22 | private List ticketInfo = new ArrayList<>();
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/constant/SeatLevelEnum.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.constant;
2 |
3 | import com.google.common.base.Joiner;
4 | import lombok.Getter;
5 |
6 | import java.util.List;
7 | import java.util.stream.Collectors;
8 |
9 | /**
10 | * @author zhangbin
11 | * @date 2019-05-31 10:52
12 | * @description: TODO
13 | */
14 | @Getter
15 | public enum SeatLevelEnum {
16 |
17 | YINGWO("硬卧",28,"3"),
18 | RUANWO("软卧",23,"4"),
19 | TWO("二等座",30,"O"),
20 | RUANZUO("软座",24,"2"),
21 | YINGZUO("硬座",29,"1"),
22 | ONE("一等座",31,"M"),
23 | SHANGWUZUO("商务座",32,"9"),
24 | GAOJIRUANWO("高级软卧",21,"6"),
25 | WUZUO("无座",26,"1")
26 | ;
27 |
28 | private String name;
29 | private int index;
30 | private String code;
31 |
32 | SeatLevelEnum(String name,int index,String code) {
33 | this.name = name;
34 | this.index = index;
35 | this.code = code;
36 | }
37 |
38 | public static String getSeatNameList(List seatLevelEnums){
39 | List seatLevels = seatLevelEnums.stream().map(level->level.name).collect(Collectors.toList());
40 | return Joiner.on(",").join(seatLevels);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/constant/StatusEnum.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.constant;
2 |
3 | /**
4 | * @author zhangbin
5 | * @date 2019-07-02 10:13
6 | * @description: TODO
7 | */
8 | public enum StatusEnum {
9 |
10 | START("开始"),
11 | ING("正在抢票"),
12 | SUCCESS("抢票成功")
13 | ;
14 |
15 | private String status;
16 |
17 | StatusEnum(String status){
18 | this.status = status;
19 | }
20 |
21 | public String getStatus() {
22 | return status;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/controller/IndexController.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.controller;
2 |
3 | import com.qianxunclub.ticket.model.BuyTicketInfoModel;
4 | import com.qianxunclub.ticket.model.PassengerModel;
5 | import com.qianxunclub.ticket.model.request.PassengerRequest;
6 | import com.qianxunclub.ticket.model.Result;
7 | import com.qianxunclub.ticket.model.request.TicketRequest;
8 | import com.qianxunclub.ticket.model.response.PassengerResponse;
9 | import com.qianxunclub.ticket.model.response.TicketInfoResponse;
10 | import com.qianxunclub.ticket.service.TicketService;
11 | import com.qianxunclub.ticket.model.UserTicketStore;
12 |
13 | import javax.servlet.http.HttpServletRequest;
14 | import org.springframework.beans.BeanUtils;
15 | import org.springframework.util.CollectionUtils;
16 | import org.springframework.util.StringUtils;
17 | import org.springframework.web.bind.annotation.GetMapping;
18 | import org.springframework.web.bind.annotation.PostMapping;
19 | import org.springframework.web.bind.annotation.RequestBody;
20 | import org.springframework.web.bind.annotation.RequestMapping;
21 | import org.springframework.web.bind.annotation.ResponseBody;
22 | import org.springframework.web.bind.annotation.RestController;
23 |
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | import io.swagger.annotations.Api;
28 | import io.swagger.annotations.ApiOperation;
29 | import lombok.AllArgsConstructor;
30 |
31 | /**
32 | * @author zhangbin
33 | * @date 2019-06-08 19:31
34 | * @description: TODO
35 | */
36 | @Api("接口文档")
37 | @RestController
38 | @AllArgsConstructor
39 | @RequestMapping(value = "/api", produces = "application/json")
40 | public class IndexController {
41 |
42 | private TicketService ticketService;
43 |
44 |
45 | @ApiOperation("登录")
46 | @ResponseBody
47 | @PostMapping("getPassenger")
48 | public Result getPassenger(@RequestBody PassengerRequest passengerRequest) {
49 | Result result = new Result("SUCCESS", "登录成功");
50 | List passengerModelList = ticketService
51 | .login(passengerRequest.toUserModel());
52 | if (CollectionUtils.isEmpty(passengerModelList)) {
53 | result = new Result("ERROR", "登录失败");
54 | }
55 | List passengerResponseList = new ArrayList<>();
56 | passengerModelList.forEach(passengerModel -> {
57 | PassengerResponse passengerResponse = passengerModel.toPassengerResponse();
58 | passengerResponseList.add(passengerResponse);
59 | });
60 | result.setData(passengerResponseList);
61 | return result;
62 | }
63 |
64 | @ApiOperation("正在抢票中的用户")
65 | @ResponseBody
66 | @GetMapping("buying")
67 | public Object buying(HttpServletRequest request) {
68 | String username = request.getParameter("username");
69 | if (StringUtils.isEmpty(username)) {
70 | List ticketInfoResponseList = new ArrayList<>();
71 | UserTicketStore.buyTicketInfoModelList.forEach(buyTicketInfoModel -> {
72 | TicketInfoResponse ticketInfoResponse = new TicketInfoResponse();
73 | BeanUtils.copyProperties(buyTicketInfoModel, ticketInfoResponse);
74 | ticketInfoResponseList.add(ticketInfoResponse);
75 | });
76 | return ticketInfoResponseList;
77 | }
78 | BuyTicketInfoModel buyTicketInfo = UserTicketStore.buyTicketInfoModelList.stream()
79 | .filter(buyTicketInfoModel ->
80 | buyTicketInfoModel.getUsername().equals(username)
81 | ).findFirst().get();
82 | TicketInfoResponse ticketInfoResponse = new TicketInfoResponse();
83 | BeanUtils.copyProperties(buyTicketInfo, ticketInfoResponse);
84 | return ticketInfoResponse;
85 | }
86 |
87 | @ApiOperation("添加抢票信息")
88 | @ResponseBody
89 | @PostMapping("ticket")
90 | public Result user(@RequestBody TicketRequest TicketRequest) {
91 | return ticketService.addTicketInfo(TicketRequest.toBuyTicketInfoModel());
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/filter/LogFilter.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.filter;
2 |
3 | import com.qianxunclub.ticket.model.LoggerMessage;
4 | import com.qianxunclub.ticket.util.LogUtil;
5 |
6 | import org.springframework.stereotype.Component;
7 |
8 | import java.text.DateFormat;
9 | import java.util.Date;
10 |
11 | import ch.qos.logback.classic.spi.ILoggingEvent;
12 | import ch.qos.logback.core.filter.Filter;
13 | import ch.qos.logback.core.spi.FilterReply;
14 |
15 | /**
16 | * @author zhangbin
17 | * @date 2019-07-01 14:21
18 | * @description: TODO
19 | */
20 | @Component
21 | public class LogFilter extends Filter {
22 | @Override
23 | public FilterReply decide(ILoggingEvent event) {
24 | LoggerMessage loggerMessage = new LoggerMessage(event.getMessage(), DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())), event.getThreadName(), event.getLoggerName(), event.getLevel().levelStr);
25 | // LogUtil.push(loggerMessage.toString());
26 | return FilterReply.ACCEPT;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/ip/AddIpThread.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.ip;
2 |
3 | import com.qianxunclub.ticket.service.IpsService;
4 | import com.qianxunclub.ticket.util.StaticUtil;
5 | import org.springframework.util.StringUtils;
6 |
7 | public class AddIpThread implements Runnable{
8 |
9 | private IpsService ipsService;
10 |
11 | public AddIpThread(IpsService ipsService){
12 | this.ipsService = ipsService;
13 | }
14 |
15 | @Override
16 | public void run() {
17 | while (true) {
18 | try {
19 | // 每十分钟获取更新一次 IP
20 | Thread.sleep(10 * 1000 * 60);
21 | } catch (InterruptedException e) {
22 | e.printStackTrace();
23 | }
24 | // 获取 IP
25 | String ip = "";
26 | if(!StringUtils.isEmpty(ip)){
27 | ipsService.addIp(ip);
28 | StaticUtil.addIp(ip);
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/ip/CheckIpThread.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.ip;
2 |
3 | import com.qianxunclub.ticket.service.IpsService;
4 | import com.qianxunclub.ticket.util.StaticUtil;
5 |
6 | public class CheckIpThread implements Runnable {
7 |
8 | private IpsService ipsService;
9 |
10 |
11 | public CheckIpThread(IpsService ipsService) {
12 | this.ipsService = ipsService;
13 | }
14 |
15 | @Override
16 | public void run() {
17 | while (true) {
18 | try {
19 | // 每十分钟检查一次 IP
20 | Thread.sleep(10 * 1000 * 60);
21 | } catch (InterruptedException e) {
22 | e.printStackTrace();
23 | }
24 | StaticUtil.ips.forEach(ip -> {
25 | Thread thread = new Thread(new PingIpThread(ipsService, ip));
26 | thread.start();
27 | try {
28 | Thread.sleep(500);
29 | } catch (InterruptedException e) {
30 | e.printStackTrace();
31 | }
32 | });
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/ip/PingIpThread.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.ip;
2 |
3 | import com.qianxunclub.ticket.service.IpsService;
4 | import com.qianxunclub.ticket.util.StaticUtil;
5 |
6 | public class PingIpThread implements Runnable {
7 |
8 | private IpsService ipsService;
9 |
10 | private String ip;
11 |
12 | public PingIpThread(IpsService ipsService,String ip) {
13 | this.ip = ip;
14 | this.ipsService = ipsService;
15 | }
16 |
17 | @Override
18 | public void run() {
19 | boolean s = true;
20 | // 这里校验 IP
21 |
22 |
23 | // 如果 IP 失效
24 | if(!s){
25 | ipsService.delIp(ip);
26 | StaticUtil.rmIp(ip);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/BuyTicketInfoModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import com.qianxunclub.ticket.constant.SeatLevelEnum;
4 | import com.qianxunclub.ticket.constant.StatusEnum;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import lombok.Data;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-05-30 17:00
14 | * @description: TODO
15 | */
16 | @Data
17 | public class BuyTicketInfoModel extends UserModel {
18 |
19 | private int id;
20 |
21 | private String date;
22 |
23 | private String from;
24 |
25 | private String to;
26 |
27 | private String trainNumber;
28 |
29 | private String allEncStr;
30 |
31 | private String mobile;
32 |
33 | private String realName;
34 |
35 | /**
36 | * server酱微信通知秘钥
37 | */
38 | private String serverSckey;
39 |
40 | private PassengerModel passengerModel;
41 |
42 | private List seat = new ArrayList<>();
43 |
44 | private int queryNum;
45 |
46 | private StatusEnum status = StatusEnum.START;
47 |
48 | public String getSeatStr() {
49 | StringBuffer seatStr = new StringBuffer();
50 | seat.forEach(seatLevelEnum -> {
51 | seatStr.append(seatLevelEnum.name() + ",");
52 | });
53 | return seatStr.substring(0, seatStr.length() - 1);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/LogdeviceModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 |
4 | import lombok.AllArgsConstructor;
5 | import lombok.Data;
6 |
7 | /**
8 | * @author zhangbin
9 | * @date 2019-05-30 16:46
10 | * @description: TODO
11 | */
12 | @Data
13 | @AllArgsConstructor
14 | public class LogdeviceModel {
15 |
16 | private String exp;
17 | private String dfp;
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/LoggerMessage.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author zhangbin
8 | * @date 2019-07-01 14:24
9 | * @description: TODO
10 | */
11 | @Data
12 | @AllArgsConstructor
13 | public class LoggerMessage {
14 | private String body;
15 | private String timestamp;
16 | private String threadName;
17 | private String className;
18 | private String level;
19 |
20 | @Override
21 | public String toString(){
22 | return "[" + timestamp + "][" + threadName + "]:" + body;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/NoticeModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | /**
9 | * @author zhangbin
10 | * @date 2019-06-08 16:11
11 | * @description: TODO
12 | */
13 | @Data
14 | @Builder
15 | @NoArgsConstructor
16 | @AllArgsConstructor
17 | public class NoticeModel {
18 | private String phoneNumber;
19 | private String userName;
20 | private String password;
21 | private String orderId;
22 | private String name;
23 | private String trainNum;
24 | private String trainDate;
25 | private String from;
26 | private String to;
27 | private String serverSckey;
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/PassengerModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import com.qianxunclub.ticket.model.response.PassengerResponse;
4 |
5 | import org.springframework.beans.BeanUtils;
6 |
7 | import java.util.Map;
8 |
9 | import lombok.Data;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-06-05 10:30
14 | * @description: TODO
15 | */
16 | @Data
17 | public class PassengerModel {
18 |
19 | String passengerName;
20 | String sexCode;
21 | String sexName;
22 | String bornDate;
23 | String countryCode;
24 | String passengerIdTypeCode;
25 | String passengerIdTypeName;
26 | String passengerIdNo;
27 | String passengerType;
28 | String passengerFlag;
29 | String passengerTypeName;
30 | String mobileNo;
31 | String phoneNo;
32 | String email;
33 | String address;
34 | String postalcode;
35 | String firstLetter;
36 | String recordCount;
37 | String totalLimes;
38 | String indexId;
39 | String gatBornDate;
40 | String gatValidDateStart;
41 | String gatValidDateEnd;
42 | String gatVersi;
43 | String allEncStr;
44 |
45 | public PassengerModel() {
46 | super();
47 | }
48 |
49 | public PassengerModel(Map passengerMap) {
50 | this.passengerName = passengerMap.get("passenger_name");
51 | this.sexCode = passengerMap.get("sex_code");
52 | this.sexName = passengerMap.get("sex_name");
53 | this.bornDate = passengerMap.get("born_date");
54 | this.countryCode = passengerMap.get("countryCode");
55 | this.passengerIdTypeCode = passengerMap.get("passenger_id_type_code");
56 | this.passengerIdTypeName = passengerMap.get("passenger_id_type_name");
57 | this.passengerIdNo = passengerMap.get("passenger_id_no");
58 | this.passengerType = passengerMap.get("passenger_type");
59 | this.passengerFlag = passengerMap.get("passenger_flag");
60 | this.passengerTypeName = passengerMap.get("passenger_type_name");
61 | this.mobileNo = passengerMap.get("mobile_no");
62 | this.phoneNo = passengerMap.get("phone_no");
63 | this.email = passengerMap.get("email");
64 | this.address = passengerMap.get("address");
65 | this.postalcode = passengerMap.get("postalcode");
66 | this.firstLetter = passengerMap.get("first_letter");
67 | this.recordCount = passengerMap.get("record_count");
68 | this.totalLimes = passengerMap.get("total_limes");
69 | this.indexId = passengerMap.get("index_id");
70 | this.gatBornDate = passengerMap.get("gat_born_date");
71 | this.gatValidDateStart = passengerMap.get("gat_valid_date_start");
72 | this.gatValidDateEnd = passengerMap.get("gat_valid_date_end");
73 | this.gatVersi = passengerMap.get("gat_versi");
74 | this.allEncStr = passengerMap.get("allEncStr");
75 | }
76 |
77 | public String getPassengerTicketStr(BuyTicketInfoModel buyTicketInfoModel,
78 | TicketModel ticketModel) {
79 | PassengerModel passengerModel = buyTicketInfoModel.getPassengerModel();
80 | return ticketModel.getSeat().get(0).getSeatLevel().getCode() + "," +
81 | "0,1," + passengerModel.getPassengerName() + "," +
82 | passengerModel.getPassengerIdTypeCode() + "," +
83 | passengerModel.getPassengerIdNo() + "," +
84 | passengerModel.getMobileNo() + "," +
85 | "N," + passengerModel.getAllEncStr();
86 | }
87 |
88 | public String getOldPassengerStr(PassengerModel passengerModel) {
89 | return passengerModel.getPassengerName() + "," +
90 | passengerModel.getPassengerIdTypeCode() + "," +
91 | passengerModel.getPassengerIdNo() + "," +
92 | passengerModel.getPassengerType() + "_";
93 | }
94 |
95 | public PassengerResponse toPassengerResponse() {
96 | PassengerResponse passengerResponse = new PassengerResponse();
97 | BeanUtils.copyProperties(this, passengerResponse);
98 | return passengerResponse;
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/Result.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author zhangbin
7 | * @date 2019-07-19 15:44
8 | * @description: TODO
9 | */
10 | @Data
11 | public class Result {
12 | private String code;
13 | private String msg;
14 | private T data;
15 |
16 | public Result(String code, String msg) {
17 | this.code = code;
18 | this.msg = msg;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/SeatModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import com.qianxunclub.ticket.constant.SeatLevelEnum;
4 |
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 |
8 | /**
9 | * @author zhangbin
10 | * @date 2019-05-31 10:50
11 | * @description: TODO
12 | */
13 | @Data
14 | @AllArgsConstructor
15 | public class SeatModel {
16 |
17 | private SeatLevelEnum seatLevel;
18 | private String count;
19 |
20 | public SeatModel(SeatLevelEnum seatLevel,String[] info) {
21 | this.seatLevel = seatLevel;
22 | String n = info[seatLevel.getIndex()];
23 | this.count = n;
24 | }
25 |
26 |
27 | @Override
28 | public String toString(){
29 | return seatLevel.getName() + "-" + count + "票";
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/TicketModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import com.qianxunclub.ticket.constant.SeatLevelEnum;
4 | import com.qianxunclub.ticket.ticket.Station;
5 |
6 | import java.net.URLDecoder;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import lombok.Data;
11 |
12 | /**
13 | * @author zhangbin
14 | * @date 2019-05-31 10:46
15 | * @description: TODO
16 | */
17 | @Data
18 | public class TicketModel {
19 |
20 | private String trainDate;
21 | private String secret;
22 | private String trainCode;
23 | private String trainNumber;
24 | private String from;
25 | private String to;
26 | private String trainLocation;
27 | private String departDate;
28 | private String arriveDate;
29 | private String interval;
30 | private String leftTicket;
31 |
32 | private List seat = new ArrayList<>();
33 |
34 | public void setInfo(String[] info) {
35 | this.secret = URLDecoder.decode(info[0]);
36 | this.trainCode = info[2];
37 | this.trainNumber = info[3];
38 | this.from = info[6];
39 | this.to = info[7];
40 | this.trainLocation = info[15];
41 | this.departDate = info[8];
42 | this.arriveDate = info[9];
43 | this.interval = info[10];
44 | this.leftTicket = info[12];
45 |
46 | for(SeatLevelEnum seatLevelEnum : SeatLevelEnum.values()){
47 | SeatModel seatModel = new SeatModel(seatLevelEnum,info);
48 | seat.add(seatModel);
49 | }
50 | }
51 |
52 | @Override
53 | public String toString(){
54 | return "✅车次[" + trainNumber + "][" + Station.getNameByCode(from) + "-" + Station.getNameByCode(to) + "][" + departDate + "-" + arriveDate + "]:" + seat.toString();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/UserModel.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author zhangbin
7 | * @date 2019-05-31 15:19
8 | * @description: TODO
9 | */
10 | @Data
11 | public class UserModel {
12 | private String username;
13 | private String password;
14 | private String answer;
15 | private String uamtk;
16 | private LogdeviceModel logdeviceModel;
17 | private String globalRepeatSubmitToken;
18 | private String keyCheckIsChange;
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/UserTicketStore.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model;
2 |
3 | import com.qianxunclub.ticket.util.HttpUtil;
4 | import org.apache.http.impl.client.BasicCookieStore;
5 |
6 | import java.util.ArrayList;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-06-08 11:10
14 | * @description: TODO
15 | */
16 | public class UserTicketStore {
17 |
18 | public static List buyTicketInfoModelList = new ArrayList<>();
19 | public static Map httpUtilStore = new HashMap<>();
20 |
21 | public static void add(BuyTicketInfoModel buyTicketInfoModel) {
22 | buyTicketInfoModelList.add(buyTicketInfoModel);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/request/PassengerRequest.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model.request;
2 |
3 | import com.qianxunclub.ticket.model.UserModel;
4 |
5 | import org.springframework.beans.BeanUtils;
6 |
7 | import io.swagger.annotations.ApiModel;
8 | import io.swagger.annotations.ApiModelProperty;
9 | import lombok.Data;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-07-23 09:32
14 | * @description: TODO
15 | */
16 | @Data
17 | @ApiModel("登录信息")
18 | public class PassengerRequest {
19 | @ApiModelProperty("12306用户名")
20 | private String username;
21 | @ApiModelProperty("12306密码")
22 | private String password;
23 |
24 | public UserModel toUserModel() {
25 | UserModel userModel = new UserModel();
26 | BeanUtils.copyProperties(this, userModel);
27 | return userModel;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/request/TicketRequest.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model.request;
2 |
3 | import com.qianxunclub.ticket.constant.SeatLevelEnum;
4 | import com.qianxunclub.ticket.model.BuyTicketInfoModel;
5 |
6 | import org.springframework.beans.BeanUtils;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import io.swagger.annotations.ApiModel;
12 | import io.swagger.annotations.ApiModelProperty;
13 | import lombok.Data;
14 |
15 | /**
16 | * @author zhangbin
17 | * @date 2019-07-25 12:01
18 | * @description: TODO
19 | */
20 | @Data
21 | @ApiModel("购票信息")
22 | public class TicketRequest extends PassengerRequest {
23 | @ApiModelProperty(value = "乘车日期", example = "2019-07-08")
24 | private String date;
25 | @ApiModelProperty(value = "出发站点", example = "深圳")
26 | private String from;
27 | @ApiModelProperty(value = "到达站点", example = "三门峡")
28 | private String to;
29 | @ApiModelProperty(value = "车次", example = "G818")
30 | private String trainNumber;
31 | @ApiModelProperty(value = "乘客编号", example = "登录完成后会乘客信息allEncStr的值")
32 | private String allEncStr;
33 | @ApiModelProperty(value = "乘车人手机号", example = "1234567890")
34 | private String mobile;
35 | @ApiModelProperty(value = "乘车人真实姓名", example = "XXX")
36 | private String realName;
37 | @ApiModelProperty(value = "座位级别", example = "ONE,RUANWO")
38 | private String seat;
39 |
40 | public List toSeatList() {
41 | List seatList = new ArrayList<>();
42 | String[] seats = seat.split(",");
43 | for (int i = 0; i < seats.length; i++) {
44 | SeatLevelEnum seatLevelEnum = SeatLevelEnum.valueOf(seats[i]);
45 | seatList.add(seatLevelEnum);
46 | }
47 | return seatList;
48 | }
49 |
50 | public BuyTicketInfoModel toBuyTicketInfoModel() {
51 | BuyTicketInfoModel buyTicketInfoModel = new BuyTicketInfoModel();
52 | BeanUtils.copyProperties(this, buyTicketInfoModel);
53 | buyTicketInfoModel.setSeat(this.toSeatList());
54 | return buyTicketInfoModel;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/response/PassengerResponse.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model.response;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.Data;
6 |
7 | /**
8 | * @author zhangbin
9 | * @date 2019-07-26 16:51
10 | * @description: TODO
11 | */
12 | @Data
13 | @ApiModel("乘客信息")
14 | public class PassengerResponse {
15 | @ApiModelProperty("乘客编号")
16 | String allEncStr;
17 | @ApiModelProperty("乘客姓名")
18 | String passengerName;
19 | @ApiModelProperty("乘客性别")
20 | String sexName;
21 | @ApiModelProperty("证件类型")
22 | String passengerIdTypeName;
23 | @ApiModelProperty("证件号码")
24 | String passengerIdNo;
25 | @ApiModelProperty("电话号码")
26 | String mobileNo;
27 | @ApiModelProperty("手机号码")
28 | String phoneNo;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/model/response/TicketInfoResponse.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.model.response;
2 |
3 | import com.qianxunclub.ticket.constant.StatusEnum;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class TicketInfoResponse {
8 |
9 | private String username;
10 | private String date;
11 | private String from;
12 | private String to;
13 | private String trainNumber;
14 | private String realName;
15 | private int queryNum;
16 | private StatusEnum status = StatusEnum.START;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/dao/IpsDao.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.dao;
2 |
3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4 | import com.qianxunclub.ticket.repository.entity.Ips;
5 | import com.qianxunclub.ticket.repository.mapper.IpsMapper;
6 | import java.util.List;
7 | import lombok.AllArgsConstructor;
8 | import org.springframework.stereotype.Repository;
9 |
10 | /**
11 | * @author zhangbin
12 | * @date 2019-07-19 10:19
13 | * @description: TODO
14 | */
15 | @Repository
16 | @AllArgsConstructor
17 | public class IpsDao {
18 |
19 | private IpsMapper ipsMapper;
20 |
21 | public List list() {
22 | return ipsMapper.selectList(null);
23 | }
24 |
25 | public Ips getIp(String ip) {
26 | QueryWrapper queryWrapper = new QueryWrapper();
27 | queryWrapper.eq("ip", ip);
28 | return ipsMapper.selectOne(queryWrapper);
29 | }
30 |
31 | public int add(Ips ips) {
32 | return ipsMapper.insert(ips);
33 | }
34 |
35 | public int deleteById(Integer id) {
36 | return ipsMapper.deleteById(id);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/dao/ProxyIpDao.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.dao;
2 |
3 | import com.qianxunclub.ticket.repository.entity.ProxyIp;
4 | import com.qianxunclub.ticket.repository.mapper.ProxyIpMapper;
5 | import java.util.List;
6 | import lombok.AllArgsConstructor;
7 | import org.springframework.stereotype.Repository;
8 |
9 | /**
10 | * @author zhangbin
11 | * @date 2019-07-19 10:19
12 | * @description: TODO
13 | */
14 | @Repository
15 | @AllArgsConstructor
16 | public class ProxyIpDao {
17 |
18 | private ProxyIpMapper proxyIpMapper;
19 |
20 | public List list() {
21 | return proxyIpMapper.selectList(null);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/dao/TicketDao.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.dao;
2 |
3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4 | import com.qianxunclub.ticket.repository.entity.Ticket;
5 | import com.qianxunclub.ticket.repository.mapper.TicketMapper;
6 |
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.util.List;
10 |
11 | import lombok.AllArgsConstructor;
12 |
13 | /**
14 | * @author zhangbin
15 | * @date 2019-07-19 10:19
16 | * @description: TODO
17 | */
18 | @Repository
19 | @AllArgsConstructor
20 | public class TicketDao {
21 | private TicketMapper ticketMapper;
22 |
23 | public List list() {
24 | return ticketMapper.selectList(null);
25 | }
26 |
27 | public Ticket getTicketByUserName(String userName) {
28 | QueryWrapper queryWrapper = new QueryWrapper();
29 | queryWrapper.eq("username", userName);
30 | return ticketMapper.selectOne(queryWrapper);
31 | }
32 |
33 | public int add(Ticket ticket) {
34 | return ticketMapper.insert(ticket);
35 | }
36 |
37 | public int deleteById(Integer id) {
38 | return ticketMapper.deleteById(id);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/entity/Ips.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.entity;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author zhangbin
7 | * @date 2019-07-19 10:18
8 | * @description: TODO
9 | */
10 | @Data
11 | public class Ips {
12 | private int id;
13 | private String ip;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/entity/ProxyIp.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.entity;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author zhangbin
7 | */
8 | @Data
9 | public class ProxyIp {
10 |
11 | private int id;
12 | private String ip;
13 | private int port;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/entity/Ticket.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.TableField;
4 | import com.qianxunclub.ticket.constant.SeatLevelEnum;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import lombok.Data;
10 |
11 | /**
12 | * @author zhangbin
13 | * @date 2019-07-19 10:18
14 | * @description: TODO
15 | */
16 | @Data
17 | public class Ticket {
18 | private int id;
19 | private String username;
20 | private String password;
21 | @TableField("`date`")
22 | private String date;
23 |
24 | @TableField("`from`")
25 | private String from;
26 | @TableField("`to`")
27 | private String to;
28 |
29 | private String trainNumber;
30 |
31 | private String passengerCode;
32 |
33 | private String mobile;
34 |
35 | private String realName;
36 |
37 | private String seat;
38 |
39 | /**
40 | * 微信通知server酱秘钥
41 | */
42 | private String serverSckey;
43 |
44 | public List toSeatList() {
45 | List seatList = new ArrayList<>();
46 | String[] seats = seat.split(",");
47 | for (int i = 0; i < seats.length; i++) {
48 | SeatLevelEnum seatLevelEnum = SeatLevelEnum.valueOf(seats[i]);
49 | seatList.add(seatLevelEnum);
50 | }
51 | return seatList;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/mapper/IpsMapper.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import com.qianxunclub.ticket.repository.entity.Ips;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | /**
8 | * @author zhangbin
9 | * @date 2019-07-18 16:46
10 | * @description: TODO
11 | */
12 | @Mapper
13 | public interface IpsMapper extends BaseMapper {
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/mapper/ProxyIpMapper.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import com.qianxunclub.ticket.repository.entity.ProxyIp;
5 | import org.apache.ibatis.annotations.Mapper;
6 |
7 | /**
8 | * @author zhangbin
9 | * @date 2019-07-18 16:46
10 | * @description: TODO
11 | */
12 | @Mapper
13 | public interface ProxyIpMapper extends BaseMapper {
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/repository/mapper/TicketMapper.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.repository.mapper;
2 |
3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4 | import com.qianxunclub.ticket.repository.entity.Ticket;
5 |
6 | import org.apache.ibatis.annotations.Mapper;
7 |
8 | /**
9 | * @author zhangbin
10 | * @date 2019-07-18 16:46
11 | * @description: TODO
12 | */
13 | @Mapper
14 | public interface TicketMapper extends BaseMapper {
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/qianxunclub/ticket/service/ApiRequestService.java:
--------------------------------------------------------------------------------
1 | package com.qianxunclub.ticket.service;
2 |
3 | import com.google.gson.Gson;
4 |
5 | import com.qianxunclub.ticket.config.ApiConfig;
6 | import com.qianxunclub.ticket.ticket.Station;
7 | import com.qianxunclub.ticket.model.PassengerModel;
8 | import com.qianxunclub.ticket.model.BuyTicketInfoModel;
9 | import com.qianxunclub.ticket.model.TicketModel;
10 | import com.qianxunclub.ticket.model.UserModel;
11 | import com.qianxunclub.ticket.model.UserTicketStore;
12 | import com.qianxunclub.ticket.util.CommonUtil;
13 | import com.qianxunclub.ticket.util.HttpUtil;
14 |
15 | import org.apache.http.Consts;
16 | import org.apache.http.NameValuePair;
17 | import org.apache.http.client.entity.UrlEncodedFormEntity;
18 | import org.apache.http.client.methods.HttpGet;
19 | import org.apache.http.client.methods.HttpPost;
20 | import org.apache.http.message.BasicNameValuePair;
21 | import org.springframework.stereotype.Service;
22 | import org.springframework.util.CollectionUtils;
23 | import org.springframework.util.StringUtils;
24 |
25 |
26 | import java.text.SimpleDateFormat;
27 | import java.util.ArrayList;
28 | import java.util.Calendar;
29 | import java.util.HashMap;
30 | import java.util.List;
31 | import java.util.Map;
32 | import java.util.regex.Matcher;
33 | import java.util.regex.Pattern;
34 |
35 | import lombok.AllArgsConstructor;
36 | import lombok.extern.slf4j.Slf4j;
37 |
38 | /**
39 | * @author zhangbin
40 | * @date 2019-05-30 16:17
41 | * @description: TODO
42 | */
43 | @Slf4j
44 | @Service
45 | @AllArgsConstructor
46 | public class ApiRequestService {
47 |
48 | private ApiConfig apiConfig;
49 |
50 | public Map station() {
51 | HttpUtil httpUtil = new HttpUtil();
52 | Map stationMap = new HashMap<>();
53 | HttpGet httpGet = new HttpGet(apiConfig.getStation());
54 | String response = httpUtil.get(httpGet);
55 | String[] all = response.split("@");
56 | for (int i = 0; i < all.length; i++) {
57 | String[] station = all[i].split("[|]");
58 | if (station.length == 6) {
59 | stationMap.put(station[1], station[2]);
60 | }
61 | }
62 | return stationMap;
63 | }
64 |
65 | public void leftTicketInit(BuyTicketInfoModel buyTicketInfoModel) {
66 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(buyTicketInfoModel.getUsername());
67 | String url = String.format(apiConfig.getInit() + "?linktypeid=dc");
68 | HttpGet httpGet = new HttpGet(url);
69 | String result = httpUtil.get(httpGet);
70 | String leftTicketBaseUrl = CommonUtil
71 | .regString("(?<=var CLeftTicketUrl = ').*?(?=')", result);
72 | if (!StringUtils.isEmpty(leftTicketBaseUrl)) {
73 | apiConfig.setLeftTicketBaseUrl("/otn/" + leftTicketBaseUrl);
74 | }
75 | }
76 |
77 | public List queryTicket(BuyTicketInfoModel buyTicketInfoModel) {
78 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(buyTicketInfoModel.getUsername());
79 | String url = String.format(apiConfig.getLeftTicket(), apiConfig.getLeftTicketBaseUrl(),
80 | buyTicketInfoModel.getDate(),
81 | Station.getCodeByName(buyTicketInfoModel.getFrom()),
82 | Station.getCodeByName(buyTicketInfoModel.getTo()));
83 | HttpGet httpGet = new HttpGet(url);
84 | String response = httpUtil.get(httpGet);
85 | if (StringUtils.isEmpty(response)) {
86 | log.error("车票查询失败! 返回response:{}",response);
87 | return null;
88 | }
89 | Gson jsonResult = new Gson();
90 | Map rsmap = null;
91 | try {
92 | rsmap = jsonResult.fromJson(response, Map.class);
93 | } catch (Exception e) {
94 | log.error("车票查询失败!序列化异常response:{}",response);
95 | log.error("错误信息",e);
96 | }
97 |
98 | Map data = (Map) rsmap.get("data");
99 | List table = (List) data.get("result");
100 | List ticketModelList = new ArrayList<>();
101 | table.forEach(v -> {
102 | TicketModel ticketModel = new TicketModel();
103 | String[] info = v.split("[|]");
104 | ticketModel.setInfo(info);
105 | ticketModelList.add(ticketModel);
106 | });
107 | return ticketModelList;
108 | }
109 |
110 | public boolean isLoginPassCode(String username) {
111 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(username);
112 | HttpGet httpGet = new HttpGet(apiConfig.getLoginConfig());
113 | String response = httpUtil.get(httpGet);
114 | Gson jsonResult = new Gson();
115 | Map rsmap = jsonResult.fromJson(response, Map.class);
116 | Map data = (Map) rsmap.get("data");
117 | String isLoginPassCode = (String) data.get("is_login_passCode");
118 | return isLoginPassCode.equals("Y");
119 | }
120 |
121 | public String captchaImage(String userName) {
122 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userName);
123 | HttpGet httpGet = new HttpGet(String.format(apiConfig.getCaptchaImage(), Math.random()));
124 | String response = httpUtil.get(httpGet);
125 | Gson jsonResult = new Gson();
126 | Map rsmap = jsonResult.fromJson(response, Map.class);
127 | return (String) rsmap.get("image");
128 | }
129 |
130 | public boolean captchaCheck(String userName, String answer) {
131 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userName);
132 | HttpGet httpGet = new HttpGet(
133 | String.format(apiConfig.getCaptchaCheck(), answer, Math.random()));
134 | String response = httpUtil.get(httpGet);
135 | Gson jsonResult = new Gson();
136 | Map rsmap = jsonResult.fromJson(response, Map.class);
137 | if (!rsmap.getOrDefault("result_code", "").toString().equals("4")) {
138 | log.error("验证码错误", rsmap.get("result_message"));
139 | return false;
140 | }
141 | log.info("验证码校验成功");
142 | return true;
143 | }
144 |
145 | public boolean isLogin(UserModel userModel) {
146 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userModel.getUsername());
147 | HttpPost httpPost = new HttpPost(apiConfig.getUamtkStatic());
148 | List formparams = new ArrayList<>();
149 | formparams.add(new BasicNameValuePair("appid", "otn"));
150 | UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formparams,
151 | Consts.UTF_8);
152 | httpPost.setEntity(urlEncodedFormEntity);
153 | String response = httpUtil.post(httpPost);
154 | Gson jsonResult = new Gson();
155 | Map rsmap = jsonResult.fromJson(response, Map.class);
156 | if ("0.0".equals(rsmap.get("result_code").toString())) {
157 | userModel.setUamtk(rsmap.get("newapptk").toString());
158 | UserTicketStore.httpUtilStore.put(userModel.getUsername(), httpUtil);
159 | return true;
160 | } else {
161 | return false;
162 | }
163 | }
164 |
165 | public boolean login(UserModel userModel) {
166 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userModel.getUsername());
167 | HttpPost httpPost = new HttpPost(apiConfig.getLogin());
168 | List formparams = new ArrayList<>();
169 | formparams.add(new BasicNameValuePair("username", userModel.getUsername()));
170 | formparams.add(new BasicNameValuePair("password", userModel.getPassword()));
171 | formparams.add(new BasicNameValuePair("appid", "otn"));
172 | formparams.add(new BasicNameValuePair("answer", userModel.getAnswer()));
173 |
174 | UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formparams,
175 | Consts.UTF_8);
176 | httpPost.setEntity(urlEncodedFormEntity);
177 | String response = httpUtil.post(httpPost);
178 | if (StringUtils.isEmpty(response)) {
179 | log.error("登录失败!");
180 | return false;
181 | }
182 | Gson jsonResult = new Gson();
183 | Map rsmap = jsonResult.fromJson(response, Map.class);
184 | if (!"0.0".equals(rsmap.getOrDefault("result_code", "").toString())) {
185 | log.error("登陆失败:{}", rsmap);
186 | return false;
187 | }
188 | UserTicketStore.httpUtilStore.put(userModel.getUsername(), httpUtil);
189 | userModel.setUamtk(rsmap.get("uamtk").toString());
190 | return true;
191 | }
192 |
193 | public String uamtk(String userName) {
194 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userName);
195 | HttpPost httpPost = new HttpPost(apiConfig.getUamtk());
196 | List formparams = new ArrayList<>();
197 | formparams.add(new BasicNameValuePair("appid", "otn"));
198 | UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formparams,
199 | Consts.UTF_8);
200 | httpPost.setEntity(urlEncodedFormEntity);
201 | Gson jsonResult = new Gson();
202 | String response = httpUtil.post(httpPost);
203 | Map rsmap = jsonResult.fromJson(response, Map.class);
204 | if ("0.0".equals(rsmap.getOrDefault("result_code", "").toString())) {
205 | UserTicketStore.httpUtilStore.put(userName, httpUtil);
206 | return rsmap.get("newapptk").toString();
207 | }
208 | return null;
209 | }
210 |
211 | public String uamauthclient(String userName, String tk) {
212 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userName);
213 | HttpPost httpPost = new HttpPost(apiConfig.getUamauthclient());
214 | List formparams = new ArrayList<>();
215 | formparams.add(new BasicNameValuePair("tk", tk));
216 | UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formparams,
217 | Consts.UTF_8);
218 | httpPost.setEntity(urlEncodedFormEntity);
219 | Gson jsonResult = new Gson();
220 | String response = httpUtil.post(httpPost);
221 | Map rsmap = jsonResult.fromJson(response, Map.class);
222 | if ("0.0".equals(rsmap.getOrDefault("result_code", "").toString())) {
223 | UserTicketStore.httpUtilStore.put(userName, httpUtil);
224 | return rsmap.get("apptk").toString();
225 | }
226 | return null;
227 | }
228 |
229 |
230 | public List passengers(String userName) {
231 | HttpUtil httpUtil = UserTicketStore.httpUtilStore.get(userName);
232 | HttpPost httpPost = new HttpPost(apiConfig.getPassengers());
233 | List formparams = new ArrayList<>();
234 | formparams.add(new BasicNameValuePair("pageIndex", "1"));
235 | formparams.add(new BasicNameValuePair("pageSize", "100"));
236 | UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formparams,
237 | Consts.UTF_8);
238 | httpPost.setEntity(urlEncodedFormEntity);
239 | Gson jsonResult = new Gson();
240 | String response = httpUtil.post(httpPost);
241 | Map rsmap = jsonResult.fromJson(response, Map.class);
242 | List passengerModelList = new ArrayList<>();
243 | if (null != rsmap.get("status") && rsmap.get("status").toString().equals("true")) {
244 | List