auth.py 14 KB

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