update
This commit is contained in:
parent
43c93ec7d8
commit
01ca146205
@ -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
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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="旧密码验证错误!")
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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="新密码")
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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
6144
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user