This commit is contained in:
mizhexiaoxiao 2024-07-31 10:45:58 +08:00
parent 43c93ec7d8
commit 01ca146205
12 changed files with 3792 additions and 2518 deletions

View File

@ -2,8 +2,7 @@ FROM node:18.12.0-alpine3.16 as web
WORKDIR /opt/vue-fastapi-admin WORKDIR /opt/vue-fastapi-admin
COPY /web ./web COPY /web ./web
RUN cd /opt/vue-fastapi-admin/web && npm i -g pnpm --registry=https://registry.npmmirror.com \ RUN cd /opt/vue-fastapi-admin/web && npm i --registry=https://registry.npmmirror.com && npm run build
&& pnpm i --registry=https://registry.npmmirror.com && pnpm run build
FROM python:3.11-slim-bullseye FROM python:3.11-slim-bullseye

View File

@ -1,10 +1,9 @@
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from fastapi.routing import APIRoute
from tortoise.expressions import Q from tortoise.expressions import Q
from app.controllers.api import api_controller from app.controllers.api import api_controller
from app.log import logger
from app.models.admin import Api
from app.schemas import Success, SuccessExtra from app.schemas import Success, SuccessExtra
from app.schemas.apis import * from app.schemas.apis import *
@ -66,33 +65,5 @@ async def delete_api(
@router.post("/refresh", summary="刷新API列表") @router.post("/refresh", summary="刷新API列表")
async def refresh_api(): async def refresh_api():
from app import app await api_controller.refresh_api()
# 删除废弃API数据
all_api_list = []
for route in app.routes:
if isinstance(route, APIRoute):
all_api_list.append((list(route.methods)[0], route.path_format))
delete_api = []
for api in await Api.all():
if (api.method, api.path) not in all_api_list:
delete_api.append((api.method, api.path))
for item in delete_api:
method, path = item
logger.debug(f"API Deleted {method} {path}")
await Api.filter(method=method, path=path).delete()
for route in app.routes:
if isinstance(route, APIRoute):
method = list(route.methods)[0]
path = route.path_format
summary = route.summary
tags = list(route.tags)[0]
api_obj = await Api.filter(method=method, path=path).first()
if api_obj:
await api_obj.update_from_dict(dict(method=method, path=path, summary=summary, tags=tags)).save()
else:
logger.debug(f"API Created {method} {path}")
await Api.create(**dict(method=method, path=path, summary=summary, tags=tags))
return Success(msg="OK") return Success(msg="OK")

View File

@ -1,8 +1,8 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from fastapi import APIRouter from fastapi import APIRouter
from app.controllers.user import UserController, user_controller from app.controllers.user import user_controller
from app.core.ctx import CTX_USER_ID from app.core.ctx import CTX_USER_ID
from app.core.dependency import DependAuth from app.core.dependency import DependAuth
from app.models.admin import Api, Menu, Role, User from app.models.admin import Api, Menu, Role, User
@ -21,7 +21,7 @@ async def login_access_token(credentials: CredentialsSchema):
user: User = await user_controller.authenticate(credentials) user: User = await user_controller.authenticate(credentials)
await user_controller.update_last_login(user.id) await user_controller.update_last_login(user.id)
access_token_expires = timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) access_token_expires = timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES)
expire = datetime.utcnow() + access_token_expires expire = datetime.now(timezone.utc) + access_token_expires
data = JWTOut( data = JWTOut(
access_token=create_access_token( access_token=create_access_token(
@ -91,10 +91,10 @@ async def get_user_api():
return Success(data=apis) return Success(data=apis)
@router.post("/update_password", summary="更新用户密码", dependencies=[DependAuth]) @router.post("/update_password", summary="修改密码", dependencies=[DependAuth])
async def update_user_password(req_in: UpdatePassword): async def update_user_password(req_in: UpdatePassword):
user_controller = UserController() user_id = CTX_USER_ID.get()
user = await user_controller.get(req_in.id) user = await user_controller.get(user_id)
verified = verify_password(req_in.old_password, user.password) verified = verify_password(req_in.old_password, user.password)
if not verified: if not verified:
return Fail(msg="旧密码验证错误!") return Fail(msg="旧密码验证错误!")

View File

@ -1,12 +1,11 @@
import logging import logging
from fastapi import APIRouter, Query from fastapi import APIRouter, Body, Query
from fastapi.exceptions import HTTPException
from tortoise.expressions import Q from tortoise.expressions import Q
from app.controllers.dept import dept_controller from app.controllers.dept import dept_controller
from app.controllers.user import UserController from app.controllers.user import user_controller
from app.schemas.base import Success, SuccessExtra from app.schemas.base import Fail, Success, SuccessExtra
from app.schemas.users import * from app.schemas.users import *
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,7 +21,6 @@ async def list_user(
email: str = Query("", description="邮箱地址"), email: str = Query("", description="邮箱地址"),
dept_id: int = Query(None, description="部门ID"), dept_id: int = Query(None, description="部门ID"),
): ):
user_controller = UserController()
q = Q() q = Q()
if username: if username:
q &= Q(username__contains=username) q &= Q(username__contains=username)
@ -43,7 +41,6 @@ async def list_user(
async def get_user( async def get_user(
user_id: int = Query(..., description="用户ID"), user_id: int = Query(..., description="用户ID"),
): ):
user_controller = UserController()
user_obj = await user_controller.get(id=user_id) user_obj = await user_controller.get(id=user_id)
user_dict = await user_obj.to_dict(exclude_fields=["password"]) user_dict = await user_obj.to_dict(exclude_fields=["password"])
return Success(data=user_dict) return Success(data=user_dict)
@ -53,14 +50,10 @@ async def get_user(
async def create_user( async def create_user(
user_in: UserCreate, user_in: UserCreate,
): ):
user_controller = UserController()
user = await user_controller.get_by_email(user_in.email) user = await user_controller.get_by_email(user_in.email)
if user: if user:
raise HTTPException( return Fail(code=400, msg="The user with this email already exists in the system.")
status_code=400, new_user = await user_controller.create_user(obj_in=user_in)
detail="The user with this email already exists in the system.",
)
new_user = await user_controller.create(obj_in=user_in)
await user_controller.update_roles(new_user, user_in.role_ids) await user_controller.update_roles(new_user, user_in.role_ids)
return Success(msg="Created Successfully") return Success(msg="Created Successfully")
@ -69,8 +62,7 @@ async def create_user(
async def update_user( async def update_user(
user_in: UserUpdate, user_in: UserUpdate,
): ):
user_controller = UserController() user = await user_controller.update(id=user_in.id, obj_in=user_in)
user = await user_controller.update(obj_in=user_in)
await user_controller.update_roles(user, user_in.role_ids) await user_controller.update_roles(user, user_in.role_ids)
return Success(msg="Updated Successfully") return Success(msg="Updated Successfully")
@ -79,6 +71,11 @@ async def update_user(
async def delete_user( async def delete_user(
user_id: int = Query(..., description="用户ID"), user_id: int = Query(..., description="用户ID"),
): ):
user_controller = UserController()
await user_controller.remove(id=user_id) await user_controller.remove(id=user_id)
return Success(msg="Deleted Successfully") return Success(msg="Deleted Successfully")
@router.post("/reset_password", summary="重置密码")
async def reset_password(user_id: int = Body(..., description="用户ID")):
await user_controller.reset_password(user_id)
return Success(msg="密码已重置为123456")

View File

@ -1,6 +1,8 @@
from app.core.crud import CRUDBase from app.core.crud import CRUDBase
from app.models.admin import Api from app.models.admin import Api
from app.schemas.apis import ApiCreate, ApiUpdate from app.schemas.apis import ApiCreate, ApiUpdate
from fastapi.routing import APIRoute
from app.log import logger
class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]): class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]):
@ -8,4 +10,34 @@ class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]):
super().__init__(model=Api) super().__init__(model=Api)
async def refresh_api(self):
from app import app
# 删除废弃API数据
all_api_list = []
for route in app.routes:
if isinstance(route, APIRoute):
all_api_list.append((list(route.methods)[0], route.path_format))
delete_api = []
for api in await Api.all():
if (api.method, api.path) not in all_api_list:
delete_api.append((api.method, api.path))
for item in delete_api:
method, path = item
logger.debug(f"API Deleted {method} {path}")
await Api.filter(method=method, path=path).delete()
for route in app.routes:
if isinstance(route, APIRoute):
method = list(route.methods)[0]
path = route.path_format
summary = route.summary
tags = list(route.tags)[0]
api_obj = await Api.filter(method=method, path=path).first()
if api_obj:
await api_obj.update_from_dict(dict(method=method, path=path, summary=summary, tags=tags)).save()
else:
logger.debug(f"API Created {method} {path}")
await Api.create(**dict(method=method, path=path, summary=summary, tags=tags))
api_controller = ApiController() api_controller = ApiController()

View File

@ -22,14 +22,11 @@ class UserController(CRUDBase[User, UserCreate, UserUpdate]):
async def get_by_username(self, username: str) -> Optional[User]: async def get_by_username(self, username: str) -> Optional[User]:
return await self.model.filter(username=username).first() return await self.model.filter(username=username).first()
async def create(self, obj_in: UserCreate) -> User: async def create_user(self, obj_in: UserCreate) -> User:
obj_in.password = get_password_hash(password=obj_in.password) obj_in.password = get_password_hash(password=obj_in.password)
obj = await super().create(obj_in.create_dict()) obj = await self.create(obj_in)
return obj return obj
async def update(self, obj_in: UserUpdate) -> User:
return await super().update(id=obj_in.id, obj_in=obj_in)
async def update_last_login(self, id: int) -> None: async def update_last_login(self, id: int) -> None:
user = await self.model.get(id=id) user = await self.model.get(id=id)
user.last_login = datetime.now() user.last_login = datetime.now()
@ -52,5 +49,12 @@ class UserController(CRUDBase[User, UserCreate, UserUpdate]):
role_obj = await role_controller.get(id=role_id) role_obj = await role_controller.get(id=role_id)
await user.roles.add(role_obj) await user.roles.add(role_obj)
async def reset_password(self, user_id: int):
user_obj = await self.get(id=user_id)
if user_obj.is_superuser:
raise HTTPException(status_code=403, detail="不允许重置超级管理员密码")
user_obj.password = get_password_hash(password="123456")
await user_obj.save()
user_controller = UserController() user_controller = UserController()

View File

@ -52,7 +52,7 @@ def register_routers(app: FastAPI, prefix: str = "/api"):
async def init_superuser(): async def init_superuser():
user = await user_controller.model.exists() user = await user_controller.model.exists()
if not user: if not user:
await user_controller.create( await user_controller.create_user(
UserCreate( UserCreate(
username="admin", username="admin",
email="admin@admin.com", email="admin@admin.com",
@ -75,7 +75,7 @@ async def init_menus():
icon="carbon:gui-management", icon="carbon:gui-management",
is_hidden=False, is_hidden=False,
component="Layout", component="Layout",
keepalive=True, keepalive=False,
redirect="/system/user", redirect="/system/user",
) )
children_menu = [ children_menu = [
@ -88,7 +88,7 @@ async def init_menus():
icon="material-symbols:person-outline-rounded", icon="material-symbols:person-outline-rounded",
is_hidden=False, is_hidden=False,
component="/system/user", component="/system/user",
keepalive=True, keepalive=False,
), ),
Menu( Menu(
menu_type=MenuType.MENU, menu_type=MenuType.MENU,
@ -99,7 +99,7 @@ async def init_menus():
icon="carbon:user-role", icon="carbon:user-role",
is_hidden=False, is_hidden=False,
component="/system/role", component="/system/role",
keepalive=True, keepalive=False,
), ),
Menu( Menu(
menu_type=MenuType.MENU, menu_type=MenuType.MENU,
@ -110,7 +110,7 @@ async def init_menus():
icon="material-symbols:list-alt-outline", icon="material-symbols:list-alt-outline",
is_hidden=False, is_hidden=False,
component="/system/menu", component="/system/menu",
keepalive=True, keepalive=False,
), ),
Menu( Menu(
menu_type=MenuType.MENU, menu_type=MenuType.MENU,
@ -121,7 +121,7 @@ async def init_menus():
icon="ant-design:api-outlined", icon="ant-design:api-outlined",
is_hidden=False, is_hidden=False,
component="/system/api", component="/system/api",
keepalive=True, keepalive=False,
), ),
Menu( Menu(
menu_type=MenuType.MENU, menu_type=MenuType.MENU,
@ -132,7 +132,7 @@ async def init_menus():
icon="mingcute:department-line", icon="mingcute:department-line",
is_hidden=False, is_hidden=False,
component="/system/dept", component="/system/dept",
keepalive=True, keepalive=False,
), ),
] ]
await Menu.bulk_create(children_menu) await Menu.bulk_create(children_menu)
@ -145,7 +145,7 @@ async def init_menus():
icon="mdi-fan-speed-1", icon="mdi-fan-speed-1",
is_hidden=False, is_hidden=False,
component="Layout", component="Layout",
keepalive=True, keepalive=False,
redirect="", redirect="",
) )
await Menu.create( await Menu.create(
@ -157,5 +157,5 @@ async def init_menus():
icon="mdi-fan-speed-1", icon="mdi-fan-speed-1",
is_hidden=False, is_hidden=False,
component="/top-menu", component="/top-menu",
keepalive=True, keepalive=False,
) )

View File

@ -40,6 +40,5 @@ class UserUpdate(BaseModel):
class UpdatePassword(BaseModel): class UpdatePassword(BaseModel):
id: int = Field(description="用户ID")
old_password: str = Field(description="旧密码") old_password: str = Field(description="旧密码")
new_password: str = Field(description="新密码") new_password: str = Field(description="新密码")

View File

@ -36,7 +36,7 @@ class Settings(BaseSettings):
LOGS_ROOT: str = os.path.join(BASE_DIR, "app/logs") LOGS_ROOT: str = os.path.join(BASE_DIR, "app/logs")
SECRET_KEY: str = "3488a63e1765035d386f05409663f55c83bfae3b3c61a932744b20ad14244dcf" # openssl rand -hex 32 SECRET_KEY: str = "3488a63e1765035d386f05409663f55c83bfae3b3c61a932744b20ad14244dcf" # openssl rand -hex 32
JWT_ALGORITHM: str = "HS256" JWT_ALGORITHM: str = "HS256"
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 day JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 24 * 7 # 7 day
TORTOISE_ORM: dict = { TORTOISE_ORM: dict = {
"connections": { "connections": {
"sqlite": { "sqlite": {

View File

@ -12,7 +12,7 @@
"prettier": "npx prettier --write ." "prettier": "npx prettier --write ."
}, },
"dependencies": { "dependencies": {
"@iconify/json": "^2.2.101", "@iconify/json": "^2.2.228",
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@unocss/eslint-config": "^0.55.0", "@unocss/eslint-config": "^0.55.0",
"@vueuse/core": "^10.3.0", "@vueuse/core": "^10.3.0",

6144
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -74,7 +74,23 @@ const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
const loading = ref(false) const loading = ref(false)
const initQuery = { ...props.queryItems } const initQuery = { ...props.queryItems }
const tableData = ref([]) const tableData = ref([])
const pagination = reactive({ page: 1, page_size: 10 }) const pagination = reactive({
page: 1,
page_size: 10,
pageSizes: [10, 20, 50, 100],
showSizePicker: true,
prefix({ itemCount }) {
return `${itemCount}`
},
onChange: (page) => {
pagination.page = page
},
onUpdatePageSize: (pageSize) => {
pagination.page_size = pageSize
pagination.page = 1
handleQuery()
},
})
async function handleQuery() { async function handleQuery() {
try { try {
@ -90,7 +106,7 @@ async function handleQuery() {
...paginationParams, ...paginationParams,
}) })
tableData.value = data tableData.value = data
pagination.itemCount = total pagination.itemCount = total || 0
} catch (error) { } catch (error) {
tableData.value = [] tableData.value = []
pagination.itemCount = 0 pagination.itemCount = 0