diff --git a/README.md b/README.md
index 0ec6924..0e2c186 100644
--- a/README.md
+++ b/README.md
@@ -190,7 +190,20 @@ pnpm dev
你可以在群里提出任何疑问,我会尽快回复答疑。
-
+
+
+## 打赏
+如果项目有帮助到你,可以请作者喝杯咖啡~
+
+
+
+
### Visitors Count
diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py
index 76a14be..1813810 100644
--- a/app/api/v1/__init__.py
+++ b/app/api/v1/__init__.py
@@ -8,6 +8,7 @@ from .depts import depts_router
from .menus import menus_router
from .roles import roles_router
from .users import users_router
+from .auditlog import auditlog_router
v1_router = APIRouter()
@@ -17,3 +18,4 @@ v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermi
v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermisson])
v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermisson])
v1_router.include_router(depts_router, prefix="/dept", dependencies=[DependPermisson])
+v1_router.include_router(auditlog_router, prefix="/auditlog", dependencies=[DependPermisson])
diff --git a/app/api/v1/apis/apis.py b/app/api/v1/apis/apis.py
index 67f9c88..9cdca28 100644
--- a/app/api/v1/apis/apis.py
+++ b/app/api/v1/apis/apis.py
@@ -1,9 +1,7 @@
from fastapi import APIRouter, Query
-
from tortoise.expressions import Q
from app.controllers.api import api_controller
-
from app.schemas import Success, SuccessExtra
from app.schemas.apis import *
diff --git a/app/api/v1/auditlog/__init__.py b/app/api/v1/auditlog/__init__.py
new file mode 100644
index 0000000..d90f07c
--- /dev/null
+++ b/app/api/v1/auditlog/__init__.py
@@ -0,0 +1,8 @@
+from fastapi import APIRouter
+
+from .auditlog import router
+
+auditlog_router = APIRouter()
+auditlog_router.include_router(router, tags=["审计日志模块"])
+
+__all__ = ["auditlog_router"]
diff --git a/app/api/v1/auditlog/auditlog.py b/app/api/v1/auditlog/auditlog.py
new file mode 100644
index 0000000..7a43b6f
--- /dev/null
+++ b/app/api/v1/auditlog/auditlog.py
@@ -0,0 +1,39 @@
+from fastapi import APIRouter, Query
+from tortoise.expressions import Q
+from app.models.admin import AuditLog
+
+from app.schemas import SuccessExtra
+from app.schemas.apis import *
+from app.core.dependency import DependPermisson
+
+router = APIRouter()
+
+@router.get('/list', summary="查看操作日志", dependencies=[DependPermisson])
+async def get_audit_log_list(
+ page: int = Query(1, description="页码"),
+ page_size: int = Query(10, description="每页数量"),
+ username: str = Query("", description="操作人名称"),
+ module: str = Query("", description="功能模块"),
+ summary: str = Query("", description="接口描述"),
+ start_time: str = Query("", description="开始时间"),
+ end_time: str = Query("", description="结束时间"),
+):
+
+ q = Q()
+ if username:
+ q &= Q(username__icontains=username)
+ if module:
+ q &= Q(module__icontains=module)
+ if summary:
+ q &= Q(summary__icontains=summary)
+ if start_time and end_time:
+ q &= Q(created_at__range=[start_time, end_time])
+ elif start_time:
+ q &= Q(created_at__gte=start_time)
+ elif end_time:
+ q &= Q(created_at__lte=end_time)
+
+ audit_log_objs = await AuditLog.filter(q).offset((page - 1) * page_size).limit(page_size).order_by("-created_at")
+ total = await AuditLog.filter(q).count()
+ data = [await audit_log.to_dict() for audit_log in audit_log_objs]
+ return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
diff --git a/app/controllers/api.py b/app/controllers/api.py
index 86cce96..41469e9 100644
--- a/app/controllers/api.py
+++ b/app/controllers/api.py
@@ -1,15 +1,15 @@
+from fastapi.routing import APIRoute
+
from app.core.crud import CRUDBase
+from app.log import logger
from app.models.admin import Api
from app.schemas.apis import ApiCreate, ApiUpdate
-from fastapi.routing import APIRoute
-from app.log import logger
class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]):
def __init__(self):
super().__init__(model=Api)
-
async def refresh_api(self):
from app import app
@@ -40,4 +40,5 @@ class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]):
logger.debug(f"API Created {method} {path}")
await Api.create(**dict(method=method, path=path, summary=summary, tags=tags))
+
api_controller = ApiController()
diff --git a/app/core/init_app.py b/app/core/init_app.py
index 1c1e5f8..ef6342c 100644
--- a/app/core/init_app.py
+++ b/app/core/init_app.py
@@ -20,7 +20,7 @@ from app.models.admin import Menu
from app.schemas.menus import MenuType
from app.settings.config import settings
-from .middlewares import BackGroundTaskMiddleware
+from .middlewares import BackGroundTaskMiddleware, HttpAuditLogMiddleware
def make_middlewares():
@@ -33,6 +33,14 @@ def make_middlewares():
allow_headers=settings.CORS_ALLOW_HEADERS,
),
Middleware(BackGroundTaskMiddleware),
+ Middleware(
+ HttpAuditLogMiddleware,
+ methods=["GET", "POST", "PUT", "DELETE"],
+ exclude_paths=[
+ "/docs",
+ "/openapi.json",
+ ],
+ ),
]
return middleware
@@ -134,6 +142,17 @@ async def init_menus():
component="/system/dept",
keepalive=False,
),
+ Menu(
+ menu_type=MenuType.MENU,
+ name="审计日志",
+ path="auditlog",
+ order=6,
+ parent_id=parent_menu.id,
+ icon="ph:clipboard-text-bold",
+ is_hidden=False,
+ component="/system/auditlog",
+ keepalive=False,
+ )
]
await Menu.bulk_create(children_menu)
parent_menu = await Menu.create(
diff --git a/app/core/middlewares.py b/app/core/middlewares.py
index a6456b5..db7396e 100644
--- a/app/core/middlewares.py
+++ b/app/core/middlewares.py
@@ -1,6 +1,16 @@
+import re
+from datetime import datetime
+
+from fastapi import FastAPI
+from fastapi.responses import Response
+from fastapi.routing import APIRoute
+from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.types import ASGIApp, Receive, Scope, Send
+from app.core.dependency import AuthControl
+from app.models.admin import AuditLog, User
+
from .bgtask import BgTasks
@@ -32,3 +42,55 @@ class BackGroundTaskMiddleware(SimpleBaseMiddleware):
async def after_request(self, request):
await BgTasks.execute_tasks()
+
+
+class HttpAuditLogMiddleware(BaseHTTPMiddleware):
+ def __init__(self, app, methods: list, exclude_paths: list):
+ super().__init__(app)
+ self.methods = methods
+ self.exclude_paths = exclude_paths
+
+ async def get_request_log(self, request: Request, response: Response) -> dict:
+ """
+ 根据request和response对象获取对应的日志记录数据
+ """
+ data: dict = {"path": request.url.path, "status": response.status_code, "method": request.method}
+ # 路由信息
+ app: FastAPI = request.app
+ for route in app.routes:
+ if (
+ isinstance(route, APIRoute)
+ and route.path_regex.match(request.url.path)
+ and request.method in route.methods
+ ):
+ data["module"] = ",".join(route.tags)
+ data["summary"] = route.summary
+ # 获取用户信息
+ token = request.headers.get("token")
+ user_obj = None
+ if token:
+ user_obj: User = await AuthControl.is_authed(token)
+ data["user_id"] = user_obj.id if user_obj else 0
+ data["username"] = user_obj.username if user_obj else ""
+ return data
+
+ async def before_request(self, request: Request):
+ pass
+
+ async def after_request(self, request: Request, response: Response, process_time: int):
+ if request.method in self.methods: # 请求方法为配置的记录方法
+ for path in self.exclude_paths:
+ if re.search(path, request.url.path, re.I) is not None:
+ return
+ data: dict = await self.get_request_log(request=request, response=response)
+ data["response_time"] = process_time # 响应时间
+ await AuditLog.create(**data)
+
+ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
+ start_time: datetime = datetime.now()
+ await self.before_request(request)
+ response = await call_next(request)
+ end_time: datetime = datetime.now()
+ process_time = int((end_time.timestamp() - start_time.timestamp()) * 1000)
+ await self.after_request(request, response, process_time)
+ return response
diff --git a/app/models/admin.py b/app/models/admin.py
index 81b5843..9ba602f 100644
--- a/app/models/admin.py
+++ b/app/models/admin.py
@@ -79,3 +79,14 @@ class DeptClosure(BaseModel, TimestampMixin):
ancestor = fields.IntField(description="父代")
descendant = fields.IntField(description="子代")
level = fields.IntField(default=0, description="深度")
+
+
+class AuditLog(BaseModel, TimestampMixin):
+ user_id = fields.IntField(description="用户ID", index=True)
+ username = fields.CharField(max_length=64, default="", description="用户名称", index=True)
+ module = fields.CharField(max_length=64, default="", description="功能模块", index=True)
+ summary = fields.CharField(max_length=128, default="", description="请求描述", index=True)
+ method = fields.CharField(max_length=10, default="", description="请求方法", index=True)
+ path = fields.CharField(max_length=255, default="", description="请求路径", index=True)
+ status = fields.IntField(default=-1, description="状态码", index=True)
+ response_time = fields.IntField(default=0, description="响应时间(单位ms)")
diff --git a/deploy/sample-picture/1.jpg b/deploy/sample-picture/1.jpg
new file mode 100644
index 0000000..d8e4f08
Binary files /dev/null and b/deploy/sample-picture/1.jpg differ
diff --git a/deploy/sample-picture/2.jpg b/deploy/sample-picture/2.jpg
new file mode 100644
index 0000000..e9961f3
Binary files /dev/null and b/deploy/sample-picture/2.jpg differ
diff --git a/deploy/sample-picture/3.jpg b/deploy/sample-picture/3.jpg
new file mode 100644
index 0000000..6a6bf83
Binary files /dev/null and b/deploy/sample-picture/3.jpg differ
diff --git a/web/src/api/index.js b/web/src/api/index.js
index 6337e6f..54f05f8 100644
--- a/web/src/api/index.js
+++ b/web/src/api/index.js
@@ -36,4 +36,6 @@ export default {
createDept: (data = {}) => request.post('/dept/create', data),
updateDept: (data = {}) => request.post('/dept/update', data),
deleteDept: (params = {}) => request.delete('/dept/delete', { params }),
+ // auditlog
+ getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
}
diff --git a/web/src/views/system/auditlog/index.vue b/web/src/views/system/auditlog/index.vue
new file mode 100644
index 0000000..5792b58
--- /dev/null
+++ b/web/src/views/system/auditlog/index.vue
@@ -0,0 +1,231 @@
+
+
+
+
+