1
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2
|
|
|
from __future__ import absolute_import, unicode_literals
|
3
|
|
|
|
4
|
|
|
import inspect
|
5
|
|
|
import requests
|
6
|
|
|
|
7
|
|
|
from school_api.client.utils import get_time_list
|
8
|
|
|
from school_api.client.api.base import BaseSchoolApi
|
9
|
|
|
from school_api.client.api.login import Login
|
10
|
|
|
from school_api.client.api.utils import get_view_state_from_html
|
11
|
|
|
from school_api.session.memorystorage import MemoryStorage
|
12
|
|
|
from school_api.utils import to_text, ObjectDict
|
13
|
|
|
from school_api.config import URL_ENDPOINT, CLASS_TIME, LOGIN_SESSION_SAVE_TIME
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
def _is_api_endpoint(obj):
|
17
|
|
|
return isinstance(obj, BaseSchoolApi)
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
class BaseSchoolClient(object):
|
|
|
|
|
21
|
|
|
|
22
|
|
|
def __init__(self, url, **kwargs):
|
23
|
|
|
|
24
|
|
|
class_time_list = kwargs.get('class_time_list') or CLASS_TIME
|
25
|
|
|
time_list = get_time_list(class_time_list)
|
26
|
|
|
|
27
|
|
|
self.school = {
|
28
|
|
|
'url': url,
|
29
|
|
|
'debug': kwargs.get('debug'),
|
30
|
|
|
'name': to_text(kwargs.get('name')),
|
31
|
|
|
'code': kwargs.get('code'),
|
32
|
|
|
'use_ex_handle': kwargs.get('use_ex_handle', True),
|
33
|
|
|
'exist_verify': kwargs.get('exist_verify', True),
|
34
|
|
|
'lan_url': kwargs.get('lan_url'),
|
35
|
|
|
'proxies': kwargs.get('proxies'),
|
36
|
|
|
'priority_porxy': kwargs.get('priority_porxy'),
|
37
|
|
|
'timeout': kwargs.get('timeout', 10),
|
38
|
|
|
'login_url': kwargs.get('login_url_path', '/default2.aspx'),
|
39
|
|
|
'url_endpoint': kwargs.get('url_endpoint') or URL_ENDPOINT,
|
40
|
|
|
'time_list': time_list
|
41
|
|
|
}
|
42
|
|
|
storage = kwargs.get('session', MemoryStorage)
|
43
|
|
|
self.session = storage(self.school['code'])
|
44
|
|
|
self.init_login_view_state(kwargs.get('login_view_state', {}))
|
45
|
|
|
self.school = ObjectDict(self.school)
|
46
|
|
|
|
47
|
|
|
def init_login_view_state(self, login_view_state):
|
48
|
|
|
""" 初始化 login_view_state"""
|
49
|
|
|
for url_key, view_state in login_view_state.items():
|
50
|
|
|
self.session.set('login_view:' + url_key, view_state)
|
51
|
|
|
|
52
|
|
|
|
53
|
|
|
class BaseUserClient(object):
|
|
|
|
|
54
|
|
|
"""docstring for BaseUserClient"""
|
55
|
|
|
|
56
|
|
|
_proxy = None
|
57
|
|
|
login = Login()
|
58
|
|
|
|
59
|
|
|
def __new__(cls, *args):
|
|
|
|
|
60
|
|
|
self = super(BaseUserClient, cls).__new__(cls)
|
61
|
|
|
api_endpoints = inspect.getmembers(self, _is_api_endpoint)
|
62
|
|
|
for name, api in api_endpoints:
|
63
|
|
|
api_cls = type(api)
|
64
|
|
|
api = api_cls(self)
|
65
|
|
|
setattr(self, name, api)
|
66
|
|
|
return self
|
67
|
|
|
|
68
|
|
|
def __init__(self, school, account, password, user_type):
|
69
|
|
|
self._http = requests.Session()
|
70
|
|
|
self._http.headers.update({
|
71
|
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) '
|
72
|
|
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
73
|
|
|
'Chrome/62.0.3202.89 Safari/537.36',
|
74
|
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
75
|
|
|
})
|
76
|
|
|
self.account = to_text(account)
|
77
|
|
|
self.password = password
|
78
|
|
|
self.user_type = user_type
|
79
|
|
|
self.school = school.school
|
80
|
|
|
self.base_url = self.school.url
|
81
|
|
|
self.session = school.session
|
82
|
|
|
|
83
|
|
|
if self.school.priority_porxy:
|
84
|
|
|
self.set_proxy()
|
85
|
|
|
|
86
|
|
|
def _request(self, method, url_or_endpoint, **kwargs):
|
87
|
|
|
if not url_or_endpoint.startswith(('http://', 'https://')):
|
88
|
|
|
url = '{base}{endpoint}'.format(
|
89
|
|
|
base=self.base_url,
|
90
|
|
|
endpoint=url_or_endpoint
|
91
|
|
|
)
|
92
|
|
|
else:
|
93
|
|
|
url = url_or_endpoint
|
94
|
|
|
|
95
|
|
|
kwargs['timeout'] = kwargs.get('timeout', self.school.timeout)
|
96
|
|
|
res = self._http.request(
|
97
|
|
|
method=method,
|
98
|
|
|
url=url,
|
99
|
|
|
proxies=self._proxy,
|
100
|
|
|
**kwargs
|
101
|
|
|
)
|
102
|
|
|
return res
|
103
|
|
|
|
104
|
|
|
def get(self, url, **kwargs):
|
|
|
|
|
105
|
|
|
return self._request(
|
106
|
|
|
method='GET',
|
107
|
|
|
url_or_endpoint=url,
|
108
|
|
|
**kwargs
|
109
|
|
|
)
|
110
|
|
|
|
111
|
|
|
def post(self, url, **kwargs):
|
|
|
|
|
112
|
|
|
return self._request(
|
113
|
|
|
method='POST',
|
114
|
|
|
url_or_endpoint=url,
|
115
|
|
|
**kwargs
|
116
|
|
|
)
|
117
|
|
|
|
118
|
|
|
def head(self, url, **kwargs):
|
|
|
|
|
119
|
|
|
return self._request(
|
120
|
|
|
method='HEAD',
|
121
|
|
|
url_or_endpoint=url,
|
122
|
|
|
**kwargs
|
123
|
|
|
)
|
124
|
|
|
|
125
|
|
|
def set_proxy(self):
|
126
|
|
|
""" 设置代理 """
|
127
|
|
|
self.school.priority_porxy = True
|
128
|
|
|
self.base_url = self.school.lan_url or self.base_url
|
129
|
|
|
self._proxy = self.school.proxies
|
130
|
|
|
|
131
|
|
|
def update_headers(self, headers_dict):
|
|
|
|
|
132
|
|
|
self._http.headers.update(headers_dict)
|
133
|
|
|
|
134
|
|
|
def get_view_state(self, url_suffix, **kwargs):
|
|
|
|
|
135
|
|
|
res = self.get(url_suffix, allow_redirects=False, **kwargs)
|
136
|
|
|
if res.status_code != 200:
|
137
|
|
|
raise requests.RequestException
|
138
|
|
|
return get_view_state_from_html(res.text)
|
139
|
|
|
|
140
|
|
|
def get_login_session_key(self):
|
141
|
|
|
''' 获取缓存登录会话的key '''
|
142
|
|
|
url = self.base_url + self.school.login_url
|
143
|
|
|
key = '{}:{}:{}'.format('login_session', url, self.account)
|
144
|
|
|
return key
|
145
|
|
|
|
146
|
|
|
def get_login_view_state(self, **kwargs):
|
147
|
|
|
''' 获取登录的view_state '''
|
148
|
|
|
base_key = 'login_view:' + self.base_url + self.school.login_url
|
149
|
|
|
if not self.session.get(base_key):
|
150
|
|
|
view_state = self.get_view_state(self.school.login_url, **kwargs)
|
151
|
|
|
self.session.set(base_key, view_state)
|
152
|
|
|
return self.session.get(base_key)
|
153
|
|
|
|
154
|
|
|
def session_management(self):
|
155
|
|
|
''' 登录会话管理 '''
|
156
|
|
|
login_session = None
|
157
|
|
|
if self._get_login_session():
|
158
|
|
|
session_expires_time = LOGIN_SESSION_SAVE_TIME \
|
159
|
|
|
- self._get_login_session_expires_time()
|
160
|
|
|
|
161
|
|
|
if session_expires_time < 60 * 5:
|
162
|
|
|
# 五分钟内,不检测登录会话是否过期
|
163
|
|
|
login_session = self
|
164
|
|
|
elif self.login.check_session():
|
165
|
|
|
# 登录会话检测
|
166
|
|
|
if 60 * 5 < session_expires_time < 60 * 10:
|
167
|
|
|
# 登录比较频繁的,更新会话时间 (例如:部门账号操作)
|
168
|
|
|
self.save_login_session()
|
169
|
|
|
login_session = self
|
170
|
|
|
else:
|
171
|
|
|
# 会话过期, 删除会话
|
172
|
|
|
self._del_login_session()
|
173
|
|
|
|
174
|
|
|
return login_session
|
175
|
|
|
|
176
|
|
|
def save_login_session(self):
|
177
|
|
|
''' 保存登录会话 '''
|
178
|
|
|
key = self.get_login_session_key()
|
179
|
|
|
cookie = self._http.cookies.get_dict()
|
180
|
|
|
self.session.set(key, cookie, LOGIN_SESSION_SAVE_TIME)
|
181
|
|
|
|
182
|
|
|
def _del_login_session(self):
|
183
|
|
|
''' 删除登录会话 '''
|
184
|
|
|
key = self.get_login_session_key()
|
185
|
|
|
self.session.delete(key)
|
186
|
|
|
self._http.cookies.clear()
|
187
|
|
|
|
188
|
|
|
def _get_login_session(self):
|
189
|
|
|
''' 获取登录会话 '''
|
190
|
|
|
key = self.get_login_session_key()
|
191
|
|
|
cookie = self.session.get(key)
|
192
|
|
|
if not cookie:
|
193
|
|
|
return None
|
194
|
|
|
url = self.base_url + self.school.login_url
|
195
|
|
|
self.update_headers({'Referer': url})
|
196
|
|
|
self._http.cookies.update(cookie)
|
197
|
|
|
return True
|
198
|
|
|
|
199
|
|
|
def _get_login_session_expires_time(self):
|
200
|
|
|
""" 获取登录会话过期时间 """
|
201
|
|
|
url = self.base_url + self.school.login_url
|
202
|
|
|
key = '{}:{}:{}'.format('login_session', url, self.account)
|
203
|
|
|
return self.session.expires_time(key)
|
204
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.