auth.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. """
  2. 客户端认证接口
  3. """
  4. from fastapi import APIRouter, Depends, HTTPException, Request
  5. from sqlalchemy.orm import Session
  6. from pydantic import BaseModel
  7. from app.database import get_db
  8. from app.models.user import User
  9. from app.models.card import Card
  10. from app.models.recharge_record import RechargeRecord
  11. from app.core.security import verify_password, get_password_hash
  12. from app.core.encrypt import encryptor
  13. from app.core.logger import logger
  14. from app.config import settings
  15. from datetime import datetime, timedelta
  16. router = APIRouter()
  17. class RegisterRequest(BaseModel):
  18. """注册请求"""
  19. username: str
  20. password: str
  21. email: str
  22. card_code: str
  23. machine_code: str
  24. class LoginRequest(BaseModel):
  25. """登录请求"""
  26. username: str
  27. password: str
  28. machine_code: str
  29. class UnbindRequest(BaseModel):
  30. """解绑请求"""
  31. username: str
  32. password: str
  33. class ActivateCardRequest(BaseModel):
  34. """激活卡密请求"""
  35. user_id: int
  36. card_code: str
  37. @router.post("/register")
  38. async def register(
  39. request: Request,
  40. db: Session = Depends(get_db)
  41. ):
  42. """用户注册"""
  43. try:
  44. # 获取请求数据
  45. data = await request.json()
  46. # 解密请求数据
  47. if settings.ENCRYPT_ENABLED:
  48. encrypted_data = data.get("encrypted", "")
  49. if not encrypted_data:
  50. return {"code": 400, "msg": "缺少加密数据"}
  51. decrypted = encryptor.decrypt(encrypted_data)
  52. else:
  53. decrypted = data
  54. req = RegisterRequest(**decrypted)
  55. # 检查用户名是否存在
  56. existing_user = db.query(User).filter(User.username == req.username).first()
  57. if existing_user:
  58. return {"code": 400, "msg": "用户名已存在"}
  59. # 验证卡密
  60. card = db.query(Card).filter(
  61. Card.card_code == req.card_code,
  62. Card.status == 0
  63. ).first()
  64. if not card:
  65. return {"code": 400, "msg": "卡密无效或已被使用"}
  66. # 计算到期时间
  67. expire_time = datetime.now() + timedelta(days=card.days)
  68. # 创建用户
  69. user = User(
  70. username=req.username,
  71. password=get_password_hash(req.password),
  72. email=req.email,
  73. machine_code=req.machine_code,
  74. card_id=card.id,
  75. expire_time=expire_time,
  76. status=1,
  77. created_at=datetime.now(),
  78. updated_at=datetime.now()
  79. )
  80. db.add(user)
  81. db.flush() # 刷新以获取user.id
  82. # 更新卡密状态
  83. card.user_id = user.id
  84. card.activated_at = datetime.now()
  85. card.expire_time = expire_time
  86. card.status = 1
  87. card.updated_at = datetime.now()
  88. # 创建充值记录
  89. recharge_record = RechargeRecord(
  90. user_id=user.id,
  91. card_id=card.id,
  92. software_code=card.software_code,
  93. days=card.days,
  94. expire_time_before=None, # 新用户,之前没有到期时间
  95. expire_time_after=expire_time,
  96. created_at=datetime.now()
  97. )
  98. db.add(recharge_record)
  99. db.commit()
  100. logger.info(f"用户注册成功: {req.username}")
  101. return {"code": 200, "msg": "注册成功", "data": {"user_id": user.id}}
  102. except Exception as e:
  103. db.rollback()
  104. logger.error(f"用户注册失败: {e}")
  105. return {"code": 500, "msg": "注册失败"}
  106. @router.post("/login")
  107. async def login(
  108. request: Request,
  109. db: Session = Depends(get_db)
  110. ):
  111. """用户登录"""
  112. try:
  113. # 获取请求数据
  114. data = await request.json()
  115. # 解密请求数据
  116. if settings.ENCRYPT_ENABLED:
  117. encrypted_data = data.get("encrypted", "")
  118. if not encrypted_data:
  119. return {"code": 400, "msg": "缺少加密数据"}
  120. decrypted = encryptor.decrypt(encrypted_data)
  121. else:
  122. decrypted = data
  123. req = LoginRequest(**decrypted)
  124. # 查找用户
  125. user = db.query(User).filter(User.username == req.username).first()
  126. if not user:
  127. return {"code": 401, "msg": "用户名或密码错误"}
  128. # 验证密码
  129. if not verify_password(req.password, user.password):
  130. return {"code": 401, "msg": "用户名或密码错误"}
  131. # 验证/绑定机器码
  132. if user.machine_code:
  133. if user.machine_code != req.machine_code:
  134. return {"code": 403, "msg": "机器码不匹配,请先解绑"}
  135. else:
  136. # 用户已解绑或尚未绑定机器码,第一次登录时绑定当前机器码
  137. user.machine_code = req.machine_code
  138. # 检查是否过期
  139. if user.expire_time and user.expire_time < datetime.now():
  140. return {"code": 403, "msg": "卡密已过期"}
  141. # 更新登录信息
  142. client_ip = request.client.host if request.client else None
  143. user.last_login_time = datetime.now()
  144. user.last_login_ip = client_ip
  145. user.updated_at = datetime.now()
  146. db.commit()
  147. logger.info(f"用户登录成功: {req.username}")
  148. return {
  149. "code": 200,
  150. "msg": "登录成功",
  151. "data": {
  152. "user_id": user.id,
  153. "username": user.username,
  154. "expire_time": user.expire_time.isoformat() if user.expire_time else None
  155. }
  156. }
  157. except Exception as e:
  158. logger.error(f"用户登录失败: {e}")
  159. return {"code": 500, "msg": "登录失败"}
  160. @router.post("/unbind")
  161. async def unbind(
  162. request: Request,
  163. db: Session = Depends(get_db)
  164. ):
  165. """机器解绑"""
  166. try:
  167. # 获取请求数据
  168. data = await request.json()
  169. # 解密请求数据
  170. if settings.ENCRYPT_ENABLED:
  171. encrypted_data = data.get("encrypted", "")
  172. if not encrypted_data:
  173. return {"code": 400, "msg": "缺少加密数据"}
  174. decrypted = encryptor.decrypt(encrypted_data)
  175. else:
  176. decrypted = data
  177. req = UnbindRequest(**decrypted)
  178. # 查找用户
  179. user = db.query(User).filter(User.username == req.username).first()
  180. if not user:
  181. return {"code": 401, "msg": "用户名或密码错误"}
  182. # 验证密码
  183. if not verify_password(req.password, user.password):
  184. return {"code": 401, "msg": "用户名或密码错误"}
  185. # 解绑机器码
  186. user.machine_code = None
  187. user.updated_at = datetime.now()
  188. db.commit()
  189. logger.info(f"机器解绑成功: {req.username}")
  190. return {"code": 200, "msg": "解绑成功"}
  191. except Exception as e:
  192. db.rollback()
  193. logger.error(f"机器解绑失败: {e}")
  194. return {"code": 500, "msg": "解绑失败"}
  195. @router.post("/heartbeat")
  196. async def heartbeat(
  197. request: Request,
  198. db: Session = Depends(get_db)
  199. ):
  200. """心跳检测"""
  201. try:
  202. # 获取请求数据
  203. data = await request.json()
  204. # 解密请求数据
  205. if settings.ENCRYPT_ENABLED:
  206. encrypted_data = data.get("encrypted", "")
  207. if not encrypted_data:
  208. return {"code": 400, "msg": "缺少加密数据"}
  209. decrypted = encryptor.decrypt(encrypted_data)
  210. else:
  211. decrypted = data
  212. user_id = decrypted.get("user_id")
  213. if not user_id:
  214. return {"code": 400, "msg": "参数错误"}
  215. # 查找用户
  216. user = db.query(User).filter(User.id == user_id).first()
  217. if not user:
  218. return {"code": 404, "msg": "用户不存在"}
  219. # 检查是否过期
  220. if user.expire_time and user.expire_time < datetime.now():
  221. return {"code": 403, "msg": "卡密已过期"}
  222. return {
  223. "code": 200,
  224. "msg": "正常",
  225. "data": {
  226. "expire_time": user.expire_time.isoformat() if user.expire_time else None
  227. }
  228. }
  229. except Exception as e:
  230. logger.error(f"心跳检测失败: {e}")
  231. return {"code": 500, "msg": "心跳检测失败"}
  232. @router.post("/activate-card")
  233. async def activate_card(
  234. request: Request,
  235. db: Session = Depends(get_db)
  236. ):
  237. """激活卡密(充值)"""
  238. try:
  239. # 获取请求数据
  240. data = await request.json()
  241. # 解密请求数据
  242. if settings.ENCRYPT_ENABLED:
  243. encrypted_data = data.get("encrypted", "")
  244. if not encrypted_data:
  245. return {"code": 400, "msg": "缺少加密数据"}
  246. decrypted = encryptor.decrypt(encrypted_data)
  247. else:
  248. decrypted = data
  249. req = ActivateCardRequest(**decrypted)
  250. # 查找用户
  251. user = db.query(User).filter(User.id == req.user_id).first()
  252. if not user:
  253. return {"code": 404, "msg": "用户不存在"}
  254. # 验证卡密
  255. card = db.query(Card).filter(
  256. Card.card_code == req.card_code,
  257. Card.status == 0
  258. ).first()
  259. if not card:
  260. return {"code": 400, "msg": "卡密无效或已被使用"}
  261. # 检查软件特征码是否匹配
  262. software_code = decrypted.get("software_code", "")
  263. if card.software_code != software_code:
  264. return {"code": 400, "msg": "卡密与当前软件不匹配"}
  265. # 计算新的到期时间
  266. expire_time_before = user.expire_time
  267. if expire_time_before and expire_time_before > datetime.now():
  268. # 如果还有有效期,则累加
  269. expire_time_after = expire_time_before + timedelta(days=card.days)
  270. else:
  271. # 如果已过期或没有有效期,则从当前时间开始计算
  272. expire_time_after = datetime.now() + timedelta(days=card.days)
  273. # 更新用户到期时间
  274. user.expire_time = expire_time_after
  275. user.updated_at = datetime.now()
  276. # 更新卡密状态
  277. card.user_id = user.id
  278. card.activated_at = datetime.now()
  279. card.expire_time = expire_time_after
  280. card.status = 1
  281. card.updated_at = datetime.now()
  282. # 创建充值记录
  283. recharge_record = RechargeRecord(
  284. user_id=user.id,
  285. card_id=card.id,
  286. software_code=card.software_code,
  287. days=card.days,
  288. expire_time_before=expire_time_before,
  289. expire_time_after=expire_time_after,
  290. created_at=datetime.now()
  291. )
  292. db.add(recharge_record)
  293. db.commit()
  294. logger.info(f"用户 {user.username} 激活卡密成功: {req.card_code}")
  295. return {
  296. "code": 200,
  297. "msg": "激活成功",
  298. "data": {
  299. "expire_time": expire_time_after.isoformat(),
  300. "days": card.days
  301. }
  302. }
  303. except Exception as e:
  304. db.rollback()
  305. logger.error(f"激活卡密失败: {e}")
  306. return {"code": 500, "msg": "激活失败"}
  307. @router.get("/recharge-records")
  308. async def get_recharge_records(
  309. request: Request,
  310. db: Session = Depends(get_db)
  311. ):
  312. """获取充值记录"""
  313. try:
  314. # 获取请求参数
  315. params = dict(request.query_params)
  316. # 解密请求数据(如果有)
  317. user_id = None
  318. if settings.ENCRYPT_ENABLED:
  319. # GET请求可能通过query参数传递加密数据
  320. encrypted_data = params.get("encrypted", "")
  321. if encrypted_data:
  322. decrypted = encryptor.decrypt(encrypted_data)
  323. user_id = decrypted.get("user_id")
  324. else:
  325. user_id = params.get("user_id")
  326. if user_id:
  327. user_id = int(user_id)
  328. if not user_id:
  329. return {"code": 400, "msg": "缺少用户ID"}
  330. # 查询充值记录
  331. records = db.query(RechargeRecord).filter(
  332. RechargeRecord.user_id == user_id
  333. ).order_by(RechargeRecord.created_at.desc()).all()
  334. # 格式化返回数据
  335. records_data = []
  336. for record in records:
  337. # 获取卡密代码
  338. card_code = ""
  339. if record.card_id:
  340. card = db.query(Card).filter(Card.id == record.card_id).first()
  341. if card:
  342. card_code = card.card_code
  343. records_data.append({
  344. "id": record.id,
  345. "card_code": card_code,
  346. "days": record.days,
  347. "expire_time_before": record.expire_time_before.isoformat() if record.expire_time_before else None,
  348. "expire_time_after": record.expire_time_after.isoformat() if record.expire_time_after else None,
  349. "created_at": record.created_at.isoformat() if record.created_at else None
  350. })
  351. return {
  352. "code": 200,
  353. "msg": "成功",
  354. "data": records_data
  355. }
  356. except Exception as e:
  357. logger.error(f"获取充值记录失败: {e}")
  358. return {"code": 500, "msg": "获取充值记录失败"}