users.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. """
  2. 用户管理接口
  3. """
  4. from fastapi import APIRouter, Depends, Query, Request
  5. from fastapi.responses import StreamingResponse
  6. from sqlalchemy.orm import Session
  7. from sqlalchemy import or_, and_
  8. from datetime import datetime
  9. from app.utils.timezone import beijing_now
  10. import csv
  11. import io
  12. from app.database import get_db
  13. from app.api.deps import get_current_admin
  14. from app.models.user import User
  15. from app.models.admin import Admin
  16. router = APIRouter()
  17. @router.get("")
  18. async def get_users(
  19. page: int = Query(1, ge=1),
  20. page_size: int = Query(10, ge=1, le=100),
  21. keyword: str = Query(None, description="搜索关键词(用户名、邮箱、机器码)"),
  22. status: int = Query(None, description="状态筛选:1-正常,0-禁用"),
  23. expire_status: str = Query(None, description="有效期状态:valid-有效,expired-已过期,none-无有效期"),
  24. machine_code: str = Query(None, description="机器码筛选"),
  25. db: Session = Depends(get_db),
  26. current_admin: Admin = Depends(get_current_admin)
  27. ):
  28. """获取用户列表"""
  29. offset = (page - 1) * page_size
  30. query = db.query(User).filter(User.is_deleted == 0)
  31. # 搜索
  32. if keyword:
  33. query = query.filter(
  34. or_(
  35. User.username.like(f"%{keyword}%"),
  36. User.email.like(f"%{keyword}%"),
  37. User.machine_code.like(f"%{keyword}%")
  38. )
  39. )
  40. # 筛选
  41. if status is not None:
  42. query = query.filter(User.status == status)
  43. if machine_code:
  44. query = query.filter(User.machine_code == machine_code)
  45. # 有效期状态筛选
  46. now = beijing_now()
  47. if expire_status == "valid":
  48. query = query.filter(User.expire_time > now)
  49. elif expire_status == "expired":
  50. query = query.filter(User.expire_time <= now)
  51. elif expire_status == "none":
  52. query = query.filter(User.expire_time.is_(None))
  53. # 排序
  54. query = query.order_by(User.created_at.desc())
  55. users = query.offset(offset).limit(page_size).all()
  56. total = query.count()
  57. return {
  58. "code": 200,
  59. "msg": "成功",
  60. "data": [
  61. {
  62. "id": u.id,
  63. "username": u.username,
  64. "email": u.email,
  65. "machine_code": u.machine_code,
  66. "expire_time": u.expire_time.isoformat() if u.expire_time else None,
  67. "last_login_time": u.last_login_time.isoformat() if u.last_login_time else None,
  68. "last_login_ip": u.last_login_ip,
  69. "status": u.status,
  70. "created_at": u.created_at.isoformat() if u.created_at else None
  71. }
  72. for u in users
  73. ],
  74. "total": total,
  75. "page": page,
  76. "page_size": page_size
  77. }
  78. @router.get("/{user_id}")
  79. async def get_user(
  80. user_id: int,
  81. db: Session = Depends(get_db),
  82. current_admin: Admin = Depends(get_current_admin)
  83. ):
  84. """获取用户详情"""
  85. from app.models.card import Card
  86. from app.models.recharge_record import RechargeRecord
  87. user = db.query(User).filter(User.id == user_id).first()
  88. if not user:
  89. return {"code": 404, "msg": "用户不存在"}
  90. # 获取当前卡密信息
  91. card_info = None
  92. if user.card_id:
  93. card = db.query(Card).filter(Card.id == user.card_id).first()
  94. if card:
  95. card_info = {
  96. "id": card.id,
  97. "card_code": card.card_code,
  98. "software_code": card.software_code,
  99. "days": card.days,
  100. "activated_at": card.activated_at.isoformat() if card.activated_at else None,
  101. "expire_time": card.expire_time.isoformat() if card.expire_time else None,
  102. }
  103. # 获取充值记录
  104. recharge_records = db.query(RechargeRecord).filter(
  105. RechargeRecord.user_id == user_id
  106. ).order_by(RechargeRecord.created_at.desc()).limit(10).all()
  107. return {
  108. "code": 200,
  109. "msg": "成功",
  110. "data": {
  111. "id": user.id,
  112. "username": user.username,
  113. "email": user.email,
  114. "machine_code": user.machine_code,
  115. "card_id": user.card_id,
  116. "card_info": card_info,
  117. "expire_time": user.expire_time.isoformat() if user.expire_time else None,
  118. "last_login_time": user.last_login_time.isoformat() if user.last_login_time else None,
  119. "last_login_ip": user.last_login_ip,
  120. "status": user.status,
  121. "created_at": user.created_at.isoformat() if user.created_at else None,
  122. "updated_at": user.updated_at.isoformat() if user.updated_at else None,
  123. "recharge_records": [
  124. {
  125. "id": r.id,
  126. "card_code": r.card.card_code if r.card else None,
  127. "software_code": r.card.software_code if r.card else None,
  128. "days": r.card.days if r.card else None,
  129. "activated_at": r.created_at.isoformat() if r.created_at else None,
  130. }
  131. for r in recharge_records
  132. ]
  133. }
  134. }
  135. @router.put("/{user_id}/status")
  136. async def update_user_status(
  137. user_id: int,
  138. status: int = Query(..., description="状态:1-正常,0-禁用"),
  139. request: Request = None,
  140. db: Session = Depends(get_db),
  141. current_admin: Admin = Depends(get_current_admin)
  142. ):
  143. """启用/禁用用户"""
  144. try:
  145. user = db.query(User).filter(User.id == user_id).first()
  146. if not user:
  147. return {"code": 404, "msg": "用户不存在"}
  148. old_status = user.status
  149. user.status = status
  150. user.updated_at = beijing_now()
  151. # 创建历史记录
  152. from app.utils.history import create_user_history, create_operation_log
  153. create_user_history(db, user, "UPDATE")
  154. # 记录操作日志
  155. client_ip = request.client.host if request and request.client else None
  156. status_text = "启用" if status == 1 else "禁用"
  157. create_operation_log(
  158. db=db,
  159. admin_id=current_admin.id,
  160. module="用户管理",
  161. action="更新用户状态",
  162. description=f"{status_text}用户账号:{user.username}",
  163. old_data={"status": old_status},
  164. new_data={"status": status},
  165. ip_address=client_ip
  166. )
  167. # 一次性提交所有更改
  168. db.commit()
  169. return {"code": 200, "msg": "状态更新成功"}
  170. except Exception as e:
  171. try:
  172. db.rollback()
  173. except Exception:
  174. pass
  175. logger.exception("更新用户状态失败")
  176. return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
  177. @router.delete("/{user_id}")
  178. async def delete_user(
  179. user_id: int,
  180. request: Request,
  181. db: Session = Depends(get_db),
  182. current_admin: Admin = Depends(get_current_admin)
  183. ):
  184. """删除用户(软删除)"""
  185. try:
  186. user = db.query(User).filter(User.id == user_id, User.is_deleted == 0).first()
  187. if not user:
  188. return {"code": 404, "msg": "用户不存在"}
  189. user.is_deleted = 1
  190. user.updated_at = beijing_now()
  191. # 创建历史记录
  192. from app.utils.history import create_user_history, create_operation_log
  193. create_user_history(db, user, "DELETE")
  194. # 记录操作日志
  195. client_ip = request.client.host if request.client else None
  196. create_operation_log(
  197. db=db,
  198. admin_id=current_admin.id,
  199. module="用户管理",
  200. action="删除用户",
  201. description=f"删除用户账号:{user.username}",
  202. old_data={
  203. "username": user.username,
  204. "email": user.email,
  205. "machine_code": user.machine_code,
  206. "status": user.status
  207. },
  208. ip_address=client_ip
  209. )
  210. # 一次性提交所有更改
  211. db.commit()
  212. return {"code": 200, "msg": "删除成功"}
  213. except Exception as e:
  214. try:
  215. db.rollback()
  216. except Exception:
  217. pass
  218. logger.exception("删除用户失败")
  219. return {"code": 500, "msg": "系统异常,操作未完成,请稍后重试;若持续发生请联系客服"}
  220. @router.get("/export/excel")
  221. async def export_users_excel(
  222. request: Request,
  223. keyword: str = Query(None, description="搜索关键词"),
  224. status: int = Query(None, description="状态筛选"),
  225. expire_status: str = Query(None, description="有效期状态"),
  226. machine_code: str = Query(None, description="机器码筛选"),
  227. db: Session = Depends(get_db),
  228. current_admin: Admin = Depends(get_current_admin)
  229. ):
  230. """导出用户为Excel"""
  231. # 应用相同的筛选条件构建查询
  232. query = db.query(User).filter(User.is_deleted == 0)
  233. # 搜索
  234. if keyword:
  235. query = query.filter(
  236. or_(
  237. User.username.like(f"%{keyword}%"),
  238. User.email.like(f"%{keyword}%"),
  239. User.machine_code.like(f"%{keyword}%")
  240. )
  241. )
  242. # 筛选
  243. if status is not None:
  244. query = query.filter(User.status == status)
  245. if machine_code:
  246. query = query.filter(User.machine_code == machine_code)
  247. # 有效期状态筛选
  248. now = beijing_now()
  249. if expire_status == "valid":
  250. query = query.filter(User.expire_time > now)
  251. elif expire_status == "expired":
  252. query = query.filter(User.expire_time <= now)
  253. elif expire_status == "none":
  254. query = query.filter(User.expire_time.is_(None))
  255. users = query.order_by(User.created_at.desc()).all()
  256. # 记录操作日志
  257. from app.utils.history import create_operation_log
  258. client_ip = request.client.host if request.client else None
  259. create_operation_log(
  260. db=db,
  261. admin_id=current_admin.id,
  262. module="用户管理",
  263. action="导出用户Excel",
  264. description=f"导出用户数据为Excel,共导出{len(users)}条用户记录",
  265. new_data={
  266. "export_count": len(users),
  267. "export_format": "Excel",
  268. "filters": {
  269. "keyword": keyword,
  270. "status": status,
  271. "expire_status": expire_status,
  272. "machine_code": machine_code
  273. }
  274. },
  275. ip_address=client_ip
  276. )
  277. db.commit()
  278. # 生成CSV格式(临时方案,后续可以升级为真正的Excel格式)
  279. output = io.StringIO()
  280. writer = csv.writer(output)
  281. writer.writerow([
  282. 'ID', '用户名', '邮箱', '机器码', '到期时间', '最后登录时间',
  283. '最后登录IP', '状态', '注册时间', '更新时间'
  284. ])
  285. for user in users:
  286. writer.writerow([
  287. user.id,
  288. user.username,
  289. user.email or '',
  290. user.machine_code or '',
  291. user.expire_time.isoformat() if user.expire_time else '',
  292. user.last_login_time.isoformat() if user.last_login_time else '',
  293. user.last_login_ip or '',
  294. '正常' if user.status == 1 else '禁用',
  295. user.created_at.isoformat() if user.created_at else '',
  296. user.updated_at.isoformat() if user.updated_at else ''
  297. ])
  298. output.seek(0)
  299. return StreamingResponse(
  300. iter([output.getvalue()]),
  301. media_type="text/csv",
  302. headers={"Content-Disposition": f"attachment; filename=users_{beijing_now().strftime('%Y%m%d_%H%M%S')}.csv"}
  303. )