blob: 0b606244f10c9f83cf2cd8578a8498b2347e6b80 [file] [log] [blame]
Raverafc93da2025-06-15 18:12:49 +08001from flask import Blueprint, request, jsonify
2from .functions.FAuth import FAuth
Raver9d0ecf52025-06-25 17:35:39 +08003from app.services.recommendation_service import RecommendationService
4from app.utils.graph_build import build_user_post_graph
TRM-coding106204b2025-06-28 00:37:46 +08005from app.utils.gpuwriter import GpuWriter
Raverafc93da2025-06-15 18:12:49 +08006from sqlalchemy import create_engine
7from sqlalchemy.orm import sessionmaker
8from config import Config
9from functools import wraps
10from datetime import datetime
11
12main = Blueprint('main', __name__)
13
TRM-coding3127efa2025-06-18 22:54:25 +080014# 初始化推荐服务
15recommendation_service = RecommendationService()
16
Raverafc93da2025-06-15 18:12:49 +080017def token_required(f):
18 """装饰器:需要令牌验证"""
19 @wraps(f)
20 def decorated(*args, **kwargs):
21 token = request.headers.get('Authorization')
Raver9d0ecf52025-06-25 17:35:39 +080022 print(f"收到的Authorization头: {token}")
23
Raverafc93da2025-06-15 18:12:49 +080024 if not token:
Raver9d0ecf52025-06-25 17:35:39 +080025 print("缺少Authorization头")
Raverafc93da2025-06-15 18:12:49 +080026 return jsonify({'success': False, 'message': '缺少访问令牌'}), 401
27
28 session = None
29 try:
30 # 移除Bearer前缀
31 if token.startswith('Bearer '):
32 token = token[7:]
Raver9d0ecf52025-06-25 17:35:39 +080033 print(f"提取的token: {token[:20]}...")
Raverafc93da2025-06-15 18:12:49 +080034
35 engine = create_engine(Config.SQLURL)
36 SessionLocal = sessionmaker(bind=engine)
37 session = SessionLocal()
38 f_auth = FAuth(session)
39
Raver9d0ecf52025-06-25 17:35:39 +080040 # 验证token
41 token_data = f_auth.verify_token(token)
42 print(f"Token验证结果: {token_data}")
43
44 if not token_data:
45 print("Token验证失败 - token无效")
46 return jsonify({'success': False, 'message': '无效的访问令牌'}), 401
47
Raverafc93da2025-06-15 18:12:49 +080048 user = f_auth.get_user_by_token(token)
49 if not user:
Raver9d0ecf52025-06-25 17:35:39 +080050 print("根据token获取用户失败")
Raverafc93da2025-06-15 18:12:49 +080051 return jsonify({'success': False, 'message': '无效的访问令牌'}), 401
52
Raver9d0ecf52025-06-25 17:35:39 +080053 print(f"Token验证成功,用户: {user.username}, ID: {user.id}")
54
Raverafc93da2025-06-15 18:12:49 +080055 # 将用户信息传递给路由函数
56 return f(user, *args, **kwargs)
57 except Exception as e:
Raver9d0ecf52025-06-25 17:35:39 +080058 print(f"Token验证异常: {str(e)}")
Raverafc93da2025-06-15 18:12:49 +080059 if session:
60 session.rollback()
Raver9d0ecf52025-06-25 17:35:39 +080061 return jsonify({'success': False, 'message': f'令牌验证失败: {str(e)}'}), 401
Raverafc93da2025-06-15 18:12:49 +080062 finally:
63 if session:
64 session.close()
65
66 return decorated
67
68@main.route('/login', methods=['POST'])
69def login():
70 """用户登录接口"""
71 session = None
72 try:
73 data = request.get_json()
74
75 # 验证必填字段
76 if not data or not data.get('email') or not data.get('password'):
77 return jsonify({
78 'success': False,
79 'message': '用户名和密码不能为空'
80 }), 400
81
82 email = data['email']
83 password = data['password']
84
85 # 创建数据库连接
86 engine = create_engine(Config.SQLURL)
87 SessionLocal = sessionmaker(bind=engine)
88 session = SessionLocal()
89
90 # 执行登录
91 f_auth = FAuth(session)
92 result = f_auth.login(email, password)
93
94 if result['success']:
95 session.commit()
96 return jsonify(result), 200
97 else:
98 return jsonify(result), 401
99
100 except Exception as e:
101 if session:
102 session.rollback()
103 return jsonify({
104 'success': False,
105 'message': '服务器内部错误'
106 }), 500
107 finally:
108 if session:
109 session.close()
110
111@main.route('/register', methods=['POST'])
112def register():
113 """用户注册接口"""
114
115 engine = create_engine(Config.SQLURL)
116 SessionLocal = sessionmaker(bind=engine)
117 session = SessionLocal()
118
119 try:
120 data = request.get_json()
121
122 # 验证必填字段
123 if not data or not data.get('username') or not data.get('email') or not data.get('password') or not data.get('verification_code'):
124 return jsonify({
125 'success': False,
126 'message': '用户名、邮箱和密码不能为空'
127 }), 400
128
129 username = data['username']
130 email = data['email']
131 password = data['password']
132 verification_code = data['verification_code']
133
134 # 简单的邮箱格式验证
135 if '@' not in email or '.' not in email:
136 return jsonify({
137 'success': False,
138 'message': '邮箱格式不正确'
139 }), 400
140
141 # 密码长度验证
142 if len(password) < 6:
143 return jsonify({
144 'success': False,
145 'message': '密码长度不能少于6位'
146 }), 400
147
148 # 执行注册
149 f_auth = FAuth(session)
150 result = f_auth.register(username, email, password, verification_code)
151
152 if result['success']:
153 session.commit()
154 return jsonify(result), 201
155 else:
156 return jsonify(result), 400
157
158 except Exception as e:
159 session.rollback()
160 return jsonify({
161 'success': False,
162 'message': '服务器内部错误'
163 }), 500
164 finally:
165 session.close()
166
167@main.route('/profile', methods=['GET'])
168@token_required
169def get_profile(current_user):
170 """获取用户信息接口(需要登录)"""
171 try:
172 return jsonify({
173 'success': True,
174 'user': current_user.to_dict()
175 }), 200
176 except Exception as e:
177 return jsonify({
178 'success': False,
179 'message': '获取用户信息失败'
180 }), 500
181
182@main.route('/logout', methods=['POST'])
183@token_required
184def logout(current_user):
185 """用户登出接口(需要登录)"""
186 try:
187 # 这里可以将令牌加入黑名单(如果需要的话)
188 return jsonify({
189 'success': True,
190 'message': '登出成功'
191 }), 200
192 except Exception as e:
193 return jsonify({
194 'success': False,
195 'message': '登出失败'
196 }), 500
197
198@main.route('/send-verification-code', methods=['POST'])
199def send_verification_code():
200 """发送邮箱验证码接口"""
201
202 engine = create_engine(Config.SQLURL)
203 SessionLocal = sessionmaker(bind=engine)
204 session = SessionLocal()
205
206 try:
207 data = request.get_json()
208
209 # 验证必填字段
210 if not data or not data.get('email'):
211 return jsonify({
212 'success': False,
213 'message': '邮箱地址不能为空'
214 }), 400
215
216 email = data['email']
217 verification_type = data.get('type', 'register') # 默认为注册验证码
218
219 # 简单的邮箱格式验证
220 if '@' not in email or '.' not in email:
221 return jsonify({
222 'success': False,
223 'message': '邮箱格式不正确'
224 }), 400
225
226 # 发送验证码
227 f_auth = FAuth(session)
228 result = f_auth.send_verification_email(email, verification_type)
229
230 if result['success']:
231 session.commit()
232 return jsonify(result), 200
233 else:
234 return jsonify(result), 400
235
236 except Exception as e:
237 session.rollback()
238 return jsonify({
239 'success': False,
240 'message': '服务器内部错误'
241 }), 500
242 finally:
243 session.close()
244
245@main.route('/reset-password', methods=['POST'])
246def reset_password():
247 """重置密码接口"""
248
249 engine = create_engine(Config.SQLURL)
250 SessionLocal = sessionmaker(bind=engine)
251 session = SessionLocal()
252
253 try:
254 data = request.get_json()
255
256 # 验证必填字段
257 if not data or not data.get('email') or not data.get('new_password') or not data.get('verification_code'):
258 return jsonify({
259 'success': False,
260 'message': '邮箱地址、新密码和验证码不能为空'
261 }), 400
262
263 email = data['email']
264 new_password = data['new_password']
265 verification_code = data['verification_code']
266
267 # 简单的邮箱格式验证
268 if '@' not in email or '.' not in email:
269 return jsonify({
270 'success': False,
271 'message': '邮箱格式不正确'
272 }), 400
273
274 # 密码长度验证
275 if len(new_password) < 6:
276 return jsonify({
277 'success': False,
278 'message': '密码长度不能少于6位'
279 }), 400
280
281 # 重置密码
282 f_auth = FAuth(session)
283 result = f_auth.reset_password_with_verification(email, new_password, verification_code)
284
285 if result['success']:
286 session.commit()
287 return jsonify(result), 200
288 else:
289 return jsonify(result), 400
290
291 except Exception as e:
292 session.rollback()
293 return jsonify({
294 'success': False,
295 'message': '服务器内部错误'
296 }), 500
297 finally:
298 session.close()
299
300@main.route('/test-jwt', methods=['POST'])
301@token_required
302def test_jwt(current_user):
303 """测试JWT令牌接口(需要登录)"""
304 try:
305 # 获取当前请求的token(从装饰器已验证的Authorization header)
306 auth_header = request.headers.get('Authorization')
307 current_token = auth_header[7:] if auth_header and auth_header.startswith('Bearer ') else None
308
309 print(f"当前用户: {current_user.username}")
310 print(f"当前用户ID: {current_user.id}")
311 print(current_user.role)
312 print(f"Token验证成功: {current_token[:20]}..." if current_token else "No token")
313
314 # 可选:检查请求体中是否有额外的token需要验证
315 data = request.get_json() or {}
316 additional_token = data.get('token')
317
318 response_data = {
319 'success': True,
320 'message': 'JWT令牌验证成功',
321 'user': current_user.to_dict(),
322 'token_info': {
323 'header_token_verified': True,
324 'token_preview': current_token[:20] + "..." if current_token else None
325 }
326 }
327
328 # 如果请求体中有额外的token,也验证一下
329 if additional_token:
330 try:
331 additional_result = FAuth.verify_token(additional_token)
332 response_data['additional_token_verification'] = additional_result
333 print(f"额外token验证结果: {additional_result}")
334 except Exception as e:
335 response_data['additional_token_verification'] = {
336 'success': False,
337 'message': f'额外token验证失败: {str(e)}'
338 }
339
340 return jsonify(response_data), 200
341
342 except Exception as e:
343 print(f"test_jwt 错误: {str(e)}")
344 return jsonify({
345 'success': False,
346 'message': f'JWT令牌验证失败: {str(e)}'
TRM-coding3127efa2025-06-18 22:54:25 +0800347 }), 500
348
349@main.route('/verify_user', methods=['POST'])
350@token_required
351def verify_user(current_user):
352 """测试JWT令牌接口(需要登录)"""
353 try:
354 # 获取当前请求的token(从装饰器已验证的Authorization header)
355 auth_header = request.headers.get('Authorization')
356 current_token = auth_header[7:] if auth_header and auth_header.startswith('Bearer ') else None
357
358 print(f"当前用户: {current_user.username}")
359 print(f"当前用户ID: {current_user.id}")
360 print(current_user.role)
361 print(f"Token验证成功: {current_token[:20]}..." if current_token else "No token")
362
363 # 可选:检查请求体中是否有额外的token需要验证
364 data = request.get_json() or {}
365 additional_token = data.get('token')
366
367 response_data = {
368 'success': True,
369 'userid': current_user.id,
370 'role': current_user.role,
371 }
372
373 return jsonify(response_data), 200
374
375 except Exception as e:
376 print(f"用户验证错误: {str(e)}")
377 return jsonify({
378 'success': False,
379 'message': f'JWT令牌验证失败: {str(e)}'
380 }), 500
381
382@main.route('/recommend', methods=['POST'])
383@token_required
384def get_recommendations(current_user):
385 """获取个性化推荐接口"""
386 try:
387 data = request.get_json() or {}
388 user_id = data.get('user_id') or current_user.id
TRM-coding3127efa2025-06-18 22:54:25 +0800389
Raver9d0ecf52025-06-25 17:35:39 +0800390 print(f"为用户 {user_id} 获取个性化推荐")
TRM-coding3127efa2025-06-18 22:54:25 +0800391
392 # 调用推荐系统
Raver9d0ecf52025-06-25 17:35:39 +0800393 user2idx, post2idx = build_user_post_graph(return_mapping=True)
394 recommendations = recommendation_service.get_recommendations(float(user_id), topk=10)
TRM-coding3127efa2025-06-18 22:54:25 +0800395
396 return jsonify({
397 'success': True,
398 'data': {
399 'user_id': user_id,
400 'recommendations': recommendations,
401 'count': len(recommendations),
402 'type': 'personalized'
403 },
404 'message': '个性化推荐获取成功'
405 }), 200
406
407 except Exception as e:
408 print(f"推荐系统错误: {str(e)}")
409 return jsonify({
410 'success': False,
411 'message': f'推荐获取失败: {str(e)}'
412 }), 500
TRM-coding106204b2025-06-28 00:37:46 +0800413
414@main.route('/gpu-usage', methods=['GET'])
415@token_required
416def get_gpu_usage(current_user):
417 """获取最新GPU使用情况接口"""
418 try:
419 limit = request.args.get('limit', 100, type=int)
420
421 # 限制最大获取条数
422 if limit > 1000:
423 limit = 1000
424
425 print(f"获取最新 {limit} 条GPU使用记录")
426
427 # 创建GpuWriter实例并获取数据
428 gpu_writer = GpuWriter(Config.SQLURL)
429 gpu_records = gpu_writer.get_latest_gpu_usage(limit)
430
431 return jsonify({
432 'success': True,
433 'data': {
434 'records': gpu_records,
435 'count': len(gpu_records),
436 'limit': limit
437 },
438 'message': 'GPU使用情况获取成功'
439 }), 200
440
441 except Exception as e:
442 print(f"获取GPU使用情况错误: {str(e)}")
443 return jsonify({
444 'success': False,
445 'message': f'获取GPU使用情况失败: {str(e)}'
446 }), 500