Update
This commit is contained in:
parent
7c3818f7c4
commit
f6ce09a0f5
@ -20,9 +20,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
|||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends gcc python3-dev bash nginx vim curl procps net-tools
|
&& apt-get install -y --no-install-recommends gcc python3-dev bash nginx vim curl procps net-tools
|
||||||
|
|
||||||
RUN pip install poetry -i https://pypi.tuna.tsinghua.edu.cn/simple \
|
RUN pip install uv -i https://pypi.tuna.tsinghua.edu.cn/simple \
|
||||||
&& poetry config virtualenvs.create false \
|
&& uv add --requirements requirements.txt
|
||||||
&& poetry install
|
|
||||||
|
|
||||||
COPY --from=web /opt/vue-fastapi-admin/web/dist /opt/vue-fastapi-admin/web/dist
|
COPY --from=web /opt/vue-fastapi-admin/web/dist /opt/vue-fastapi-admin/web/dist
|
||||||
ADD /deploy/web.conf /etc/nginx/sites-available/web.conf
|
ADD /deploy/web.conf /etc/nginx/sites-available/web.conf
|
||||||
|
|||||||
25
Makefile
25
Makefile
@ -36,14 +36,15 @@ targets:
|
|||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install: ## Install dependencies
|
install: ## Install dependencies
|
||||||
poetry install
|
uv add pyproject.toml
|
||||||
|
|
||||||
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: start
|
run: start
|
||||||
|
|
||||||
.PHONY: start
|
.PHONY: start
|
||||||
start: ## Starts the server
|
start: ## Starts the server
|
||||||
poetry run python run.py
|
python run.py
|
||||||
|
|
||||||
# Check, lint and format targets
|
# Check, lint and format targets
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
@ -53,28 +54,24 @@ check: check-format lint
|
|||||||
|
|
||||||
.PHONY: check-format
|
.PHONY: check-format
|
||||||
check-format: ## Dry-run code formatter
|
check-format: ## Dry-run code formatter
|
||||||
poetry run black ./ --check
|
black ./ --check
|
||||||
poetry run isort ./ --profile black --check
|
isort ./ --profile black --check
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: ## Run ruff
|
lint: ## Run ruff
|
||||||
poetry run ruff check ./app
|
ruff check ./app
|
||||||
|
|
||||||
.PHONY: format
|
.PHONY: format
|
||||||
format: ## Run code formatter
|
format: ## Run code formatter
|
||||||
poetry run black ./
|
black ./
|
||||||
poetry run isort ./ --profile black
|
isort ./ --profile black
|
||||||
|
|
||||||
.PHONY: check-lockfile
|
|
||||||
check-lockfile: ## Compares lock file with pyproject.toml
|
|
||||||
poetry lock --check
|
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: ## Run the test suite
|
test: ## Run the test suite
|
||||||
$(eval include .env)
|
$(eval include .env)
|
||||||
$(eval export $(sh sed 's/=.*//' .env))
|
$(eval export $(sh sed 's/=.*//' .env))
|
||||||
|
pytest -vv -s --cache-clear ./
|
||||||
poetry run pytest -vv -s --cache-clear ./
|
|
||||||
|
|
||||||
.PHONY: clean-db
|
.PHONY: clean-db
|
||||||
clean-db: ## 删除migrations文件夹和db.sqlite3
|
clean-db: ## 删除migrations文件夹和db.sqlite3
|
||||||
@ -83,8 +80,8 @@ clean-db: ## 删除migrations文件夹和db.sqlite3
|
|||||||
|
|
||||||
.PHONY: migrate
|
.PHONY: migrate
|
||||||
migrate: ## 运行aerich migrate命令生成迁移文件
|
migrate: ## 运行aerich migrate命令生成迁移文件
|
||||||
poetry run aerich migrate
|
aerich migrate
|
||||||
|
|
||||||
.PHONY: upgrade
|
.PHONY: upgrade
|
||||||
upgrade: ## 运行aerich upgrade命令应用迁移
|
upgrade: ## 运行aerich upgrade命令应用迁移
|
||||||
poetry run aerich upgrade
|
aerich upgrade
|
||||||
37
README-en.md
37
README-en.md
@ -86,19 +86,48 @@ password:123456
|
|||||||
#### Backend
|
#### Backend
|
||||||
The backend service requires the following environment:
|
The backend service requires the following environment:
|
||||||
- Python 3.11
|
- Python 3.11
|
||||||
- [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)
|
|
||||||
|
|
||||||
|
#### Method 1 (Recommended): Install Dependencies with uv
|
||||||
|
1. Install uv
|
||||||
|
```sh
|
||||||
|
pip install uv
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create and activate virtual environment
|
||||||
|
```sh
|
||||||
|
uv venv
|
||||||
|
source .venv/bin/activate # Linux/Mac
|
||||||
|
# or
|
||||||
|
.\.venv\Scripts\activate # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Install dependencies
|
||||||
|
```sh
|
||||||
|
uv add pyproject.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Start the backend service
|
||||||
|
```sh
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Method 2: Install Dependencies with Pip
|
||||||
1. Create a Python virtual environment:
|
1. Create a Python virtual environment:
|
||||||
```sh
|
```sh
|
||||||
poetry shell
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate # Linux/Mac
|
||||||
|
# or
|
||||||
|
.\venv\Scripts\activate # Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install project dependencies:
|
2. Install project dependencies:
|
||||||
```sh
|
```sh
|
||||||
poetry install
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Start the backend service:
|
3. Start the backend service:
|
||||||
```sh
|
```sh
|
||||||
make run
|
python run.py
|
||||||
```
|
```
|
||||||
The backend service is now running, and you can visit http://localhost:9999/docs to view the API documentation.
|
The backend service is now running, and you can visit http://localhost:9999/docs to view the API documentation.
|
||||||
|
|
||||||
|
|||||||
50
README.md
50
README.md
@ -87,33 +87,49 @@ password:123456
|
|||||||
启动项目需要以下环境:
|
启动项目需要以下环境:
|
||||||
- Python 3.11
|
- Python 3.11
|
||||||
|
|
||||||
#### 方法一(推荐):[Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) 安装依赖
|
#### 方法一(推荐):使用 uv 安装依赖
|
||||||
|
1. 安装 uv
|
||||||
|
```sh
|
||||||
|
pip install uv
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 创建并激活虚拟环境
|
||||||
|
```sh
|
||||||
|
uv venv
|
||||||
|
source .venv/bin/activate # Linux/Mac
|
||||||
|
# 或
|
||||||
|
.\.venv\Scripts\activate # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 安装依赖
|
||||||
|
```sh
|
||||||
|
uv add pyproject.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 启动服务
|
||||||
|
```sh
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方法二:使用 Pip 安装依赖
|
||||||
1. 创建虚拟环境
|
1. 创建虚拟环境
|
||||||
```sh
|
```sh
|
||||||
poetry shell
|
python3 -m venv venv
|
||||||
```
|
|
||||||
2. 安装依赖
|
|
||||||
```sh
|
|
||||||
poetry install
|
|
||||||
```
|
|
||||||
3. 启动服务
|
|
||||||
```sh
|
|
||||||
make run
|
|
||||||
```
|
|
||||||
#### 方法二:Pip 安装依赖
|
|
||||||
1. 创建虚拟环境
|
|
||||||
```sh
|
|
||||||
python3.11 -m venv venv
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 激活虚拟环境
|
2. 激活虚拟环境
|
||||||
```sh
|
```sh
|
||||||
source venv/bin/activate
|
source venv/bin/activate # Linux/Mac
|
||||||
|
# 或
|
||||||
|
.\venv\Scripts\activate # Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 安装依赖
|
3. 安装依赖
|
||||||
```sh
|
```sh
|
||||||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
```
|
```
|
||||||
3. 启动服务
|
|
||||||
|
4. 启动服务
|
||||||
```sh
|
```sh
|
||||||
python run.py
|
python run.py
|
||||||
```
|
```
|
||||||
|
|||||||
@ -30,7 +30,7 @@ async def get_audit_log_list(
|
|||||||
if summary:
|
if summary:
|
||||||
q &= Q(summary__icontains=summary)
|
q &= Q(summary__icontains=summary)
|
||||||
if status:
|
if status:
|
||||||
q &= Q(status__icontains=status)
|
q &= Q(status=status)
|
||||||
if start_time and end_time:
|
if start_time and end_time:
|
||||||
q &= Q(created_at__range=[start_time, end_time])
|
q &= Q(created_at__range=[start_time, end_time])
|
||||||
elif start_time:
|
elif start_time:
|
||||||
|
|||||||
@ -43,6 +43,7 @@ def make_middlewares():
|
|||||||
HttpAuditLogMiddleware,
|
HttpAuditLogMiddleware,
|
||||||
methods=["GET", "POST", "PUT", "DELETE"],
|
methods=["GET", "POST", "PUT", "DELETE"],
|
||||||
exclude_paths=[
|
exclude_paths=[
|
||||||
|
"/api/v1/base/access_token",
|
||||||
"/docs",
|
"/docs",
|
||||||
"/openapi.json",
|
"/openapi.json",
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import json
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Any, AsyncGenerator
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
@ -45,10 +47,77 @@ class BackGroundTaskMiddleware(SimpleBaseMiddleware):
|
|||||||
|
|
||||||
|
|
||||||
class HttpAuditLogMiddleware(BaseHTTPMiddleware):
|
class HttpAuditLogMiddleware(BaseHTTPMiddleware):
|
||||||
def __init__(self, app, methods: list, exclude_paths: list):
|
def __init__(self, app, methods: list[str], exclude_paths: list[str]):
|
||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
self.methods = methods
|
self.methods = methods
|
||||||
self.exclude_paths = exclude_paths
|
self.exclude_paths = exclude_paths
|
||||||
|
self.audit_log_paths = ["/api/v1/auditlog/list"]
|
||||||
|
self.max_body_size = 1024 * 1024 # 1MB 响应体大小限制
|
||||||
|
|
||||||
|
async def get_request_args(self, request: Request) -> dict:
|
||||||
|
args = {}
|
||||||
|
# 获取查询参数
|
||||||
|
for key, value in request.query_params.items():
|
||||||
|
args[key] = value
|
||||||
|
|
||||||
|
# 获取请求体
|
||||||
|
if request.method in ["POST", "PUT", "PATCH"]:
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
args.update(body)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
try:
|
||||||
|
body = await request.form()
|
||||||
|
args.update(body)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
async def get_response_body(self, request: Request, response: Response) -> Any:
|
||||||
|
# 检查Content-Length
|
||||||
|
content_length = response.headers.get("content-length")
|
||||||
|
if content_length and int(content_length) > self.max_body_size:
|
||||||
|
return {"code": 0, "msg": "Response too large to log", "data": None}
|
||||||
|
|
||||||
|
if hasattr(response, "body"):
|
||||||
|
body = response.body
|
||||||
|
else:
|
||||||
|
body_chunks = []
|
||||||
|
async for chunk in response.body_iterator:
|
||||||
|
if not isinstance(chunk, bytes):
|
||||||
|
chunk = chunk.encode(response.charset)
|
||||||
|
body_chunks.append(chunk)
|
||||||
|
|
||||||
|
response.body_iterator = self._async_iter(body_chunks)
|
||||||
|
body = b"".join(body_chunks)
|
||||||
|
|
||||||
|
if any(request.url.path.startswith(path) for path in self.audit_log_paths):
|
||||||
|
try:
|
||||||
|
data = self.lenient_json(body)
|
||||||
|
# 只保留基本信息,去除详细的响应内容
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data.pop("response_body", None)
|
||||||
|
if "data" in data and isinstance(data["data"], list):
|
||||||
|
for item in data["data"]:
|
||||||
|
item.pop("response_body", None)
|
||||||
|
return data
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.lenient_json(body)
|
||||||
|
|
||||||
|
def lenient_json(self, v: Any) -> Any:
|
||||||
|
if isinstance(v, (str, bytes)):
|
||||||
|
try:
|
||||||
|
return json.loads(v)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
return v
|
||||||
|
|
||||||
|
async def _async_iter(self, items: list[bytes]) -> AsyncGenerator[bytes, None]:
|
||||||
|
for item in items:
|
||||||
|
yield item
|
||||||
|
|
||||||
async def get_request_log(self, request: Request, response: Response) -> dict:
|
async def get_request_log(self, request: Request, response: Response) -> dict:
|
||||||
"""
|
"""
|
||||||
@ -73,23 +142,29 @@ class HttpAuditLogMiddleware(BaseHTTPMiddleware):
|
|||||||
user_obj: User = await AuthControl.is_authed(token)
|
user_obj: User = await AuthControl.is_authed(token)
|
||||||
data["user_id"] = user_obj.id if user_obj else 0
|
data["user_id"] = user_obj.id if user_obj else 0
|
||||||
data["username"] = user_obj.username if user_obj else ""
|
data["username"] = user_obj.username if user_obj else ""
|
||||||
except Exception as e:
|
except Exception:
|
||||||
data["user_id"] = 0
|
data["user_id"] = 0
|
||||||
data["username"] = ""
|
data["username"] = ""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def before_request(self, request: Request):
|
async def before_request(self, request: Request):
|
||||||
pass
|
request_args = await self.get_request_args(request)
|
||||||
|
request.state.request_args = request_args
|
||||||
|
|
||||||
async def after_request(self, request: Request, response: Response, process_time: int):
|
async def after_request(self, request: Request, response: Response, process_time: int):
|
||||||
if request.method in self.methods: # 请求方法为配置的记录方法
|
if request.method in self.methods:
|
||||||
for path in self.exclude_paths:
|
for path in self.exclude_paths:
|
||||||
if re.search(path, request.url.path, re.I) is not None:
|
if re.search(path, request.url.path, re.I) is not None:
|
||||||
return
|
return
|
||||||
data: dict = await self.get_request_log(request=request, response=response)
|
data: dict = await self.get_request_log(request=request, response=response)
|
||||||
data["response_time"] = process_time # 响应时间
|
data["response_time"] = process_time
|
||||||
|
|
||||||
|
data["request_args"] = request.state.request_args
|
||||||
|
data["response_body"] = await self.get_response_body(request, response)
|
||||||
await AuditLog.create(**data)
|
await AuditLog.create(**data)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
||||||
start_time: datetime = datetime.now()
|
start_time: datetime = datetime.now()
|
||||||
await self.before_request(request)
|
await self.before_request(request)
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class Menu(BaseModel, TimestampMixin):
|
|||||||
icon = fields.CharField(max_length=100, null=True, description="菜单图标")
|
icon = fields.CharField(max_length=100, null=True, description="菜单图标")
|
||||||
path = fields.CharField(max_length=100, description="菜单路径", index=True)
|
path = fields.CharField(max_length=100, description="菜单路径", index=True)
|
||||||
order = fields.IntField(default=0, description="排序", index=True)
|
order = fields.IntField(default=0, description="排序", index=True)
|
||||||
parent_id = fields.IntField(default=0, max_length=10, description="父菜单ID", index=True)
|
parent_id = fields.IntField(default=0, description="父菜单ID", index=True)
|
||||||
is_hidden = fields.BooleanField(default=False, description="是否隐藏")
|
is_hidden = fields.BooleanField(default=False, description="是否隐藏")
|
||||||
component = fields.CharField(max_length=100, description="组件")
|
component = fields.CharField(max_length=100, description="组件")
|
||||||
keepalive = fields.BooleanField(default=True, description="存活")
|
keepalive = fields.BooleanField(default=True, description="存活")
|
||||||
@ -85,3 +85,5 @@ class AuditLog(BaseModel, TimestampMixin):
|
|||||||
path = fields.CharField(max_length=255, default="", description="请求路径", index=True)
|
path = fields.CharField(max_length=255, default="", description="请求路径", index=True)
|
||||||
status = fields.IntField(default=-1, description="状态码", index=True)
|
status = fields.IntField(default=-1, description="状态码", index=True)
|
||||||
response_time = fields.IntField(default=0, description="响应时间(单位ms)", index=True)
|
response_time = fields.IntField(default=0, description="响应时间(单位ms)", index=True)
|
||||||
|
request_args = fields.JSONField(null=True, description="请求参数")
|
||||||
|
response_body = fields.JSONField(null=True, description="返回数据")
|
||||||
|
|||||||
@ -10,8 +10,7 @@ class BaseApi(BaseModel):
|
|||||||
tags: str = Field(..., description="API标签", example="User")
|
tags: str = Field(..., description="API标签", example="User")
|
||||||
|
|
||||||
|
|
||||||
class ApiCreate(BaseApi):
|
class ApiCreate(BaseApi): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class ApiUpdate(BaseApi):
|
class ApiUpdate(BaseApi):
|
||||||
|
|||||||
@ -8,8 +8,7 @@ class BaseDept(BaseModel):
|
|||||||
parent_id: int = Field(0, description="父部门ID")
|
parent_id: int = Field(0, description="父部门ID")
|
||||||
|
|
||||||
|
|
||||||
class DeptCreate(BaseDept):
|
class DeptCreate(BaseDept): ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class DeptUpdate(BaseDept):
|
class DeptUpdate(BaseDept):
|
||||||
|
|||||||
110
pyproject.toml
110
pyproject.toml
@ -1,55 +1,87 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "vue-fastapi-admin"
|
name = "vue-fastapi-admin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Vue Fastapi admin"
|
description = "Vue Fastapi admin"
|
||||||
authors = ["mizhexiaoxiao <mizhexiaoxiao@gmail.com>"]
|
authors = [
|
||||||
readme = "README.md"
|
{name = "mizhexiaoxiao", email = "mizhexiaoxiao@gmail.com"},
|
||||||
|
]
|
||||||
[tool.poetry.dependencies]
|
requires-python = ">=3.11"
|
||||||
python = "^3.11"
|
dependencies = [
|
||||||
fastapi = "0.111.0"
|
"fastapi==0.111.0",
|
||||||
tortoise-orm = "^0.20.1"
|
"tortoise-orm==0.23.0",
|
||||||
pydantic = "^2.7.1"
|
"pydantic==2.10.5",
|
||||||
email-validator = "^2.0.0.post2"
|
"email-validator==2.2.0",
|
||||||
passlib = "^1.7.4"
|
"passlib==1.7.4",
|
||||||
pyjwt = "^2.7.0"
|
"pyjwt==2.10.1",
|
||||||
black = "^23.7.0"
|
"black==24.10.0",
|
||||||
isort = "^5.12.0"
|
"isort==5.13.2",
|
||||||
ruff = "^0.0.281"
|
"ruff==0.9.1",
|
||||||
loguru = "^0.7.0"
|
"loguru==0.7.3",
|
||||||
pydantic-settings = "^2.0.3"
|
"pydantic-settings==2.7.1",
|
||||||
argon2-cffi = "^23.1.0"
|
"argon2-cffi==23.1.0",
|
||||||
pydantic-core = "^2.18.2"
|
"pydantic-core==2.27.2",
|
||||||
annotated-types = "^0.6.0"
|
"annotated-types==0.7.0",
|
||||||
setuptools = "^70.0.0"
|
"setuptools==75.8.0",
|
||||||
uvicorn = "^0.30.1"
|
"uvicorn==0.34.0",
|
||||||
h11 = "^0.14.0"
|
"h11==0.14.0",
|
||||||
aerich = "^0.7.2"
|
"aerich==0.8.1",
|
||||||
|
"aiosqlite==0.20.0",
|
||||||
|
"anyio==4.8.0",
|
||||||
|
"argon2-cffi-bindings==21.2.0",
|
||||||
|
"asyncclick==8.1.8",
|
||||||
|
"certifi==2024.12.14",
|
||||||
|
"cffi==1.17.1",
|
||||||
|
"click==8.1.8",
|
||||||
|
"dictdiffer==0.9.0",
|
||||||
|
"dnspython==2.7.0",
|
||||||
|
"fastapi-cli==0.0.7",
|
||||||
|
"httpcore==1.0.7",
|
||||||
|
"httptools==0.6.4",
|
||||||
|
"httpx==0.28.1",
|
||||||
|
"idna==3.10",
|
||||||
|
"iso8601==2.1.0",
|
||||||
|
"jinja2==3.1.5",
|
||||||
|
"markdown-it-py==3.0.0",
|
||||||
|
"markupsafe==3.0.2",
|
||||||
|
"mdurl==0.1.2",
|
||||||
|
"mypy-extensions==1.0.0",
|
||||||
|
"orjson==3.10.14",
|
||||||
|
"packaging==24.2",
|
||||||
|
"pathspec==0.12.1",
|
||||||
|
"platformdirs==4.3.6",
|
||||||
|
"pycparser==2.22",
|
||||||
|
"pygments==2.19.1",
|
||||||
|
"pypika-tortoise==0.3.2",
|
||||||
|
"python-dotenv==1.0.1",
|
||||||
|
"python-multipart==0.0.20",
|
||||||
|
"pytz==2024.2",
|
||||||
|
"pyyaml==6.0.2",
|
||||||
|
"rich==13.9.4",
|
||||||
|
"rich-toolkit==0.13.2",
|
||||||
|
"shellingham==1.5.4",
|
||||||
|
"sniffio==1.3.1",
|
||||||
|
"starlette==0.37.2",
|
||||||
|
"typer==0.15.1",
|
||||||
|
"typing-extensions==4.12.2",
|
||||||
|
"ujson==5.10.0",
|
||||||
|
"uvloop==0.21.0",
|
||||||
|
"watchfiles==1.0.4",
|
||||||
|
"websockets==14.1",
|
||||||
|
"pyproject-toml>=0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
target-version = ["py310", "py311"]
|
target-version = ["py310", "py311"]
|
||||||
|
|
||||||
[[tool.poetry.source]]
|
|
||||||
name = "tsinghua"
|
|
||||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
|
|
||||||
priority = "primary"
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
extend-select = [
|
lint.extend-select = []
|
||||||
# "I", # isort
|
lint.ignore = [
|
||||||
# "B", # flake8-bugbear
|
|
||||||
# "C4", # flake8-comprehensions
|
|
||||||
# "PGH", # pygrep-hooks
|
|
||||||
# "RUF", # ruff
|
|
||||||
# "W", # pycodestyle
|
|
||||||
# "YTT", # flake8-2020
|
|
||||||
]
|
|
||||||
ignore = [
|
|
||||||
"F403",
|
"F403",
|
||||||
"F405",
|
"F405",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.aerich]
|
[tool.aerich]
|
||||||
tortoise_orm = "app.settings.TORTOISE_ORM"
|
tortoise_orm = "app.settings.TORTOISE_ORM"
|
||||||
location = "./migrations"
|
location = "./migrations"
|
||||||
|
|||||||
@ -1,60 +1,60 @@
|
|||||||
aerich==0.7.2
|
aerich==0.8.1
|
||||||
aiosqlite==0.17.0
|
aiosqlite==0.20.0
|
||||||
annotated-types==0.6.0
|
annotated-types==0.7.0
|
||||||
anyio==4.4.0
|
anyio==4.8.0
|
||||||
argon2-cffi==23.1.0
|
argon2-cffi==23.1.0
|
||||||
argon2-cffi-bindings==21.2.0
|
argon2-cffi-bindings==21.2.0
|
||||||
black==23.12.1
|
asyncclick==8.1.8
|
||||||
certifi==2024.7.4
|
black==24.10.0
|
||||||
cffi==1.17.0
|
certifi==2024.12.14
|
||||||
click==8.1.7
|
cffi==1.17.1
|
||||||
|
click==8.1.8
|
||||||
dictdiffer==0.9.0
|
dictdiffer==0.9.0
|
||||||
dnspython==2.6.1
|
dnspython==2.7.0
|
||||||
email_validator==2.2.0
|
email-validator==2.2.0
|
||||||
fastapi==0.111.0
|
fastapi==0.111.0
|
||||||
fastapi-cli==0.0.5
|
fastapi-cli==0.0.7
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httpcore==1.0.5
|
httpcore==1.0.7
|
||||||
httptools==0.6.1
|
httptools==0.6.4
|
||||||
httpx==0.27.0
|
httpx==0.28.1
|
||||||
idna==3.8
|
idna==3.10
|
||||||
iso8601==1.1.0
|
iso8601==2.1.0
|
||||||
isort==5.13.2
|
isort==5.13.2
|
||||||
Jinja2==3.1.4
|
jinja2==3.1.5
|
||||||
loguru==0.7.2
|
loguru==0.7.3
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
MarkupSafe==2.1.5
|
markupsafe==3.0.2
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
mypy-extensions==1.0.0
|
mypy-extensions==1.0.0
|
||||||
orjson==3.10.7
|
orjson==3.10.14
|
||||||
packaging==24.1
|
packaging==24.2
|
||||||
passlib==1.7.4
|
passlib==1.7.4
|
||||||
pathspec==0.12.1
|
pathspec==0.12.1
|
||||||
platformdirs==4.2.2
|
platformdirs==4.3.6
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pydantic==2.9.0b1
|
pydantic==2.10.5
|
||||||
pydantic-settings==2.4.0
|
pydantic-core==2.27.2
|
||||||
pydantic_core==2.23.0
|
pydantic-settings==2.7.1
|
||||||
Pygments==2.18.0
|
pygments==2.19.1
|
||||||
PyJWT==2.9.0
|
pyjwt==2.10.1
|
||||||
pypika-tortoise==0.1.6
|
pypika-tortoise==0.3.2
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
python-multipart==0.0.9
|
python-multipart==0.0.20
|
||||||
pytz==2024.1
|
pytz==2024.2
|
||||||
PyYAML==6.0.2
|
pyyaml==6.0.2
|
||||||
rich==13.8.0
|
rich==13.9.4
|
||||||
ruff==0.0.281
|
rich-toolkit==0.13.2
|
||||||
setuptools==70.3.0
|
ruff==0.9.1
|
||||||
|
setuptools==75.8.0
|
||||||
shellingham==1.5.4
|
shellingham==1.5.4
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
starlette==0.37.2
|
starlette==0.37.2
|
||||||
tomlkit==0.13.2
|
tortoise-orm==0.23.0
|
||||||
tortoise-orm==0.20.1
|
typer==0.15.1
|
||||||
typer==0.12.5
|
typing-extensions==4.12.2
|
||||||
typing_extensions==4.12.2
|
|
||||||
tzdata==2024.1
|
|
||||||
ujson==5.10.0
|
ujson==5.10.0
|
||||||
uvicorn==0.30.6
|
uvicorn==0.34.0
|
||||||
uvloop==0.20.0
|
uvloop==0.21.0; sys_platform != 'win32'
|
||||||
watchfiles==0.23.0
|
watchfiles==1.0.4
|
||||||
websockets==13.0
|
websockets==14.1
|
||||||
|
|||||||
11
run.py
11
run.py
@ -1,4 +1,13 @@
|
|||||||
import uvicorn
|
import uvicorn
|
||||||
|
from uvicorn.config import LOGGING_CONFIG
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run("app:app", host="0.0.0.0", port=9999, reload=True, log_config="uvicorn_loggin_config.json")
|
# 修改默认日志配置
|
||||||
|
LOGGING_CONFIG["formatters"]["default"]["fmt"] = "%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
LOGGING_CONFIG["formatters"]["default"]["datefmt"] = "%Y-%m-%d %H:%M:%S"
|
||||||
|
LOGGING_CONFIG["formatters"]["access"][
|
||||||
|
"fmt"
|
||||||
|
] = '%(asctime)s - %(levelname)s - %(client_addr)s - "%(request_line)s" %(status_code)s'
|
||||||
|
LOGGING_CONFIG["formatters"]["access"]["datefmt"] = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
uvicorn.run("app:app", host="0.0.0.0", port=9988, reload=True, log_config=LOGGING_CONFIG)
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"disable_existing_loggers": false,
|
|
||||||
"formatters": {
|
|
||||||
"default": {
|
|
||||||
"()": "uvicorn.logging.DefaultFormatter",
|
|
||||||
"fmt": "%(levelprefix)s %(message)s",
|
|
||||||
"use_colors": null
|
|
||||||
},
|
|
||||||
"access": {
|
|
||||||
"()": "uvicorn.logging.AccessFormatter",
|
|
||||||
"fmt": "%(asctime)s - %(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers": {
|
|
||||||
"default": {
|
|
||||||
"formatter": "default",
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stderr"
|
|
||||||
},
|
|
||||||
"access": {
|
|
||||||
"formatter": "access",
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stdout"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"uvicorn": {
|
|
||||||
"handlers": [
|
|
||||||
"default"
|
|
||||||
],
|
|
||||||
"level": "INFO"
|
|
||||||
},
|
|
||||||
"uvicorn.error": {
|
|
||||||
"level": "INFO"
|
|
||||||
},
|
|
||||||
"uvicorn.access": {
|
|
||||||
"handlers": [
|
|
||||||
"access"
|
|
||||||
],
|
|
||||||
"level": "INFO",
|
|
||||||
"propagate": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { NInput, NSelect } from 'naive-ui'
|
import { NInput, NSelect, NPopover } from 'naive-ui'
|
||||||
|
import TheIcon from '@/components/icon/TheIcon.vue'
|
||||||
|
|
||||||
import CommonPage from '@/components/page/CommonPage.vue'
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
||||||
@ -78,6 +79,16 @@ const methodOptions = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function formatJSON(data) {
|
||||||
|
try {
|
||||||
|
return typeof data === 'string'
|
||||||
|
? JSON.stringify(JSON.parse(data), null, 2)
|
||||||
|
: JSON.stringify(data, null, 2)
|
||||||
|
} catch (e) {
|
||||||
|
return data || '无数据'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '用户名称',
|
title: '用户名称',
|
||||||
@ -121,6 +132,62 @@ const columns = [
|
|||||||
width: 'auto',
|
width: 'auto',
|
||||||
ellipsis: { tooltip: true },
|
ellipsis: { tooltip: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '请求体',
|
||||||
|
key: 'request_body',
|
||||||
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
NPopover,
|
||||||
|
{
|
||||||
|
trigger: 'hover',
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trigger: () =>
|
||||||
|
h('div', { style: 'cursor: pointer;' }, [h(TheIcon, { icon: 'carbon:data-view' })]),
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
'pre',
|
||||||
|
{
|
||||||
|
style:
|
||||||
|
'max-height: 400px; overflow: auto; background-color: #f5f5f5; padding: 8px; border-radius: 4px;',
|
||||||
|
},
|
||||||
|
formatJSON(row.request_args)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '响应体',
|
||||||
|
key: 'response_body',
|
||||||
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
NPopover,
|
||||||
|
{
|
||||||
|
trigger: 'hover',
|
||||||
|
placement: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trigger: () =>
|
||||||
|
h('div', { style: 'cursor: pointer;' }, [h(TheIcon, { icon: 'carbon:data-view' })]),
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
'pre',
|
||||||
|
{
|
||||||
|
style:
|
||||||
|
'max-height: 400px; overflow: auto; background-color: #f5f5f5; padding: 8px; border-radius: 4px;',
|
||||||
|
},
|
||||||
|
formatJSON(row.response_body)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '响应时间(s)',
|
title: '响应时间(s)',
|
||||||
key: 'response_time',
|
key: 'response_time',
|
||||||
@ -203,7 +270,7 @@ const columns = [
|
|||||||
@keypress.enter="$table?.handleSearch()"
|
@keypress.enter="$table?.handleSearch()"
|
||||||
/>
|
/>
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
<QueryBarItem label="创建时间" :label-width="70">
|
<QueryBarItem label="操作时间" :label-width="70">
|
||||||
<NDatePicker
|
<NDatePicker
|
||||||
v-model:value="datetimeRange"
|
v-model:value="datetimeRange"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user