logs.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. """
  2. 日志管理接口
  3. """
  4. from fastapi import APIRouter, Depends, Query
  5. from fastapi.responses import StreamingResponse
  6. from sqlalchemy.orm import Session
  7. from sqlalchemy import or_, and_
  8. from app.database import get_db
  9. from app.api.deps import get_current_admin
  10. from app.models.api_log import APILog
  11. from datetime import datetime, timedelta
  12. import csv
  13. import io
  14. router = APIRouter()
  15. @router.get("")
  16. async def get_logs(
  17. page: int = Query(1, ge=1),
  18. page_size: int = Query(10, ge=1, le=100),
  19. keyword: str = Query(None, description="搜索关键词(接口路径)"),
  20. user_id: int = Query(None, description="用户ID筛选"),
  21. endpoint: str = Query(None, description="接口筛选"),
  22. method: str = Query(None, description="方法筛选"),
  23. response_code: int = Query(None, description="状态码筛选"),
  24. start_time: str = Query(None, description="开始时间"),
  25. end_time: str = Query(None, description="结束时间"),
  26. db: Session = Depends(get_db),
  27. current_admin = Depends(get_current_admin)
  28. ):
  29. """获取日志列表"""
  30. offset = (page - 1) * page_size
  31. query = db.query(APILog)
  32. # 搜索
  33. if keyword:
  34. query = query.filter(APILog.endpoint.like(f"%{keyword}%"))
  35. # 筛选
  36. if user_id is not None:
  37. query = query.filter(APILog.user_id == user_id)
  38. if endpoint:
  39. query = query.filter(APILog.endpoint.like(f"%{endpoint}%"))
  40. if method:
  41. query = query.filter(APILog.method == method)
  42. if response_code is not None:
  43. query = query.filter(APILog.response_code == response_code)
  44. if start_time:
  45. try:
  46. start_dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
  47. query = query.filter(APILog.created_at >= start_dt)
  48. except:
  49. pass
  50. if end_time:
  51. try:
  52. end_dt = datetime.fromisoformat(end_time.replace('Z', '+00:00'))
  53. query = query.filter(APILog.created_at <= end_dt)
  54. except:
  55. pass
  56. # 排序和分页
  57. total = query.count()
  58. logs = query.order_by(APILog.created_at.desc()).offset(offset).limit(page_size).all()
  59. return {
  60. "code": 200,
  61. "msg": "成功",
  62. "data": [
  63. {
  64. "id": l.id,
  65. "user_id": l.user_id,
  66. "endpoint": l.endpoint,
  67. "method": l.method,
  68. "ip_address": l.ip_address,
  69. "user_agent": l.user_agent,
  70. "response_code": l.response_code,
  71. "execution_time": l.execution_time,
  72. "created_at": l.created_at.isoformat() if l.created_at else None
  73. }
  74. for l in logs
  75. ],
  76. "total": total,
  77. "page": page,
  78. "page_size": page_size
  79. }
  80. @router.get("/{log_id}")
  81. async def get_log_detail(
  82. log_id: int,
  83. db: Session = Depends(get_db),
  84. current_admin = Depends(get_current_admin)
  85. ):
  86. """获取日志详情"""
  87. log = db.query(APILog).filter(APILog.id == log_id).first()
  88. if not log:
  89. return {"code": 404, "msg": "日志不存在"}
  90. return {
  91. "code": 200,
  92. "msg": "成功",
  93. "data": {
  94. "id": log.id,
  95. "user_id": log.user_id,
  96. "endpoint": log.endpoint,
  97. "method": log.method,
  98. "request_data": log.request_data,
  99. "response_data": log.response_data,
  100. "ip_address": log.ip_address,
  101. "user_agent": log.user_agent,
  102. "response_code": log.response_code,
  103. "execution_time": log.execution_time,
  104. "created_at": log.created_at.isoformat() if log.created_at else None
  105. }
  106. }
  107. @router.delete("")
  108. async def clear_logs(
  109. db: Session = Depends(get_db),
  110. current_admin = Depends(get_current_admin)
  111. ):
  112. """清空日志"""
  113. try:
  114. db.query(APILog).delete()
  115. db.commit()
  116. return {"code": 200, "msg": "清空成功"}
  117. except Exception as e:
  118. db.rollback()
  119. return {"code": 500, "msg": f"清空失败: {str(e)}"}
  120. @router.get("/export/csv")
  121. async def export_logs_csv(
  122. keyword: str = Query(None),
  123. user_id: int = Query(None),
  124. endpoint: str = Query(None),
  125. method: str = Query(None),
  126. response_code: int = Query(None),
  127. start_time: str = Query(None),
  128. end_time: str = Query(None),
  129. db: Session = Depends(get_db),
  130. current_admin = Depends(get_current_admin)
  131. ):
  132. """导出日志为CSV"""
  133. query = db.query(APILog)
  134. # 应用相同的筛选条件
  135. if keyword:
  136. query = query.filter(APILog.endpoint.like(f"%{keyword}%"))
  137. if user_id is not None:
  138. query = query.filter(APILog.user_id == user_id)
  139. if endpoint:
  140. query = query.filter(APILog.endpoint.like(f"%{endpoint}%"))
  141. if method:
  142. query = query.filter(APILog.method == method)
  143. if response_code is not None:
  144. query = query.filter(APILog.response_code == response_code)
  145. if start_time:
  146. try:
  147. start_dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
  148. query = query.filter(APILog.created_at >= start_dt)
  149. except:
  150. pass
  151. if end_time:
  152. try:
  153. end_dt = datetime.fromisoformat(end_time.replace('Z', '+00:00'))
  154. query = query.filter(APILog.created_at <= end_dt)
  155. except:
  156. pass
  157. logs = query.order_by(APILog.created_at.desc()).all()
  158. # 生成CSV
  159. output = io.StringIO()
  160. writer = csv.writer(output)
  161. writer.writerow(['ID', '用户ID', '接口', '方法', 'IP地址', '用户代理', '状态码', '执行时间(ms)', '创建时间'])
  162. for log in logs:
  163. writer.writerow([
  164. log.id,
  165. log.user_id or '',
  166. log.endpoint,
  167. log.method,
  168. log.ip_address or '',
  169. log.user_agent or '',
  170. log.response_code or '',
  171. log.execution_time or '',
  172. log.created_at.isoformat() if log.created_at else ''
  173. ])
  174. output.seek(0)
  175. return StreamingResponse(
  176. iter([output.getvalue()]),
  177. media_type="text/csv",
  178. headers={"Content-Disposition": f"attachment; filename=logs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"}
  179. )
  180. @router.get("/export/excel")
  181. async def export_logs(
  182. keyword: str = Query(None),
  183. user_id: int = Query(None),
  184. endpoint: str = Query(None),
  185. method: str = Query(None),
  186. response_code: int = Query(None),
  187. start_time: str = Query(None),
  188. end_time: str = Query(None),
  189. db: Session = Depends(get_db),
  190. current_admin = Depends(get_current_admin)
  191. ):
  192. """导出日志为Excel"""
  193. # 暂时使用CSV格式,如果需要Excel格式可以安装openpyxl
  194. return await export_logs_csv(
  195. keyword=keyword,
  196. user_id=user_id,
  197. endpoint=endpoint,
  198. method=method,
  199. response_code=response_code,
  200. start_time=start_time,
  201. end_time=end_time,
  202. db=db,
  203. current_admin=current_admin
  204. )