Skip to content

Commit 564ee49

Browse files
committed
feat:asyncsql
1 parent 42f5ee0 commit 564ee49

12 files changed

+295
-103
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/test/
22
/.venv/
3+
uploads/*
4+
log/*

api/adminapi.py

+45-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from fastapi import APIRouter, Depends, HTTPException
2+
from slowapi import Limiter
3+
from slowapi.util import get_remote_address
24
from sqlalchemy.orm import Session
35
from starlette import status
46

@@ -9,6 +11,15 @@
911

1012
adminapp = APIRouter()
1113

14+
Allow_register: bool = True
15+
16+
17+
def get_is_Allow_register():
18+
return Allow_register
19+
20+
21+
limiter = Limiter(key_func=get_remote_address, enabled=True)
22+
1223

1324
async def get_admin(current_user: TokenData = Depends(get_current_user)):
1425
if current_user.gid == 1:
@@ -17,7 +28,38 @@ async def get_admin(current_user: TokenData = Depends(get_current_user)):
1728
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='非管理员')
1829

1930

20-
@adminapp.get("/test", response_model=list[UserOut | None])
21-
async def alluserinfo(session: Session = Depends(get_session), current_admin=Depends(get_admin)):
22-
userlist: list[UserOut | None] = crud.get_all_user(session=session)
31+
@adminapp.get("/alluser", response_model=list[UserOut | None], dependencies=[Depends(get_admin)])
32+
async def alluserinfo(session: Session = Depends(get_session)):
33+
userlist: list[UserOut | None] = await crud.get_all_user(session=session)
2334
return userlist
35+
36+
37+
@adminapp.get("/is_allow_register", response_model=bool)
38+
async def is_allow_register():
39+
return Allow_register
40+
41+
42+
@adminapp.get("/is_limiter", response_model=bool)
43+
async def is_limiter():
44+
return limiter.enabled
45+
46+
47+
@adminapp.put("/allow_register", dependencies=[Depends(get_admin)])
48+
async def allow_register(allow: bool):
49+
global Allow_register
50+
Allow_register = allow
51+
return '修改成功'
52+
53+
54+
@adminapp.put("/set_limiter", dependencies=[Depends(get_admin)])
55+
async def set_limiter(allow: bool):
56+
limiter.enabled = allow
57+
if allow:
58+
return '开启限制'
59+
else:
60+
return '关闭限制'
61+
62+
63+
@adminapp.delete("/deleteuser")
64+
async def deleteuser(username, session: Session = Depends(get_session), current_admin=Depends(get_admin)):
65+
return await crud.delete_user(session=session, username=username)

api/userapi.py

+67-22
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
from fastapi import APIRouter, Depends, HTTPException, status, Query
1+
import hashlib
2+
import os
3+
4+
import aiofiles
5+
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile
26
from fastapi.security import OAuth2PasswordRequestForm
37
from pydantic.schema import timedelta
4-
from sqlalchemy.orm import Session
8+
from sqlalchemy.ext.asyncio import AsyncSession
9+
from starlette.requests import Request
510

611
from config import Config
712
from sql import crud
813
from sql.database import get_session
14+
from .adminapi import get_is_Allow_register, limiter
915
from .token import authenticate_user, create_access_token, get_current_user
10-
from .verifyModel import UserCreate, RegisterSuccess, UserOut, Token, TokenData, Userbase, UpdateSuccess, PubUserInfo
16+
from .verifyModel import UserCreate, RegisterSuccess, UserOut, Token, TokenData, Userbase, UpdateSuccess, PubUserInfo, \
17+
UploadSuccess
1118

1219
userapp = APIRouter()
1320

1421

1522
@userapp.post("/token", response_model=Token)
16-
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(),
17-
session: Session = Depends(get_session)):
23+
@limiter.limit(limit_value="5/minute")
24+
async def login_for_access_token(request: Request, form_data: OAuth2PasswordRequestForm = Depends(),
25+
session: AsyncSession = Depends(get_session)):
1826
user = await authenticate_user(session=session, username=form_data.username, password=form_data.password)
1927
if not user:
2028
raise HTTPException(
@@ -31,16 +39,22 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
3139

3240

3341
@userapp.post("/register", description='用户注册', response_model=RegisterSuccess)
34-
async def register(user_in: UserCreate, session: Session = Depends(get_session)):
35-
await crud.create_user(session, user_in)
36-
u = crud.findUser_by_name(session, user_in.username)
37-
return RegisterSuccess.from_userOut(userout=u, detail="注册成功", )
42+
@limiter.limit(limit_value="5/minute")
43+
async def register(request: Request, user_in: UserCreate, session: AsyncSession = Depends(get_session)):
44+
if get_is_Allow_register():
45+
await crud.create_user(session, user_in)
46+
u = await crud.findUser_by_name(session, user_in.username)
47+
return RegisterSuccess.from_userOut(userout=u, detail="注册成功", )
48+
else:
49+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='不允许注册')
3850

3951

4052
@userapp.get("/pubInfo", response_model=PubUserInfo)
53+
@limiter.limit(limit_value="10/minute")
4154
async def publish_user_info(
55+
request: Request,
4256
username: str = Query(default=..., max_length=30),
43-
session: Session = Depends(get_session)
57+
session: AsyncSession = Depends(get_session)
4458
):
4559
user = await crud.findPubUser_by_name(session, username)
4660
if user is not None:
@@ -50,7 +64,7 @@ async def publish_user_info(
5064

5165

5266
@userapp.get("/info", response_model=UserOut)
53-
async def userinfo(session: Session = Depends(get_session), current_user: TokenData = Depends(get_current_user)):
67+
async def userinfo(session: AsyncSession = Depends(get_session), current_user: TokenData = Depends(get_current_user)):
5468
user = await crud.findUser_by_name(session, current_user.username)
5569
if user is not None:
5670
return user
@@ -60,26 +74,57 @@ async def userinfo(session: Session = Depends(get_session), current_user: TokenD
6074

6175
@userapp.put("/update_username", response_model=UpdateSuccess)
6276
async def update_username(old_password: str, username_new: str,
63-
session: Session = Depends(get_session),
77+
session: AsyncSession = Depends(get_session),
6478
current_user: TokenData = Depends(get_current_user)):
65-
r = await crud.change_user_name(session,
66-
user_old=Userbase(username=current_user.username, password=old_password),
67-
username_new=username_new)
79+
try:
80+
user_old = Userbase(username=current_user.username, password=old_password)
81+
except Exception as e:
82+
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=str(e))
83+
r = await crud.change_user_name(session, user_old=user_old, username_new=username_new)
6884
return r
6985

7086

7187
@userapp.put("/update_password", response_model=UpdateSuccess)
7288
async def update_password(old_password: str, password_new: str,
73-
session: Session = Depends(get_session),
89+
session: AsyncSession = Depends(get_session),
7490
current_user: TokenData = Depends(get_current_user)):
75-
r = await crud.change_user_passwd(session,
76-
user_old=Userbase(username=current_user.username, password=old_password),
77-
password_new=password_new)
91+
try:
92+
user_old = Userbase(username=current_user.username, password=old_password)
93+
except Exception as e:
94+
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=str(e))
95+
r = await crud.change_user_passwd(session, user_old=user_old, password_new=password_new)
7896
return r
7997

8098

81-
@userapp.put("/update_avatar", response_model=UpdateSuccess)
82-
async def update_username(avatar_new: str, session: Session = Depends(get_session),
99+
@userapp.put("/update_avatar", response_model=UploadSuccess)
100+
@limiter.limit(limit_value="5/minute")
101+
async def update_username(request: Request, avatar_new: UploadFile, session: AsyncSession = Depends(get_session),
83102
current_user: TokenData = Depends(get_current_user)):
84-
r = await crud.change_user_avatar(session, username=current_user.username, new_avatar=avatar_new)
103+
fileinfo = await upload(avatar_new)
104+
r = await crud.change_user_avatar(session, username=current_user.username, fileinfo=fileinfo)
85105
return r
106+
107+
108+
@userapp.post("/upload_file/", response_model=UploadSuccess, dependencies=[Depends(get_current_user)])
109+
@limiter.limit(limit_value="5/minute")
110+
async def create_upload_file(request: Request, file: UploadFile):
111+
return await upload(file)
112+
113+
114+
async def upload(file: UploadFile):
115+
ext = os.path.splitext(file.filename)[1]
116+
if ext not in {".jpg", ".jpeg", ".png", ".gif"}:
117+
raise HTTPException(status_code=400, detail="Invalid file extension.")
118+
119+
data = await file.read()
120+
if len(data) > Config['MAX_FILE_SIZE_MB'] * 1048576:
121+
raise HTTPException(status_code=400, detail=f"File size = {round(len(data) / 1048576, 2)}MB exceeds the limit.")
122+
123+
md5 = hashlib.md5(data).hexdigest()
124+
filename = f"{md5}{ext}"
125+
file_path = os.path.join(os.path.dirname(__file__), "..", "uploads", filename)
126+
if os.path.exists(file_path):
127+
return UploadSuccess(filename=filename, content_type=file.content_type, detail="文件已存在")
128+
async with aiofiles.open(file_path, "wb") as buffer:
129+
await buffer.write(data)
130+
return UploadSuccess(filename=filename + ext, content_type=file.content_type, detail="上传成功")

api/verifyModel.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Optional
2+
from typing import Optional, Type
33

44
from pydantic import BaseModel, Field
55

@@ -47,7 +47,7 @@ def from_userOut(cls, userout: UserOut, detail: str):
4747

4848
class UpdateSuccess(RegisterSuccess):
4949
@classmethod
50-
def from_User(cls, user: User, detail: str):
50+
def from_User(cls, user: Type[User], detail: str):
5151
return cls(
5252
detail=detail, data=UserOut(
5353
id=user.id,
@@ -75,3 +75,10 @@ class TokenData(BaseModel):
7575
class AdminTokenData(BaseModel):
7676
username: str
7777
id: int
78+
79+
80+
class UploadSuccess(BaseModel):
81+
filename: str
82+
content_type: str
83+
detail: str
84+

app.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
import uvicorn
22
from fastapi import FastAPI
3+
from uvicorn.config import LOGGING_CONFIG
4+
35
from api.adminapi import adminapp
46
from api.postapi import postapp
57
from api.userapi import userapp
68
from config import Config
9+
from config.log_config import my_LOGGING_CONFIG
710

811
app = FastAPI(
912
title='api docs',
1013
version='1.0',
1114
description='接口文档'
1215
)
1316

17+
18+
@app.on_event("startup")
19+
async def startup():
20+
...
21+
22+
23+
@app.on_event("shutdown")
24+
async def shutdown():
25+
...
26+
27+
1428
app.include_router(userapp, prefix='/api/user', tags=['用户'])
1529
app.include_router(adminapp, prefix='/api/admin', tags=['管理员'])
1630
app.include_router(postapp, prefix='/api/post', tags=['post'])
1731

32+
log_cfg = my_LOGGING_CONFIG if Config['Development'] else LOGGING_CONFIG
33+
34+
1835
if __name__ == '__main__':
19-
c = Config["fastapi"]
36+
c = Config["uvicorn"]
2037
uvicorn.run(app='app:app',
2138
host=c["host"],
2239
port=c["port"],
23-
reload=c["reload"])
40+
reload=Config['Development'],
41+
workers=c['workers'],
42+
log_config=log_cfg,
43+
)

config.yaml

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
1-
# token密钥
2-
SECRET_KEY: "b7a9563b93f709f4caa6cf63be75e094faa9d288e8d36ca259f6f056c818166"
3-
ALGORITHM: "HS256"
4-
# token过期时间
5-
ACCESS_TOKEN_EXPIRE_MINUTES: 1440
6-
7-
# 默认管理员
8-
Default_Administrator: 'admin'
9-
# 默认密码
10-
Default_Passwd: 'nHYjlSm773RlLq'
1+
# 开发模式输出更多信息
2+
Development: true
113
# mysql数据库信息
124
databases:
135
host: 127.0.0.1
146
username: root
157
password: 123456
168
port: 3306
179
dbname: blog
18-
echo: true
1910

2011
# fastapi
21-
fastapi:
12+
uvicorn:
2213
host: 127.0.0.1
2314
port: 8000
24-
reload: true
15+
# 使用多个工作进程
16+
workers: 1
2517

18+
# token密钥
19+
SECRET_KEY: "b7a9563b93f709f4caa6cf63be75e094faa9d288e8d36ca259f6f056c818166"
20+
ALGORITHM: "HS256"
21+
# token过期时间
22+
ACCESS_TOKEN_EXPIRE_MINUTES: 1440
2623

24+
# 默认管理员
25+
Default_Administrator: 'admin'
26+
# 默认密码
27+
Default_Passwd: 'nHYjlSm773RlLq'
2728

29+
# 文件上传大小限制
30+
MAX_FILE_SIZE_MB: 5

config/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
import logging
12
import os
23
import yaml
34

45
with open(os.path.join(os.path.dirname(__file__), '..', 'config.yaml'), 'r', encoding='utf8') as f:
56
Config = yaml.safe_load(f)
67

8+
if Config['Development'] is False:
9+
logging.basicConfig()
10+
handler = logging.FileHandler('./log/sqlalchemy.log')
11+
handler.setLevel(logging.INFO)
12+
handler.setFormatter(logging.Formatter('%(levelname)s [%(asctime)s] - %(name)s - %(message)s'))
13+
logging.getLogger('sqlalchemy.engine').addHandler(handler)
14+
715
if __name__ == '__main__':
816
print(Config)

config/log_config.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
my_LOGGING_CONFIG = {
2+
"version": 1,
3+
"disable_existing_loggers": False,
4+
"formatters": {
5+
"default": {
6+
"()": "uvicorn.logging.DefaultFormatter",
7+
"fmt": "%(levelprefix)s [%(asctime)s] %(message)s",
8+
"use_colors": None,
9+
},
10+
"access": {
11+
"()": "uvicorn.logging.AccessFormatter",
12+
"fmt": '%(levelprefix)s [%(asctime)s] %(client_addr)s - "%(request_line)s" %(status_code)s',
13+
},
14+
},
15+
"handlers": {
16+
"default": {
17+
"formatter": "default",
18+
"class": "logging.handlers.TimedRotatingFileHandler",
19+
"filename": "./log/uvicorn.log"
20+
},
21+
"access": {
22+
"formatter": "access",
23+
"class": "logging.handlers.TimedRotatingFileHandler",
24+
"filename": "./log/uvicorn.log"
25+
26+
},
27+
},
28+
"loggers": {
29+
"": {"handlers": ["default"], "level": "INFO"},
30+
"uvicorn.error": {"level": "INFO"},
31+
"uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
32+
},
33+
}

requirements.txt

236 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)