users.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. """
  2. 用户管理接口
  3. """
  4. from typing import List, Optional
  5. from fastapi import APIRouter, Depends, Query, Request
  6. from fastapi.responses import StreamingResponse
  7. from sqlalchemy.orm import Session
  8. from sqlalchemy import or_, and_
  9. from datetime import datetime
  10. from app.utils.timezone import beijing_now
  11. from app.core.logger import logger
  12. import csv
  13. import io
  14. from app.database import get_db
  15. from app.api.deps import get_current_admin
  16. from app.models.user import User
  17. from app.models.admin import Admin
  18. from app.models.card import Card
  19. from app.models.software import Software
  20. from app.models.user_steam_account_limit import UserSteamAccountLimit
  21. router = APIRouter()
  22. DEFAULT_MAX_STEAM_ACCOUNTS = 3
  23. @router.get("")
  24. async def get_users(
  25. page: int = Query(1, ge=1),
  26. page_size: int = Query(10, ge=1, le=100),
  27. keyword: str = Query(None, description="搜索关键词(用户名、邮箱、机器码)"),
  28. status: int = Query(None, description="状态筛选:1-正常,0-禁用"),
  29. expire_status: str = Query(None, description="有效期状态:valid-有效,expired-已过期,none-无有效期"),
  30. machine_code: str = Query(None, description="机器码筛选"),
  31. software_ids: Optional[List[int]] = Query(None, description="软件ID多选筛选,仅展示当前卡密属于所选软件的用户"),
  32. db: Session = Depends(get_db),
  33. current_admin: Admin = Depends(get_current_admin)
  34. ):
  35. """获取用户列表,含当前卡密对应软件名称;支持按软件ID多选筛选"""
  36. offset = (page - 1) * page_size
  37. # 左连 Card、Software 以带出软件名称;有 software_ids 时改为内连并过滤
  38. if software_ids:
  39. query = (
  40. db.query(User, Software.name.label("software_name"))
  41. .select_from(User)
  42. .join(Card, User.card_id == Card.id)
  43. .join(Software, Card.software_code == Software.code)
  44. .filter(User.is_deleted == 0, Software.id.in_(software_ids))
  45. )
  46. else:
  47. query = (
  48. db.query(User, Software.name.label("software_name"))
  49. .select_from(User)
  50. .outerjoin(Card, User.card_id == Card.id)
  51. .outerjoin(Software, Card.software_code == Software.code)
  52. .filter(User.is_deleted == 0)
  53. )
  54. # 搜索
  55. if keyword:
  56. query = query.filter(
  57. or_(
  58. User.username.like(f"%{keyword}%"),
  59. User.email.like(f"%{keyword}%"),
  60. User.machine_code.like(f"%{keyword}%")
  61. )
  62. )
  63. # 筛选
  64. if status is not None:
  65. query = query.filter(User.status == status)
  66. if machine_code:
  67. query = query.filter(User.machine_code == machine_code)
  68. # 有效期状态筛选
  69. now = beijing_now()
  70. if expire_status == "valid":
  71. query = query.filter(User.expire_time > now)
  72. elif expire_status == "expired":
  73. query = query.filter(User.expire_time <= now)
  74. elif expire_status == "none":
  75. query = query.filter(User.expire_time.is_(None))
  76. # 排序
  77. query = query.order_by(User.created_at.desc())
  78. total = query.count()
  79. rows = query.offset(offset).limit(page_size).all()
  80. return {
  81. "code": 200,
  82. "msg": "成功",
  83. "data": [
  84. {
  85. "id": u.id,
  86. "username": u.username,
  87. "email": u.email,
  88. "machine_code": u.machine_code,
  89. "software_name": software_name,
  90. "expire_time": u.expire_time.isoformat() if u.expire_time else None,
  91. "last_login_time": u.last_login_time.isoformat() if u.last_login_time else None,
  92. "last_login_ip": u.last_login_ip,
  93. "status": u.status,
  94. "created_at": u.created_at.isoformat() if u.created_at else None
  95. }
  96. for u, software_name in rows
  97. ],
  98. "total": total,
  99. "page": page,
  100. "page_size": page_size
  101. }
  102. @router.get("/{user_id}/steam-account-limit")
  103. async def get_user_steam_account_limit(
  104. user_id: int,
  105. db: Session = Depends(get_db),
  106. current_admin: Admin = Depends(get_current_admin)
  107. ):
  108. """获取某用户允许绑定的最大 Steam 账号数(无配置时默认 3)"""
  109. user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first()
  110. if not user:
  111. return {"code": 404, "msg": "用户不存在"}
  112. row = db.query(UserSteamAccountLimit).filter(UserSteamAccountLimit.user_id == user_id).first()
  113. max_steam_accounts = row.max_steam_accounts if row else DEFAULT_MAX_STEAM_ACCOUNTS
  114. return {"code": 200, "msg": "成功", "data": {"user_id": user_id, "max_steam_accounts": max_steam_accounts}}
  115. @router.put("/{user_id}/steam-account-limit")
  116. async def update_user_steam_account_limit(
  117. user_id: int,
  118. request: Request,
  119. max_steam_accounts: int = Query(..., ge=1, le=100, description="该用户最多可绑定的 Steam 账号数"),
  120. db: Session = Depends(get_db),
  121. current_admin: Admin = Depends(get_current_admin)
  122. ):
  123. """设置某用户允许绑定的最大 Steam 账号数"""
  124. from app.utils.history import create_operation_log
  125. user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first()
  126. if not user:
  127. return {"code": 404, "msg": "用户不存在"}
  128. try:
  129. row = db.query(UserSteamAccountLimit).filter(UserSteamAccountLimit.user_id == user_id).first()
  130. if row:
  131. old_val = row.max_steam_accounts
  132. row.max_steam_accounts = max_steam_accounts
  133. row.updated_at = beijing_now()
  134. else:
  135. row = UserSteamAccountLimit(user_id=user_id, max_steam_accounts=max_steam_accounts)
  136. db.add(row)
  137. old_val = None
  138. client_ip = request.client.host if request and request.client else None
  139. create_operation_log(
  140. db=db,
  141. admin_id=current_admin.id,
  142. module="用户管理",
  143. action="设置用户Steam账号上限",
  144. description=f"用户 {user.username}(id={user_id}) 最大Steam账号数: {old_val} -> {max_steam_accounts}",
  145. old_data={"max_steam_accounts": old_val},
  146. new_data={"max_steam_accounts": max_steam_accounts},
  147. ip_address=client_ip
  148. )
  149. db.commit()
  150. return {"code": 200, "msg": "设置成功", "data": {"user_id": user_id, "max_steam_accounts": max_steam_accounts}}
  151. except Exception as e:
  152. try:
  153. db.rollback()
  154. except Exception:
  155. pass
  156. logger.exception("设置用户Steam账号上限失败")
  157. return {"code": 500, "msg": "系统异常,请稍后重试"}
  158. @router.get("/{user_id}")
  159. async def get_user(
  160. user_id: int,
  161. db: Session = Depends(get_db),
  162. current_admin: Admin = Depends(get_current_admin)
  163. ):
  164. """获取用户详情"""
  165. from app.models.card import Card
  166. from app.models.recharge_record import RechargeRecord
  167. user = db.query(User).filter(User.id == user_id).first()
  168. if not user:
  169. return {"code": 404, "msg": "用户不存在"}
  170. # 获取当前卡密信息
  171. card_info = None
  172. if user.card_id:
  173. card = db.query(Card).filter(Card.id == user.card_id).first()
  174. if card:
  175. card_info = {
  176. "id": card.id,
  177. "card_code": card.card_code,
  178. "software_code": card.software_code,
  179. "days": card.days,
  180. "activated_at": card.activated_at.isoformat() if card.activated_at else None,
  181. "expire_time": card.expire_time.isoformat() if card.expire_time else None,
  182. }
  183. # 获取充值记录
  184. recharge_records = db.query(RechargeRecord).filter(
  185. RechargeRecord.user_id == user_id
  186. ).order_by(RechargeRecord.created_at.desc()).limit(10).all()
  187. # 该用户允许绑定的最大 Steam 账号数(无配置时默认 3)
  188. limit_row = db.query(UserSteamAccountLimit).filter(UserSteamAccountLimit.user_id == user_id).first()
  189. max_steam_accounts = limit_row.max_steam_accounts if limit_row else DEFAULT_MAX_STEAM_ACCOUNTS
  190. return {
  191. "code": 200,
  192. "msg": "成功",
  193. "data": {
  194. "id": user.id,
  195. "username": user.username,
  196. "email": user.email,
  197. "machine_code": user.machine_code,
  198. "card_id": user.card_id,
  199. "card_info": card_info,
  200. "max_steam_accounts": max_steam_accounts,
  201. "expire_time": user.expire_time.isoformat() if user.expire_time else None,
  202. "last_login_time": user.last_login_time.isoformat() if user.last_login_time else None,
  203. "last_login_ip": user.last_login_ip,
  204. "status": user.status,
  205. "created_at": user.created_at.isoformat() if user.created_at else None,
  206. "updated_at": user.updated_at.isoformat() if user.updated_at else None,
  207. "recharge_records": [
  208. {
  209. "id": r.id,
  210. "card_code": r.card.card_code if r.card else None,
  211. "software_code": r.card.software_code if r.card else None,
  212. "days": r.card.days if r.card else None,
  213. "activated_at": r.created_at.isoformat() if r.created_at else None,
  214. }
  215. for r in recharge_records
  216. ]
  217. }
  218. }
  219. @router.put("/{user_id}/status")
  220. async def update_user_status(
  221. user_id: int,
  222. status: int = Query(..., description="状态:1-正常,0-禁用"),
  223. request: Request = None,
  224. db: Session = Depends(get_db),
  225. current_admin: Admin = Depends(get_current_admin)
  226. ):
  227. """启用/禁用用户"""
  228. try:
  229. user = db.query(User).filter(User.id == user_id).first()
  230. if not user:
  231. return {"code": 404, "msg": "用户不存在"}
  232. old_status = user.status
  233. user.status = status
  234. user.updated_at = beijing_now()
  235. # 创建历史记录
  236. from app.utils.history import create_user_history, create_operation_log
  237. create_user_history(db, user, "UPDATE")
  238. # 记录操作日志
  239. client_ip = request.client.host if request and request.client else None
  240. status_text = "启用" if status == 1 else "禁用"
  241. create_operation_log(
  242. db=db,
  243. admin_id=current_admin.id,
  244. module="用户管理",
  245. action="更新用户状态",
  246. description=f"{status_text}用户账号:{user.username}",
  247. old_data={"status": old_status},
  248. new_data={"status": status},
  249. ip_address=client_ip
  250. )
  251. # 一次性提交所有更改
  252. db.commit()
  253. return {"code": 200, "msg": "状态更新成功"}
  254. except Exception as e:
  255. try:
  256. db.rollback()
  257. except Exception:
  258. pass
  259. logger.exception("更新用户状态失败")
  260. return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
  261. @router.delete("/{user_id}")
  262. async def delete_user(
  263. user_id: int,
  264. request: Request,
  265. db: Session = Depends(get_db),
  266. current_admin: Admin = Depends(get_current_admin)
  267. ):
  268. """删除用户(软删除)"""
  269. try:
  270. user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first()
  271. if not user:
  272. return {"code": 404, "msg": "用户不存在"}
  273. user.is_deleted = 1
  274. user.updated_at = beijing_now()
  275. # 创建历史记录
  276. from app.utils.history import create_user_history, create_operation_log
  277. create_user_history(db, user, "DELETE")
  278. # 记录操作日志
  279. client_ip = request.client.host if request.client else None
  280. create_operation_log(
  281. db=db,
  282. admin_id=current_admin.id,
  283. module="用户管理",
  284. action="删除用户",
  285. description=f"删除用户账号:{user.username}",
  286. old_data={
  287. "username": user.username,
  288. "email": user.email,
  289. "machine_code": user.machine_code,
  290. "status": user.status
  291. },
  292. ip_address=client_ip
  293. )
  294. # 一次性提交所有更改
  295. db.commit()
  296. return {"code": 200, "msg": "删除成功"}
  297. except Exception as e:
  298. try:
  299. db.rollback()
  300. except Exception:
  301. pass
  302. logger.exception("删除用户失败")
  303. return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
  304. @router.get("/export/excel")
  305. async def export_users_excel(
  306. request: Request,
  307. keyword: str = Query(None, description="搜索关键词"),
  308. status: int = Query(None, description="状态筛选"),
  309. expire_status: str = Query(None, description="有效期状态"),
  310. machine_code: str = Query(None, description="机器码筛选"),
  311. software_ids: Optional[List[int]] = Query(None, description="软件ID多选筛选"),
  312. db: Session = Depends(get_db),
  313. current_admin: Admin = Depends(get_current_admin)
  314. ):
  315. """导出用户为Excel(含软件名称列,支持按软件多选筛选)"""
  316. if software_ids:
  317. query = (
  318. db.query(User, Software.name.label("software_name"))
  319. .select_from(User)
  320. .join(Card, User.card_id == Card.id)
  321. .join(Software, Card.software_code == Software.code)
  322. .filter(User.is_deleted == 0, Software.id.in_(software_ids))
  323. )
  324. else:
  325. query = (
  326. db.query(User, Software.name.label("software_name"))
  327. .select_from(User)
  328. .outerjoin(Card, User.card_id == Card.id)
  329. .outerjoin(Software, Card.software_code == Software.code)
  330. .filter(User.is_deleted == 0)
  331. )
  332. if keyword:
  333. query = query.filter(
  334. or_(
  335. User.username.like(f"%{keyword}%"),
  336. User.email.like(f"%{keyword}%"),
  337. User.machine_code.like(f"%{keyword}%")
  338. )
  339. )
  340. if status is not None:
  341. query = query.filter(User.status == status)
  342. if machine_code:
  343. query = query.filter(User.machine_code == machine_code)
  344. now = beijing_now()
  345. if expire_status == "valid":
  346. query = query.filter(User.expire_time > now)
  347. elif expire_status == "expired":
  348. query = query.filter(User.expire_time <= now)
  349. elif expire_status == "none":
  350. query = query.filter(User.expire_time.is_(None))
  351. rows = query.order_by(User.created_at.desc()).all()
  352. from app.utils.history import create_operation_log
  353. client_ip = request.client.host if request.client else None
  354. create_operation_log(
  355. db=db,
  356. admin_id=current_admin.id,
  357. module="用户管理",
  358. action="导出用户Excel",
  359. description=f"导出用户数据为Excel,共导出{len(rows)}条用户记录",
  360. new_data={
  361. "export_count": len(rows),
  362. "export_format": "Excel",
  363. "filters": {
  364. "keyword": keyword,
  365. "status": status,
  366. "expire_status": expire_status,
  367. "machine_code": machine_code,
  368. "software_ids": software_ids
  369. }
  370. },
  371. ip_address=client_ip
  372. )
  373. db.commit()
  374. output = io.StringIO()
  375. writer = csv.writer(output)
  376. writer.writerow([
  377. 'ID', '用户名', '邮箱', '机器码', '软件', '到期时间', '最后登录时间',
  378. '最后登录IP', '状态', '注册时间', '更新时间'
  379. ])
  380. for user, software_name in rows:
  381. writer.writerow([
  382. user.id,
  383. user.username,
  384. user.email or '',
  385. user.machine_code or '',
  386. software_name or '',
  387. user.expire_time.isoformat() if user.expire_time else '',
  388. user.last_login_time.isoformat() if user.last_login_time else '',
  389. user.last_login_ip or '',
  390. '正常' if user.status == 1 else '禁用',
  391. user.created_at.isoformat() if user.created_at else '',
  392. user.updated_at.isoformat() if user.updated_at else ''
  393. ])
  394. output.seek(0)
  395. return StreamingResponse(
  396. iter([output.getvalue()]),
  397. media_type="text/csv",
  398. headers={"Content-Disposition": f"attachment; filename=users_{beijing_now().strftime('%Y%m%d_%H%M%S')}.csv"}
  399. )