-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
430 lines (353 loc) · 15.5 KB
/
Copy pathapp.py
File metadata and controls
430 lines (353 loc) · 15.5 KB
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
#Flask API服务器,提供登录验证功能
import os
import re
import json
from datetime import datetime
from pathlib import Path
from flask import Flask, request, jsonify
from verification_service import send_email_code, gen_code, save_code, verify_code
# 激活虚拟环境变量
# import dotenv
# dotenv.load_dotenv('doNotUpload.env')
app = Flask(__name__)
# 邮箱验证正则表达式
EMAIL_PATTERN = re.compile(r'^[a-zA-Z0-9._%+-]+@wku\.edu\.cn$')
# 用户数据存储文件
USER_DATA_FILE = "/home/cxy/log/users_data.json"
def validate_wku_email(email):
"""验证邮箱是否是有效的@wku.edu.cn邮箱"""
return EMAIL_PATTERN.match(email) is not None
def load_users_data():
"""加载用户数据"""
if not Path(USER_DATA_FILE).exists():
return {}
try:
with open(USER_DATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"加载用户数据时出错: {e}")
return {}
def save_users_data(data):
"""保存用户数据"""
try:
with open(USER_DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存用户数据时出错: {e}")
return False
def save_user_room_info(email, room_info):
"""保存用户宿舍信息"""
users_data = load_users_data()
user_record = {
"email": email,
"room_info": room_info,
"registration_time": datetime.now().isoformat(),
"last_login": datetime.now().isoformat(),
"login_count": 1 # 新增登录次数计数器,初始为1
}
users_data[email] = user_record
return save_users_data(users_data)
@app.route('/api/send_code', methods=['POST'])
def send_code():
"""发送验证码API"""
try:
data = request.get_json()
if not data or 'email' not in data:
return jsonify({'error': '邮箱地址是必需的'}), 400
email = data['email'].strip().lower()
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
# 生成验证码并保存到Redis
code = gen_code()
save_code(email, code, ttl=300) # 5分钟有效期
# 异步发送邮件
send_email_code.delay(email)
return jsonify({'message': '验证码已发送,请检查您的邮箱'}), 202
except Exception as e:
print(f"发送验证码时出错: {e}")
return jsonify({'error': '发送验证码失败,请稍后重试'}), 500
@app.route('/api/verify_code', methods=['POST'])
def verify_code_api():
"""验证验证码API"""
try:
data = request.get_json()
if not data or 'email' not in data or 'code' not in data:
return jsonify({'error': '邮箱和验证码都是必需的'}), 400
email = data['email'].strip().lower()
code = data['code'].strip()
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
# 验证验证码
if verify_code(email, code):
return jsonify({'valid': True, 'message': '验证成功'}), 200
else:
return jsonify({'valid': False, 'error': '验证码错误或已过期'}), 400
except Exception as e:
print(f"验证验证码时出错: {e}")
return jsonify({'error': '验证失败,请稍后重试'}), 500
@app.route('/api/verify_code_with_room', methods=['POST'])
def verify_code_with_room_api():
"""验证验证码并保存宿舍信息API"""
try:
data = request.get_json()
if not data or 'email' not in data or 'code' not in data or 'room_info' not in data:
return jsonify({'error': '邮箱、验证码和宿舍信息都是必需的'}), 400
email = data['email'].strip().lower()
code = data['code'].strip()
room_info = data['room_info']
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
# 验证宿舍信息完整性
required_room_fields = ['building_code', 'building_name', 'floor_code', 'floor_name', 'room_code', 'room_name']
for field in required_room_fields:
if field not in room_info or not room_info[field]:
return jsonify({'error': f'宿舍信息不完整,缺少: {field}'}), 400
# 验证验证码
if not verify_code(email, code):
return jsonify({'valid': False, 'error': '验证码错误或已过期'}), 400
# 检查用户是否已经注册
users_data = load_users_data()
if email in users_data:
# 更新最后登录时间和登录次数
users_data[email]['last_login'] = datetime.now().isoformat()
# 增加登录次数计数器,如果不存在则初始化为1
users_data[email]['login_count'] = users_data[email].get('login_count', 0) + 1
save_users_data(users_data)
return jsonify({'valid': True, 'message': '登录成功', 'user_exists': True}), 200
# 保存用户宿舍信息
if save_user_room_info(email, room_info):
print(f"✅ 新用户注册成功: {email}")
print(f" 宿舍信息: {room_info['building_name']} {room_info['floor_name']} {room_info['room_name']}")
return jsonify({
'valid': True,
'message': '注册成功',
'user_exists': False,
'room_info': room_info
}), 200
else:
return jsonify({'error': '保存用户信息失败,请稍后重试'}), 500
except Exception as e:
print(f"验证验证码并保存宿舍信息时出错: {e}")
return jsonify({'error': '处理失败,请稍后重试'}), 500
@app.route('/api/user_info/<email>', methods=['GET'])
def get_user_info(email):
"""获取用户信息API"""
try:
email = email.strip().lower()
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
users_data = load_users_data()
if email in users_data:
user_info = users_data[email].copy()
return jsonify({'exists': True, 'user_info': user_info}), 200
else:
return jsonify({'exists': False, 'message': '用户不存在'}), 404
except Exception as e:
print(f"获取用户信息时出错: {e}")
return jsonify({'error': '获取用户信息失败'}), 500
@app.route('/api/users_stats', methods=['GET'])
def get_users_stats():
"""获取用户统计信息API"""
try:
users_data = load_users_data()
total_users = len(users_data)
# 按楼栋统计
building_stats = {}
# 登录次数统计
total_logins = 0
login_counts = []
for user_info in users_data.values():
room_info = user_info.get('room_info', {})
building = room_info.get('building_name', '未知楼栋')
building_stats[building] = building_stats.get(building, 0) + 1
# 统计登录次数
login_count = user_info.get('login_count', 0)
total_logins += login_count
login_counts.append(login_count)
# 计算登录次数统计
avg_logins = total_logins / total_users if total_users > 0 else 0
max_logins = max(login_counts) if login_counts else 0
min_logins = min(login_counts) if login_counts else 0
# 获取数据库统计信息
try:
from database import get_query_statistics
db_stats = get_query_statistics()
except Exception as e:
print(f"获取数据库统计信息失败: {e}")
db_stats = {}
return jsonify({
'total_users': total_users,
'building_stats': building_stats,
'data_file': USER_DATA_FILE,
'database_stats': db_stats,
'login_stats': {
'total_logins': total_logins,
'average_logins': round(avg_logins, 2),
'max_logins': max_logins,
'min_logins': min_logins
}
}), 200
except Exception as e:
print(f"获取用户统计信息时出错: {e}")
return jsonify({'error': '获取统计信息失败'}), 500
@app.route('/api/query_history/<email>', methods=['GET'])
def get_query_history(email):
"""获取用户查询历史API"""
try:
email = email.strip().lower()
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
# 获取查询历史
try:
from database import get_user_query_history
history = get_user_query_history(email, limit=30)
return jsonify({
'email': email,
'history': history,
'total_records': len(history)
}), 200
except Exception as e:
print(f"获取查询历史失败: {e}")
return jsonify({'error': '查询历史服务暂不可用'}), 503
except Exception as e:
print(f"获取查询历史时出错: {e}")
return jsonify({'error': '获取查询历史失败'}), 500
@app.route('/api/scheduler/status', methods=['GET'])
def get_scheduler_status():
"""获取定时任务状态API"""
try:
from scheduler_service import get_status
status = get_status()
return jsonify(status), 200
except Exception as e:
print(f"获取定时任务状态时出错: {e}")
return jsonify({'error': '定时任务服务暂不可用'}), 503
@app.route('/api/scheduler/manual_query', methods=['POST'])
def manual_query_api():
"""手动执行查询API"""
try:
data = request.get_json() or {}
target_email = data.get('email')
# 如果指定了邮箱,验证格式
if target_email:
target_email = target_email.strip().lower()
if not validate_wku_email(target_email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
from scheduler_service import manual_query
result = manual_query(target_email)
return jsonify({
'success': result,
'message': '手动查询已完成' if result else '手动查询失败',
'target_email': target_email
}), 200
except Exception as e:
print(f"手动查询时出错: {e}")
return jsonify({'error': '手动查询失败'}), 500
@app.route('/api/device_token', methods=['POST'])
def save_device_token():
"""保存用户设备推送token"""
try:
data = request.get_json()
if not data or 'email' not in data or 'device_token' not in data:
return jsonify({'error': '邮箱和设备token都是必需的'}), 400
email = data['email'].strip().lower()
device_token = data['device_token'].strip()
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
# 验证设备token格式(基本长度检查)
if len(device_token) < 64:
return jsonify({'error': '设备token格式不正确'}), 400
# 保存设备token
try:
from database import save_user_device_token
result = save_user_device_token(email, device_token)
if result:
return jsonify({
'success': True,
'message': '设备token保存成功',
'email': email
}), 200
else:
return jsonify({'error': '保存设备token失败'}), 500
except Exception as e:
print(f"保存设备token失败: {e}")
return jsonify({'error': '数据库操作失败'}), 500
except Exception as e:
print(f"保存设备token时出错: {e}")
return jsonify({'error': '保存设备token失败'}), 500
@app.route('/api/device_token/<email>', methods=['GET'])
def get_device_token(email):
"""获取用户设备token状态"""
try:
email = email.strip().lower()
# 验证邮箱格式
if not validate_wku_email(email):
return jsonify({'error': '邮箱必须是@wku.edu.cn后缀'}), 400
try:
from database import get_user_device_token
device_token = get_user_device_token(email)
return jsonify({
'email': email,
'has_device_token': device_token is not None,
'push_enabled': device_token is not None
}), 200
except Exception as e:
print(f"获取设备token失败: {e}")
return jsonify({'error': '数据库操作失败'}), 500
except Exception as e:
print(f"获取设备token时出错: {e}")
return jsonify({'error': '获取设备token失败'}), 500
@app.route('/api/health', methods=['GET'])
def health_check():
"""健康检查API"""
return jsonify({'status': 'ok', 'message': 'API服务器运行正常'}), 200
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': '接口不存在'}), 404
@app.errorhandler(405)
def method_not_allowed(error):
return jsonify({'error': '不支持的请求方法'}), 405
@app.errorhandler(500)
def internal_error(error):
return jsonify({'error': '服务器内部错误'}), 500
if __name__ == '__main__':
port = int(os.getenv('API_PORT', 5000))
debug = os.getenv('FLASK_ENV') == 'development'
print("=" * 60)
print(" CardQuery API 服务器")
print("=" * 60)
print("📧 邮箱验证API:")
print(" POST /api/send_code - 发送验证码")
print(" POST /api/verify_code - 验证验证码")
print(" POST /api/verify_code_with_room - 验证验证码并保存宿舍信息")
print("\n👥 用户管理API:")
print(" GET /api/user_info/<email> - 获取用户信息")
print(" GET /api/users_stats - 获取用户统计信息")
print(" GET /api/query_history/<email> - 获取查询历史")
print("\n📱 推送服务API:")
print(" POST /api/device_token - 保存设备推送token")
print(" GET /api/device_token/<email> - 获取设备token状态")
print("\n⏰ 定时任务API:")
print(" GET /api/scheduler/status - 获取调度器状态")
print(" POST /api/scheduler/manual_query - 手动执行查询")
print("\n💾 数据存储:")
print(f" 用户数据文件: {USER_DATA_FILE}")
print(f" 查询历史数据库: /home/cxy/log/cardquery_data.db")
print("=" * 60)
# SSL证书路径
cert_path = '/etc/myapp/certs/fullchain.pem'
key_path = '/etc/myapp/certs/privkey.pem'
if os.path.exists(cert_path) and os.path.exists(key_path):
print(f"🔒 启动HTTPS服务器: https://wkuquery.asia:{port}")
app.run(host='0.0.0.0', port=port, debug=debug,
ssl_context=(cert_path, key_path))
else:
print(f"⚠️ SSL证书未找到,启动HTTP服务器")
app.run(host='0.0.0.0', port=port, debug=debug)