diff --git a/main.py b/main.py index 6560fe3..12d9725 100644 --- a/main.py +++ b/main.py @@ -9,9 +9,12 @@ from py12306.user.user import User def main(): # Thread(target=Query.run).start() # 余票查询 - create_thread_and_run(User, 'run', wait=False) - # Query.run() + # create_thread_and_run(User, 'run', wait=False) + User.run() + Query.run() # Query.run() + while True: + sleep(1) pass diff --git a/py12306/config.py b/py12306/config.py index be00cdd..f90743e 100644 --- a/py12306/config.py +++ b/py12306/config.py @@ -48,5 +48,19 @@ if path.exists(CONFIG_FILE): exec(open(CONFIG_FILE, encoding='utf8').read()) +class UserType: + ADULT = 1 + CHILD = 2 + STUDENT = 3 + SOLDIER = 4 + + dicts = { + '成人': ADULT, + '儿童': CHILD, + '学生': STUDENT, + '残疾军人、伤残人民警察': SOLDIER, + } + + def get(key, default=None): return eval(key) diff --git a/py12306/exceptions/MemberInvalidException.py b/py12306/exceptions/MemberInvalidException.py new file mode 100755 index 0000000..de37bb3 --- /dev/null +++ b/py12306/exceptions/MemberInvalidException.py @@ -0,0 +1,4 @@ +class MemberInvalidException(Exception): + pass + + diff --git a/py12306/exceptions/__init__.py b/py12306/exceptions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py12306/helpers/api.py b/py12306/helpers/api.py index 43a54d4..9e21f92 100644 --- a/py12306/helpers/api.py +++ b/py12306/helpers/api.py @@ -42,6 +42,7 @@ API_AUTH_UAMAUTHCLIENT = { API_USER_INFO = { 'url': BASE_URL_OF_12306 + '/otn/modifyUser/initQueryUserInfoApi' } +API_USER_PASSENGERS = BASE_URL_OF_12306 + '/otn/confirmPassenger/getPassengerDTOs' urls = { "auth": { # 登录接口 diff --git a/py12306/helpers/func.py b/py12306/helpers/func.py index c6f11af..b44bace 100644 --- a/py12306/helpers/func.py +++ b/py12306/helpers/func.py @@ -75,15 +75,26 @@ def time_now(): return datetime.datetime.now() -def create_thread_and_run(jobs, callback_name, wait=True): +def create_thread_and_run(jobs, callback_name, wait=True, daemon=True): threads = [] if not isinstance(jobs, list): jobs = [jobs] for job in jobs: thread = threading.Thread(target=getattr(job, callback_name)) + thread.setDaemon(daemon) thread.start() threads.append(thread) if wait: for thread in threads: thread.join() + +def dict_find_key_by_value(data, value, default=None): + result = [k for k, v in data.items() if v == value] + return result.pop() if len(result) else default + + +def array_dict_find_by_key_value(data, key, value, default=None): + result = [v for k, v in enumerate(data) if key in v and v[key] == value] + return result.pop() if len(result) else default + # def test: diff --git a/py12306/log/base.py b/py12306/log/base.py index 0896a4b..67638f9 100644 --- a/py12306/log/base.py +++ b/py12306/log/base.py @@ -22,7 +22,7 @@ class BaseLog: return self @classmethod - def flush(cls, sep='\n', end='\n', file=None): + def flush(cls, sep='\n', end='\n', file=None, exit=False): self = cls() if self.quick_log: logs = self.quick_log @@ -41,6 +41,8 @@ class BaseLog: else: if logs: del self.thread_logs[current_thread_id()] # print(self.logs) + if exit: + sys.exit() @classmethod def add_quick_log(cls, content): diff --git a/py12306/log/user_log.py b/py12306/log/user_log.py index 22af8af..a04ee5c 100644 --- a/py12306/log/user_log.py +++ b/py12306/log/user_log.py @@ -14,6 +14,9 @@ class UserLog(BaseLog): MESSAGE_LOADED_USER_BUT_EXPIRED = '用户状态已过期,正在重新登录' MESSAGE_USER_HEARTBEAT_NORMAL = '用户 {} 心跳正常,下次检测 {} 秒后' + MESSAGE_GET_USER_PASSENGERS_FAIL = '获取用户乘客列表失败,错误原因: {} {} 秒后重试' + MESSAGE_USER_PASSENGERS_IS_INVALID = '乘客信息校验失败,在账号 {} 中未找到该乘客: {}' + def __init__(self): super().__init__() self.init_data() @@ -45,3 +48,11 @@ class UserLog(BaseLog): self.add_log('正在登录用户 {}'.format(user.user_name)) self.flush() return self + + @classmethod + def print_user_passenger_init_success(cls, passengers): + self = cls() + result = [passenger.get('name') + '(' + passenger.get('type_text') + ')' for passenger in passengers] + self.add_quick_log('# 乘客验证成功 {} #'.format(', '.join(result))) + self.flush() + return self diff --git a/py12306/order/order.py b/py12306/order/order.py new file mode 100644 index 0000000..204451f --- /dev/null +++ b/py12306/order/order.py @@ -0,0 +1,34 @@ +from py12306.helpers.app import * +from py12306.helpers.func import * +from py12306.log.user_log import UserLog +from py12306.user.job import UserJob + + +class Order: + """ + 处理下单 + """ + heartbeat = 60 * 2 + users = [] + + def __init__(self): + pass + + @classmethod + def run(cls): + self = cls() + app_available_check() + self.start() + pass + + def start(self): + self.init_users() + UserLog.print_init_users(users=self.users) + # 多线程维护用户 + create_thread_and_run(jobs=self.users, callback_name='run', wait=False) + + def init_users(self): + accounts = config.USER_ACCOUNTS + for account in accounts: + user = UserJob(info=account, user=self) + self.users.append(user) diff --git a/py12306/query/job.py b/py12306/query/job.py index f4852fa..2041515 100644 --- a/py12306/query/job.py +++ b/py12306/query/job.py @@ -2,6 +2,8 @@ from py12306.helpers.api import LEFT_TICKETS from py12306.helpers.station import Station from py12306.log.query_log import QueryLog from py12306.helpers.func import * +from py12306.log.user_log import UserLog +from py12306.user.user import User class Job: @@ -15,11 +17,13 @@ class Job: left_station_code = '' arrive_station_code = '' + account_key = 0 allow_seats = [] allow_train_numbers = [] members = [] member_num = 0 member_num_take = 0 # 最终提交的人数 + passengers = [] allow_less_member = False interval = {} @@ -40,6 +44,7 @@ class Job: self.left_station_code = Station.get_station_key_by_name(self.left_station) self.arrive_station_code = Station.get_station_key_by_name(self.arrive_station) + self.account_key = info.get('account_key') self.allow_seats = info.get('seats') self.allow_train_numbers = info.get('train_numbers') self.members = info.get('members') @@ -62,6 +67,9 @@ class Job: :param job: :return: """ + if not self.passengers: + User.check_members(self.members, self.account_key, call_back=self.set_passengers) + QueryLog.print_job_start() for date in self.left_dates: response = self.query_by_date(date) @@ -109,26 +117,29 @@ class Job: if not self.is_has_ticket(ticket_info): continue allow_seats = self.allow_seats if self.allow_seats else list(config.SEAT_TYPES.values()) # 未设置 则所有可用 - for seat in allow_seats: # 检查座位是否有票 - ticket_of_seat = ticket_info[get_seat_number_by_name(seat)] - if not self.is_has_ticket_by_seat(ticket_of_seat): # 座位是否有效 + self.handle_seats(allow_seats, ticket_info) + + def handle_seats(self, allow_seats, ticket_info): + for seat in allow_seats: # 检查座位是否有票 + ticket_of_seat = ticket_info[get_seat_number_by_name(seat)] + if not self.is_has_ticket_by_seat(ticket_of_seat): # 座位是否有效 + continue + QueryLog.print_ticket_seat_available(left_date=self.get_info_of_left_date(), + train_number=self.get_info_of_train_number(), seat_type=seat, + rest_num=ticket_of_seat) + if not self.is_member_number_valid(ticket_of_seat): # 乘车人数是否有效 + if self.allow_less_member: + self.member_num_take = int(ticket_of_seat) + QueryLog.print_ticket_num_less_than_specified(ticket_of_seat, self) + else: + QueryLog.add_quick_log( + QueryLog.MESSAGE_GIVE_UP_CHANCE_CAUSE_TICKET_NUM_LESS_THAN_SPECIFIED).flush() continue - QueryLog.print_ticket_seat_available(left_date=self.get_info_of_left_date(), - train_number=self.get_info_of_train_number(), seat_type=seat, - rest_num=ticket_of_seat) - if not self.is_member_number_valid(ticket_of_seat): # 乘车人数是否有效 - if self.allow_less_member: - self.member_num_take = int(ticket_of_seat) - QueryLog.print_ticket_num_less_than_specified(ticket_of_seat, self) - else: - QueryLog.add_quick_log( - QueryLog.MESSAGE_GIVE_UP_CHANCE_CAUSE_TICKET_NUM_LESS_THAN_SPECIFIED).flush() - continue - # 检查完成 开始提交订单 - QueryLog.print_ticket_available(left_date=self.get_info_of_left_date(), - train_number=self.get_info_of_train_number(), - rest_num=ticket_of_seat) - print('检查完成 开始提交订单') + # 检查完成 开始提交订单 + QueryLog.print_ticket_available(left_date=self.get_info_of_left_date(), + train_number=self.get_info_of_train_number(), + rest_num=ticket_of_seat) + print('检查完成 开始提交订单') def get_results(self, response): """ @@ -164,6 +175,10 @@ class Job: QueryLog.add_stay_log(interval) stay_second(interval) + def set_passengers(self, passengers): + UserLog.print_user_passenger_init_success(passengers) + self.passengers = passengers + # 提供一些便利方法 def get_info_of_left_date(self): return self.ticket_info[self.INDEX_LEFT_DATE] diff --git a/py12306/query/query.py b/py12306/query/query.py index 9f1158a..9de27cc 100644 --- a/py12306/query/query.py +++ b/py12306/query/query.py @@ -31,6 +31,7 @@ class Query: pass def start(self): + return # DEBUG self.init_jobs() QueryLog.print_init_jobs(jobs=self.jobs) while True: diff --git a/py12306/user/job.py b/py12306/user/job.py index 5a5b963..9ee5496 100644 --- a/py12306/user/job.py +++ b/py12306/user/job.py @@ -1,7 +1,8 @@ import pickle from os import path -from py12306.helpers.api import API_USER_CHECK, API_BASE_LOGIN, API_AUTH_UAMTK, API_AUTH_UAMAUTHCLIENT, API_USER_INFO +from py12306.config import * +from py12306.helpers.api import * from py12306.helpers.app import * from py12306.helpers.auth_code import AuthCode from py12306.helpers.func import * @@ -18,6 +19,9 @@ class UserJob: user = None info = {} # 用户信息 last_heartbeat = None + is_ready = False + passengers = [] + retry_time = 5 def __init__(self, info, user): self.session = Request() @@ -50,6 +54,7 @@ class UserJob: if self.is_first_time() or not self.check_user_is_login(): self.handle_login() + self.is_ready = True UserLog.add_quick_log(UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format(self.get_name(), self.heartbeat)).flush() self.last_heartbeat = time_now() @@ -178,3 +183,55 @@ class UserJob: self.did_loaded_user() return True return None + + def check_is_ready(self): + return self.is_ready + + def get_user_passengers(self): + if self.passengers: return self.passengers + response = self.session.post(API_USER_PASSENGERS) + result = response.json() + if result.get('data') and result.get('data').get('normal_passengers'): + self.passengers = result.get('data').get('normal_passengers') + return self.passengers + else: + UserLog.add_quick_log( + UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format(result.get('messages', '-'), self.retry_time)) + stay_second(self.retry_time) + return self.get_user_passengers() + + def get_passengers_by_members(self, members): + """ + 获取格式化后的乘客信息 + :param members: + :return: + [{ + name: '项羽', + type: 1, + id_card: 0000000000000000000, + type_text: '成人' + }] + """ + self.get_user_passengers() + results = [] + for member in members: + child_check = array_dict_find_by_key_value(results, 'name', member) + if child_check: + new_member = child_check.copy() + new_member['type'] = UserType.CHILD + new_member['type_text'] = dict_find_key_by_value(UserType.dicts, int(new_member['type'])) + else: + passenger = array_dict_find_by_key_value(self.passengers, 'passenger_name', member) + if not passenger: + UserLog.add_quick_log( + UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format(self.user_name, member)).flush( + exit=True) # TODO 需要优化 + new_member = { + 'name': passenger.get('passenger_name'), + 'id_card': passenger.get('passenger_id_no'), + 'type': passenger.get('passenger_type'), + 'type_text': dict_find_key_by_value(UserType.dicts, int(passenger.get('passenger_type'))) + } + results.append(new_member) + + return results diff --git a/py12306/user/user.py b/py12306/user/user.py index 77e8c9e..7c13119 100644 --- a/py12306/user/user.py +++ b/py12306/user/user.py @@ -30,3 +30,19 @@ class User: for account in accounts: user = UserJob(info=account, user=self) self.users.append(user) + + @classmethod + def check_members(cls, members, user_key, call_back): + """ + 检测乘客信息 + :param passengers: + :return: + """ + self = cls() + + for user in self.users: + assert isinstance(user, UserJob) + if user.key == user_key and user.check_is_ready(): + passengers = user.get_passengers_by_members(members) + call_back(passengers) + pass