auth.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. """
  2. 客户端认证接口 - 统一API格式
  3. """
  4. from fastapi import APIRouter, Depends, HTTPException, Request
  5. from fastapi.responses import JSONResponse
  6. from sqlalchemy.orm import Session
  7. from app.database import get_db
  8. from app.api.deps import get_current_user_session, get_current_user_session_for_heartbeat
  9. from app.models.user import User
  10. from app.models.card import Card
  11. from app.models.recharge_record import RechargeRecord
  12. from app.models.system_config import SystemConfig
  13. from app.core.security import verify_password, get_password_hash
  14. from app.core.encrypt import encryptor
  15. from app.core.logger import logger
  16. from app.config import settings
  17. from app.schemas.client_api import *
  18. from app.services.key_rotation_service import KeyRotationService
  19. from app.services.session_service import SessionService
  20. from app.services.heartbeat_monitor import HeartbeatMonitor
  21. from app.utils.timestamp_validator import TimestampValidator
  22. from app.utils.timezone import beijing_now
  23. from datetime import datetime, timedelta
  24. import json
  25. router = APIRouter()
  26. def process_client_request(request: Request, db: Session) -> tuple[ClientApiRequest, dict]:
  27. """
  28. 处理客户端请求:解密、验证时间戳
  29. Returns:
  30. tuple: (解析后的请求对象, 响应头)
  31. """
  32. try:
  33. # 获取请求数据
  34. data = request.json() if hasattr(request, 'json') else {}
  35. # 调试日志:打印原始请求数据
  36. logger.debug(f"[API请求] 原始请求数据: {json.dumps(data, ensure_ascii=False, indent=2)}")
  37. # 解密请求数据
  38. if settings.ENCRYPT_ENABLED:
  39. encrypted_req = EncryptedRequest(**data)
  40. decrypted_data = encryptor.decrypt_from_client(
  41. encrypted_req.encrypted,
  42. encrypted_req.key_version,
  43. db
  44. )
  45. logger.debug(f"[API请求] 解密后数据: {json.dumps(decrypted_data, ensure_ascii=False, indent=2)}")
  46. else:
  47. decrypted_data = data
  48. logger.debug(f"[API请求] 未加密数据: {json.dumps(decrypted_data, ensure_ascii=False, indent=2)}")
  49. # 解析为统一格式
  50. api_request = ClientApiRequest(**decrypted_data)
  51. logger.info(f"[API请求] 解析后的请求 - 操作: {api_request.action}, 时间戳: {api_request.timestamp}, 版本: {api_request.version}")
  52. # 验证时间戳
  53. is_valid, error_msg = TimestampValidator.validate_timestamp(api_request.timestamp, db)
  54. if not is_valid:
  55. logger.warning(f"[API请求] 时间戳验证失败: {error_msg}")
  56. raise HTTPException(status_code=400, detail=error_msg)
  57. # 准备响应头
  58. response_headers = {}
  59. if settings.ENCRYPT_ENABLED:
  60. active_version = KeyRotationService.get_active_key_version(db)
  61. response_headers["X-Key-Version"] = str(active_version)
  62. logger.debug(f"[API请求] 响应密钥版本: {active_version}")
  63. return api_request, response_headers
  64. except Exception as e:
  65. logger.error(f"[API请求] 处理客户端请求失败: {e}")
  66. raise HTTPException(status_code=400, detail=f"请求格式错误: {str(e)}")
  67. def create_client_response(data: dict = None, code: int = 200, msg: str = "成功", db: Session = None) -> dict:
  68. """
  69. 创建客户端响应:统一格式化并加密
  70. Returns:
  71. dict: 响应数据(已加密)
  72. """
  73. try:
  74. # 创建响应对象
  75. response_data = ClientApiResponse(
  76. code=code,
  77. msg=msg,
  78. data=data,
  79. timestamp=TimestampValidator.generate_timestamp(),
  80. version="1.0"
  81. )
  82. # 转换为字典
  83. response_dict = response_data.dict()
  84. # 调试日志:打印响应数据
  85. logger.info(f"[API响应] 响应数据 - 状态码: {code}, 消息: {msg}, 数据大小: {len(str(data)) if data else 0} 字符")
  86. logger.debug(f"[API响应] 完整响应数据: {json.dumps(response_dict, ensure_ascii=False, indent=2)}")
  87. # 加密响应
  88. if settings.ENCRYPT_ENABLED and db:
  89. encrypted_data, key_version = encryptor.encrypt_for_client(response_dict, db)
  90. encrypted_response = {
  91. "encrypted": encrypted_data,
  92. "key_version": key_version
  93. }
  94. logger.debug(f"[API响应] 加密响应数据: {json.dumps(encrypted_response, ensure_ascii=False, indent=2)}")
  95. return encrypted_response
  96. return response_dict
  97. except Exception as e:
  98. logger.error(f"[API响应] 创建客户端响应失败: {e}")
  99. # 返回未加密的错误响应
  100. error_response = {
  101. "code": 500,
  102. "msg": f"响应处理失败: {str(e)}",
  103. "timestamp": TimestampValidator.generate_timestamp(),
  104. "version": "1.0"
  105. }
  106. logger.debug(f"[API响应] 错误响应数据: {json.dumps(error_response, ensure_ascii=False, indent=2)}")
  107. return error_response
  108. @router.post("/register")
  109. async def register(
  110. request: Request,
  111. db: Session = Depends(get_db)
  112. ):
  113. """用户注册"""
  114. try:
  115. # 处理客户端请求
  116. api_request, response_headers = process_client_request(request, db)
  117. # 验证操作类型
  118. if api_request.action != ApiActions.REGISTER:
  119. return create_client_response(code=400, msg="操作类型不匹配", db=db)
  120. # 解析注册参数
  121. register_params = RegisterRequest(**api_request.params)
  122. # 检查用户名是否存在
  123. existing_user = db.query(User).filter(User.username == register_params.username).first()
  124. if existing_user:
  125. return create_client_response(code=400, msg="用户名已存在", db=db)
  126. # 验证卡密
  127. card = db.query(Card).filter(
  128. Card.card_code == register_params.card_code,
  129. Card.status == 0
  130. ).first()
  131. if not card:
  132. return create_client_response(code=400, msg="卡密无效或已被使用", db=db)
  133. # 计算到期时间
  134. expire_time = beijing_now() + timedelta(days=card.days)
  135. # 创建用户
  136. user = User(
  137. username=register_params.username,
  138. password=get_password_hash(register_params.password),
  139. email=register_params.email,
  140. machine_code=register_params.machine_code,
  141. card_id=card.id,
  142. expire_time=expire_time,
  143. status=1,
  144. created_at=beijing_now(),
  145. updated_at=beijing_now()
  146. )
  147. db.add(user)
  148. db.flush() # 刷新以获取user.id
  149. # 更新卡密状态
  150. card.user_id = user.id
  151. card.activated_at = beijing_now()
  152. card.expire_time = expire_time
  153. card.status = 1
  154. card.updated_at = beijing_now()
  155. # 创建充值记录
  156. recharge_record = RechargeRecord(
  157. user_id=user.id,
  158. card_id=card.id,
  159. software_code=card.software_code,
  160. days=card.days,
  161. expire_time_before=None, # 新用户,之前没有到期时间
  162. expire_time_after=expire_time,
  163. created_at=beijing_now()
  164. )
  165. db.add(recharge_record)
  166. # 创建历史记录
  167. from app.utils.history import create_user_history, create_card_history, create_recharge_record_history
  168. create_user_history(db, user, "INSERT")
  169. create_card_history(db, card, "UPDATE")
  170. create_recharge_record_history(db, recharge_record, "INSERT")
  171. # 一次性提交所有更改
  172. db.commit()
  173. # 准备响应数据
  174. response_data = RegisterResponseData(user_id=user.id)
  175. logger.info(f"用户注册成功: {register_params.username}")
  176. return create_client_response(data=response_data.dict(), db=db)
  177. except Exception as e:
  178. db.rollback()
  179. logger.error(f"用户注册失败: {e}")
  180. return create_client_response(code=500, msg="注册失败", db=db)
  181. @router.post("/login")
  182. async def login(
  183. request: Request,
  184. db: Session = Depends(get_db)
  185. ):
  186. """用户登录"""
  187. try:
  188. logger.info("[用户登录] 开始处理登录请求")
  189. # 处理客户端请求
  190. api_request, response_headers = process_client_request(request, db)
  191. # 验证操作类型
  192. if api_request.action != ApiActions.LOGIN:
  193. logger.warning(f"[用户登录] 操作类型不匹配: {api_request.action}")
  194. return create_client_response(code=400, msg="操作类型不匹配", db=db)
  195. # 解析登录参数
  196. login_params = LoginRequest(**api_request.params)
  197. logger.info(f"[用户登录] 用户名: {login_params.username}, 机器码: {login_params.machine_code}")
  198. # 获取系统配置
  199. config = KeyRotationService.get_config(db)
  200. # 查找用户
  201. user = db.query(User).filter(User.username == login_params.username).first()
  202. if not user:
  203. logger.warning(f"[用户登录] 用户不存在: {login_params.username}")
  204. return create_client_response(code=401, msg="用户名或密码错误", db=db)
  205. # 验证密码
  206. if not verify_password(login_params.password, user.password):
  207. logger.warning(f"[用户登录] 密码验证失败: {login_params.username}")
  208. return create_client_response(code=401, msg="用户名或密码错误", db=db)
  209. # 验证/绑定机器码
  210. if user.machine_code:
  211. if user.machine_code != login_params.machine_code:
  212. logger.warning(f"[用户登录] 机器码不匹配 - 用户: {user.machine_code}, 请求: {login_params.machine_code}")
  213. return create_client_response(code=403, msg="机器码不匹配,请先解绑", db=db)
  214. else:
  215. # 用户已解绑或尚未绑定机器码,第一次登录时绑定当前机器码
  216. user.machine_code = login_params.machine_code
  217. logger.info(f"[用户登录] 绑定新机器码: {login_params.machine_code}")
  218. # 检查是否过期
  219. if user.expire_time and user.expire_time < beijing_now():
  220. logger.warning(f"[用户登录] 用户卡密已过期: {user.expire_time}")
  221. return create_client_response(code=403, msg="卡密已过期", db=db)
  222. # 获取客户端IP和User-Agent
  223. client_ip = request.client.host if request.client else None
  224. user_agent = request.headers.get("User-Agent")
  225. # 创建用户会话
  226. logger.debug(f"[用户登录] 创建会话 - 用户ID: {user.id}, IP: {client_ip}")
  227. session_success, session_msg, session_id = SessionService.create_session(
  228. db=db,
  229. user_id=user.id,
  230. machine_code=login_params.machine_code,
  231. ip_address=client_ip,
  232. user_agent=user_agent
  233. )
  234. if not session_success:
  235. logger.error(f"[用户登录] 创建会话失败: {session_msg}")
  236. return create_client_response(code=500, msg=f"创建会话失败: {session_msg}", db=db)
  237. # 更新登录信息
  238. user.last_login_time = beijing_now()
  239. user.last_login_ip = client_ip
  240. user.updated_at = beijing_now()
  241. db.commit()
  242. # 准备响应数据
  243. response_data = LoginResponseData(
  244. user_id=user.id,
  245. username=user.username,
  246. session_id=session_id,
  247. expire_time=user.expire_time.isoformat() if user.expire_time else None,
  248. machine_code=user.machine_code
  249. )
  250. logger.info(f"[用户登录] 登录成功 - 用户: {login_params.username}, SessionID: {session_id[:8]}...")
  251. return create_client_response(data=response_data.dict(), db=db)
  252. except Exception as e:
  253. # 回滚数据库事务以避免半提交状态
  254. try:
  255. db.rollback()
  256. except Exception:
  257. # 回滚失败不影响主流程,继续记录异常
  258. logger.warning("回滚数据库事务时发生异常")
  259. # 记录完整堆栈以便排查
  260. logger.exception("用户登录失败")
  261. # 返回给客户端一个友好的错误提示(不暴露内部细节)
  262. return create_client_response(code=500, msg="系统异常,登录失败,请稍后重试;若持续发生请联系客服", db=db)
  263. @router.post("/unbind")
  264. async def unbind(
  265. request: Request,
  266. db: Session = Depends(get_db)
  267. ):
  268. """机器解绑"""
  269. try:
  270. # 获取请求数据
  271. data = await request.json()
  272. # 解密请求数据
  273. if settings.ENCRYPT_ENABLED:
  274. encrypted_data = data.get("encrypted", "")
  275. if not encrypted_data:
  276. return {"code": 400, "msg": "缺少加密数据"}
  277. decrypted = encryptor.decrypt(encrypted_data)
  278. else:
  279. decrypted = data
  280. req = UnbindRequest(**decrypted)
  281. # 查找用户
  282. user = db.query(User).filter(User.username == req.username).first()
  283. if not user:
  284. return {"code": 401, "msg": "用户名或密码错误"}
  285. # 验证密码
  286. if not verify_password(req.password, user.password):
  287. return {"code": 401, "msg": "用户名或密码错误"}
  288. # 解绑机器码
  289. user.machine_code = None
  290. user.updated_at = beijing_now()
  291. db.commit()
  292. logger.info(f"机器解绑成功: {req.username}")
  293. return {"code": 200, "msg": "解绑成功"}
  294. except Exception as e:
  295. try:
  296. db.rollback()
  297. except Exception:
  298. logger.warning("回滚数据库事务时发生异常")
  299. logger.exception("机器解绑失败")
  300. return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
  301. @router.post("/heartbeat")
  302. async def heartbeat(
  303. user_session: tuple[User, Session] = Depends(get_current_user_session_for_heartbeat),
  304. db: Session = Depends(get_db)
  305. ):
  306. """心跳检测"""
  307. try:
  308. user, session = user_session
  309. # 记录心跳日志
  310. logger.info(f"[心跳检测] 用户: {user.username}(ID:{user.id}), Session: {session.session_id[:8]}...")
  311. # 记录心跳成功
  312. HeartbeatMonitor.record_heartbeat_success(db, user.id, session.session_id)
  313. # 检查用户是否过期
  314. if user.expire_time and user.expire_time < beijing_now():
  315. logger.warning(f"用户 {user.username} 卡密已过期")
  316. return create_client_response(code=403, msg="卡密已过期", db=db)
  317. # 检查用户状态
  318. if user.status != 1:
  319. logger.warning(f"用户 {user.username} 状态异常: {user.status}")
  320. return create_client_response(code=403, msg="账号状态异常", db=db)
  321. # 更新会话活动时间(已在依赖注入中自动更新)
  322. # SessionService.validate_session 中已经调用了 update_activity
  323. # 获取系统配置
  324. from app.services.session_service import SessionService
  325. config = SessionService.get_session_config(db)
  326. # 返回心跳响应,包含更多状态信息
  327. response_data = HeartbeatResponseData(
  328. expire_time=user.expire_time.isoformat() if user.expire_time else None
  329. )
  330. # 获取session延长配置
  331. session_config = SessionService.get_session_config(db)
  332. # 获取客户端配置(通过心跳实时更新)
  333. from app.models.system_config import SystemConfig
  334. client_configs = {
  335. "api_timeout_seconds": SystemConfig.get_config_value('api_timeout_seconds', 30),
  336. "client_heartbeat_interval_seconds": SystemConfig.get_config_value('client_heartbeat_interval_seconds', 30),
  337. "max_heartbeat_failures": SystemConfig.get_config_value('max_heartbeat_failures', 10),
  338. "max_network_failures": SystemConfig.get_config_value('max_network_failures', 15),
  339. "sweep_batch_size": SystemConfig.get_config_value('sweep_batch_size', 10),
  340. "sweep_purchase_interval": SystemConfig.get_config_value('sweep_purchase_interval', 30),
  341. "sweep_max_purchases": SystemConfig.get_config_value('sweep_max_purchases', 100),
  342. "page_size": SystemConfig.get_config_value('page_size', 10),
  343. "steam_auth_countdown_seconds": SystemConfig.get_config_value('steam_auth_countdown_seconds', 30),
  344. }
  345. # 获取当前激活的密钥信息
  346. from app.models.api_key import APIKey
  347. current_key = db.query(APIKey).filter(APIKey.is_active == 1).first()
  348. key_info = None
  349. if current_key:
  350. key_info = {
  351. "key_version": current_key.key_version,
  352. "key_updated_at": current_key.updated_at.isoformat() if current_key.updated_at else None,
  353. }
  354. # 添加扩展响应数据
  355. extended_data = response_data.dict()
  356. extended_data.update({
  357. "server_time": beijing_now().isoformat(),
  358. "session_status": "active",
  359. "next_heartbeat": client_configs.get("client_heartbeat_interval_seconds", 30),
  360. "session_expires_at": session.expires_at.isoformat(),
  361. "session_extend_enabled": session_config.get("session_extend_enabled", True),
  362. "session_extend_threshold_hours": session_config.get("session_extend_threshold_hours", 4),
  363. # 实时配置更新
  364. "client_configs": client_configs,
  365. # 密钥版本信息(用于客户端密钥同步)
  366. "key_info": key_info
  367. })
  368. logger.debug(f"用户 {user.username} 心跳检测成功")
  369. return create_client_response(data=extended_data, db=db)
  370. except Exception as e:
  371. logger.error(f"心跳检测失败: {e}")
  372. return create_client_response(code=500, msg="心跳检测失败", db=db)
  373. @router.post("/logout")
  374. async def logout(
  375. request: Request,
  376. db: Session = Depends(get_db)
  377. ):
  378. """用户登出"""
  379. try:
  380. logger.info("[用户登出] 开始处理登出请求")
  381. # 处理客户端请求
  382. api_request, response_headers = process_client_request(request, db)
  383. # 验证操作类型
  384. if api_request.action != ApiActions.LOGOUT:
  385. logger.warning(f"[用户登出] 操作类型不匹配: {api_request.action}")
  386. return create_client_response(code=400, msg="操作类型不匹配", db=db)
  387. # 解析登出参数
  388. logout_params = LogoutRequest(**api_request.params)
  389. logger.info(f"[用户登出] 用户ID: {logout_params.user_id}, Session: {logout_params.session_id[:8]}...")
  390. # 验证会话(确保是当前用户的有效会话)
  391. is_valid, session, error_msg = SessionService.validate_session(db, logout_params.session_id)
  392. if not is_valid:
  393. logger.warning(f"[用户登出] 会话验证失败: {error_msg}")
  394. # 即使会话验证失败,也允许登出(可能是会话已过期但用户主动登出)
  395. logger.info("[用户登出] 会话验证失败但仍允许登出")
  396. # 停用会话
  397. if session:
  398. SessionService.deactivate_session(db, logout_params.session_id)
  399. logger.info(f"[用户登出] 用户 {logout_params.user_id} 的会话已停用")
  400. # 记录心跳监控:清除用户的缓存状态
  401. HeartbeatMonitor.reset_user_heartbeat(logout_params.user_id, logout_params.session_id)
  402. # 记录登出日志(可以后续在API日志中体现)
  403. logger.info(f"[用户登出] 用户 {logout_params.user_id} 成功登出")
  404. return create_client_response(msg="登出成功", db=db)
  405. except Exception as e:
  406. logger.error(f"[用户登出] 登出失败: {e}")
  407. return create_client_response(code=500, msg="登出失败", db=db)
  408. @router.post("/validate-session")
  409. async def validate_session(
  410. request: Request,
  411. db: Session = Depends(get_db)
  412. ):
  413. """验证用户会话(用于自动登录验证)"""
  414. try:
  415. logger.info("[会话验证] 开始验证用户会话")
  416. # 处理客户端请求
  417. api_request, response_headers = process_client_request(request, db)
  418. # 验证操作类型
  419. if api_request.action != ApiActions.VALIDATE_SESSION:
  420. logger.warning(f"[会话验证] 操作类型不匹配: {api_request.action}")
  421. return create_client_response(code=400, msg="操作类型不匹配", db=db)
  422. # 解析验证参数
  423. validate_params = ValidateSessionRequest(**api_request.params)
  424. logger.info(f"[会话验证] 验证会话ID: {validate_params.session_id[:8]}...")
  425. # 验证会话
  426. is_valid, session, error_msg = SessionService.validate_session(db, validate_params.session_id)
  427. if not is_valid:
  428. logger.warning(f"[会话验证] 会话验证失败: {error_msg}")
  429. return create_client_response(code=401, msg=f"会话验证失败: {error_msg}", db=db)
  430. # 获取用户信息
  431. user = db.query(User).filter(User.id == session.user_id).first()
  432. if not user:
  433. logger.warning("[会话验证] 用户不存在")
  434. return create_client_response(code=401, msg="用户不存在", db=db)
  435. # 检查用户是否过期
  436. if user.expire_time and user.expire_time < beijing_now():
  437. logger.warning(f"[会话验证] 用户 {user.username} 卡密已过期")
  438. return create_client_response(code=403, msg="卡密已过期", db=db)
  439. # 检查用户状态
  440. if user.status != 1:
  441. logger.warning(f"[会话验证] 用户 {user.username} 状态异常: {user.status}")
  442. return create_client_response(code=403, msg="账号状态异常", db=db)
  443. # 更新会话活动时间
  444. SessionService.update_session_activity(db, session.session_id)
  445. # 准备响应数据
  446. response_data = ValidateSessionResponseData(
  447. user_id=user.id,
  448. username=user.username,
  449. expire_time=user.expire_time.isoformat() if user.expire_time else None,
  450. is_active=user.status == 1,
  451. machine_code=user.machine_code or ""
  452. )
  453. logger.info(f"[会话验证] 用户 {user.username} 会话验证成功")
  454. return create_client_response(data=response_data.dict(), db=db)
  455. except Exception as e:
  456. logger.error(f"[会话验证] 验证失败: {e}")
  457. return create_client_response(code=500, msg="会话验证失败", db=db)
  458. @router.post("/activate-card")
  459. async def activate_card(
  460. user: User = Depends(get_current_user_session),
  461. request: Request = None,
  462. db: Session = Depends(get_db)
  463. ):
  464. """激活卡密(充值)"""
  465. try:
  466. logger.info(f"[卡密激活] 开始处理激活请求 - 用户: {user.username}(ID:{user.id})")
  467. # 处理客户端请求
  468. api_request, response_headers = process_client_request(request, db)
  469. # 验证操作类型
  470. if api_request.action != ApiActions.ACTIVATE_CARD:
  471. logger.warning(f"[卡密激活] 操作类型不匹配: {api_request.action}")
  472. return create_client_response(code=400, msg="操作类型不匹配", db=db)
  473. # 解析激活参数
  474. activate_params = ActivateCardRequest(**api_request.params)
  475. logger.info(f"[卡密激活] 激活参数 - 用户ID: {activate_params.user_id}, 卡密: {activate_params.card_code[:8]}...")
  476. # 验证用户ID是否匹配当前登录用户
  477. if activate_params.user_id != user.id:
  478. return create_client_response(code=403, msg="无权操作其他用户", db=db)
  479. # 验证卡密
  480. card = db.query(Card).filter(
  481. Card.card_code == activate_params.card_code,
  482. Card.status == 0
  483. ).first()
  484. if not card:
  485. return create_client_response(code=400, msg="卡密无效或已被使用", db=db)
  486. # 检查软件特征码是否匹配
  487. software_code = api_request.params.get("software_code", "")
  488. if card.software_code != software_code:
  489. return create_client_response(code=400, msg="卡密与当前软件不匹配", db=db)
  490. # 计算新的到期时间
  491. expire_time_before = user.expire_time
  492. if expire_time_before and expire_time_before > beijing_now():
  493. # 如果还有有效期,则累加
  494. expire_time_after = expire_time_before + timedelta(days=card.days)
  495. else:
  496. # 如果已过期或没有有效期,则从当前时间开始计算
  497. expire_time_after = beijing_now() + timedelta(days=card.days)
  498. # 更新用户到期时间
  499. user.expire_time = expire_time_after
  500. user.updated_at = beijing_now()
  501. # 更新卡密状态
  502. card.user_id = user.id
  503. card.activated_at = beijing_now()
  504. card.expire_time = expire_time_after
  505. card.status = 1
  506. card.updated_at = beijing_now()
  507. # 创建充值记录
  508. recharge_record = RechargeRecord(
  509. user_id=user.id,
  510. card_id=card.id,
  511. software_code=card.software_code,
  512. days=card.days,
  513. expire_time_before=expire_time_before,
  514. expire_time_after=expire_time_after,
  515. created_at=beijing_now()
  516. )
  517. db.add(recharge_record)
  518. # 创建历史记录
  519. from app.utils.history import create_user_history, create_card_history, create_recharge_record_history
  520. create_user_history(db, user, "UPDATE")
  521. create_card_history(db, card, "UPDATE")
  522. create_recharge_record_history(db, recharge_record, "INSERT")
  523. # 一次性提交所有更改
  524. db.commit()
  525. # 准备响应数据
  526. response_data = ActivateCardResponseData(
  527. expire_time=expire_time_after.isoformat(),
  528. days=card.days
  529. )
  530. logger.info(f"用户 {user.username} 激活卡密成功: {activate_params.card_code}")
  531. return create_client_response(data=response_data.dict(), db=db)
  532. except Exception as e:
  533. db.rollback()
  534. logger.error(f"激活卡密失败: {e}")
  535. return create_client_response(code=500, msg="激活失败", db=db)
  536. @router.get("/recharge-records")
  537. async def get_recharge_records(
  538. request: Request,
  539. db: Session = Depends(get_db)
  540. ):
  541. """获取充值记录"""
  542. try:
  543. # 获取请求参数
  544. params = dict(request.query_params)
  545. # 解密请求数据(如果有)
  546. user_id = None
  547. if settings.ENCRYPT_ENABLED:
  548. # GET请求可能通过query参数传递加密数据
  549. encrypted_data = params.get("encrypted", "")
  550. if encrypted_data:
  551. decrypted = encryptor.decrypt(encrypted_data)
  552. user_id = decrypted.get("user_id")
  553. else:
  554. user_id = params.get("user_id")
  555. if user_id:
  556. user_id = int(user_id)
  557. if not user_id:
  558. return {"code": 400, "msg": "缺少用户ID"}
  559. # 查询充值记录
  560. records = db.query(RechargeRecord).filter(
  561. RechargeRecord.user_id == user_id,
  562. RechargeRecord.is_deleted == 0
  563. ).order_by(RechargeRecord.created_at.desc()).all()
  564. # 格式化返回数据
  565. records_data = []
  566. for record in records:
  567. # 获取卡密代码
  568. card_code = ""
  569. if record.card_id:
  570. card = db.query(Card).filter(Card.id == record.card_id).first()
  571. if card:
  572. card_code = card.card_code
  573. records_data.append({
  574. "id": record.id,
  575. "card_code": card_code,
  576. "days": record.days,
  577. "expire_time_before": record.expire_time_before.isoformat() if record.expire_time_before else None,
  578. "expire_time_after": record.expire_time_after.isoformat() if record.expire_time_after else None,
  579. "created_at": record.created_at.isoformat() if record.created_at else None
  580. })
  581. return {
  582. "code": 200,
  583. "msg": "成功",
  584. "data": records_data
  585. }
  586. except Exception as e:
  587. logger.exception("获取充值记录失败")
  588. return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}