项目初次提交
This commit is contained in:
0
apps/vadmin/__init__.py
Normal file
0
apps/vadmin/__init__.py
Normal file
0
apps/vadmin/analysis/__init__.py
Normal file
0
apps/vadmin/analysis/__init__.py
Normal file
84
apps/vadmin/analysis/views.py
Normal file
84
apps/vadmin/analysis/views.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/10/19 15:41
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from apps.vadmin.auth.utils.current import AllUserAuth
|
||||
from utils.response import SuccessResponse
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
import random
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 图表数据
|
||||
###########################################################
|
||||
@app.get("/random/number", summary="获取随机整数")
|
||||
async def get_random_number(auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(random.randint(500, 20000))
|
||||
|
||||
|
||||
@app.get("/banners", summary="轮播图")
|
||||
async def get_banners(auth: Auth = Depends(AllUserAuth())):
|
||||
data = [
|
||||
{
|
||||
"id": 1, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-14/1.jpg"
|
||||
},
|
||||
{
|
||||
"id": 2, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-09/banner1.png"
|
||||
},
|
||||
{
|
||||
"id": 3, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-09/banner3.png"
|
||||
},
|
||||
]
|
||||
return SuccessResponse(data)
|
||||
|
||||
|
||||
@app.get("/user/access/source", summary="用户来源")
|
||||
async def get_user_access_source(auth: Auth = Depends(AllUserAuth())):
|
||||
data = [
|
||||
{"value": 1000, "name": 'analysis.directAccess'},
|
||||
{"value": 310, "name": 'analysis.mailMarketing'},
|
||||
{"value": 234, "name": 'analysis.allianceAdvertising'},
|
||||
{"value": 135, "name": 'analysis.videoAdvertising'},
|
||||
{"value": 1548, "name": 'analysis.searchEngines'}
|
||||
]
|
||||
return SuccessResponse(data)
|
||||
|
||||
|
||||
@app.get("/weekly/user/activity", summary="每周用户活跃量")
|
||||
async def get_weekly_user_activity(auth: Auth = Depends(AllUserAuth())):
|
||||
data = [
|
||||
{"value": 13253, "name": 'analysis.monday'},
|
||||
{"value": 34235, "name": 'analysis.tuesday'},
|
||||
{"value": 26321, "name": 'analysis.wednesday'},
|
||||
{"value": 12340, "name": 'analysis.thursday'},
|
||||
{"value": 24643, "name": 'analysis.friday'},
|
||||
{"value": 1322, "name": 'analysis.saturday'},
|
||||
{"value": 1324, "name": 'analysis.sunday'}
|
||||
]
|
||||
return SuccessResponse(data)
|
||||
|
||||
|
||||
@app.get("/monthly/sales", summary="每月销售额")
|
||||
async def get_monthly_sales(auth: Auth = Depends(AllUserAuth())):
|
||||
data = [
|
||||
{"estimate": 100, "actual": 120, "name": 'analysis.january'},
|
||||
{"estimate": 120, "actual": 82, "name": 'analysis.february'},
|
||||
{"estimate": 161, "actual": 91, "name": 'analysis.march'},
|
||||
{"estimate": 134, "actual": 154, "name": 'analysis.april'},
|
||||
{"estimate": 105, "actual": 162, "name": 'analysis.may'},
|
||||
{"estimate": 160, "actual": 140, "name": 'analysis.june'},
|
||||
{"estimate": 165, "actual": 145, "name": 'analysis.july'},
|
||||
{"estimate": 114, "actual": 250, "name": 'analysis.august'},
|
||||
{"estimate": 163, "actual": 134, "name": 'analysis.september'},
|
||||
{"estimate": 185, "actual": 56, "name": 'analysis.october'},
|
||||
{"estimate": 118, "actual": 99, "name": 'analysis.november'},
|
||||
{"estimate": 123, "actual": 123, "name": 'analysis.december'}
|
||||
]
|
||||
return SuccessResponse(data)
|
0
apps/vadmin/auth/__init__.py
Normal file
0
apps/vadmin/auth/__init__.py
Normal file
1075
apps/vadmin/auth/crud.py
Normal file
1075
apps/vadmin/auth/crud.py
Normal file
File diff suppressed because it is too large
Load Diff
14
apps/vadmin/auth/models/__init__.py
Normal file
14
apps/vadmin/auth/models/__init__.py
Normal file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : __init__.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
|
||||
from .m2m import vadmin_auth_user_roles, vadmin_auth_role_menus, vadmin_auth_user_depts, vadmin_auth_role_depts
|
||||
from .menu import VadminMenu
|
||||
from .role import VadminRole
|
||||
from .user import VadminUser
|
||||
from .dept import VadminDept
|
31
apps/vadmin/auth/models/dept.py
Normal file
31
apps/vadmin/auth/models/dept.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/10/23 13:41
|
||||
# @File : dept.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 部门模型
|
||||
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Boolean, Integer, ForeignKey
|
||||
|
||||
|
||||
class VadminDept(BaseModel):
|
||||
__tablename__ = "vadmin_auth_dept"
|
||||
__table_args__ = ({'comment': '部门表'})
|
||||
|
||||
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="部门名称")
|
||||
dept_key: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="部门标识")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||
order: Mapped[int | None] = mapped_column(Integer, comment="显示排序")
|
||||
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
|
||||
owner: Mapped[str | None] = mapped_column(String(255), comment="负责人")
|
||||
phone: Mapped[str | None] = mapped_column(String(255), comment="联系电话")
|
||||
email: Mapped[str | None] = mapped_column(String(255), comment="邮箱")
|
||||
|
||||
parent_id: Mapped[int | None] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_auth_dept.id", ondelete='CASCADE'),
|
||||
comment="上级部门"
|
||||
)
|
41
apps/vadmin/auth/models/m2m.py
Normal file
41
apps/vadmin/auth/models/m2m.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : m2m.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 关联中间表
|
||||
|
||||
from db.db_base import Base
|
||||
from sqlalchemy import ForeignKey, Column, Table, Integer
|
||||
|
||||
|
||||
vadmin_auth_user_roles = Table(
|
||||
"vadmin_auth_user_roles",
|
||||
Base.metadata,
|
||||
Column("user_id", Integer, ForeignKey("vadmin_auth_user.id", ondelete="CASCADE")),
|
||||
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
|
||||
vadmin_auth_role_menus = Table(
|
||||
"vadmin_auth_role_menus",
|
||||
Base.metadata,
|
||||
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
|
||||
Column("menu_id", Integer, ForeignKey("vadmin_auth_menu.id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
vadmin_auth_user_depts = Table(
|
||||
"vadmin_auth_user_depts",
|
||||
Base.metadata,
|
||||
Column("user_id", Integer, ForeignKey("vadmin_auth_user.id", ondelete="CASCADE")),
|
||||
Column("dept_id", Integer, ForeignKey("vadmin_auth_dept.id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
vadmin_auth_role_depts = Table(
|
||||
"vadmin_auth_role_depts",
|
||||
Base.metadata,
|
||||
Column("role_id", Integer, ForeignKey("vadmin_auth_role.id", ondelete="CASCADE")),
|
||||
Column("dept_id", Integer, ForeignKey("vadmin_auth_dept.id", ondelete="CASCADE")),
|
||||
)
|
||||
|
67
apps/vadmin/auth/models/menu.py
Normal file
67
apps/vadmin/auth/models/menu.py
Normal file
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : menu.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 菜单模型
|
||||
|
||||
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Boolean, Integer, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
|
||||
class VadminMenu(BaseModel):
|
||||
__tablename__ = "vadmin_auth_menu"
|
||||
__table_args__ = ({'comment': '菜单表'})
|
||||
|
||||
title: Mapped[str] = mapped_column(String(50), comment="名称")
|
||||
icon: Mapped[str | None] = mapped_column(String(50), comment="菜单图标")
|
||||
redirect: Mapped[str | None] = mapped_column(String(100), comment="重定向地址")
|
||||
component: Mapped[str | None] = mapped_column(String(255), comment="前端组件地址")
|
||||
path: Mapped[str | None] = mapped_column(String(50), comment="前端路由地址")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||
hidden: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否隐藏")
|
||||
order: Mapped[int] = mapped_column(Integer, comment="排序")
|
||||
menu_type: Mapped[str] = mapped_column(String(8), comment="菜单类型")
|
||||
parent_id: Mapped[int | None] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_auth_menu.id", ondelete='CASCADE'),
|
||||
comment="父菜单"
|
||||
)
|
||||
perms: Mapped[str | None] = mapped_column(String(50), comment="权限标识", unique=False, index=True)
|
||||
|
||||
"""以下属性主要用于补全前端路由属性,"""
|
||||
noCache: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
comment="如果设置为true,则不会被 <keep-alive> 缓存(默认 false)",
|
||||
default=False
|
||||
)
|
||||
breadcrumb: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
comment="如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)",
|
||||
default=True
|
||||
)
|
||||
affix: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
comment="如果设置为true,则会一直固定在tag项中(默认 false)",
|
||||
default=False
|
||||
)
|
||||
noTagsView: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
comment="如果设置为true,则不会出现在tag中(默认 false)",
|
||||
default=False
|
||||
)
|
||||
canTo: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
comment="设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)",
|
||||
default=False
|
||||
)
|
||||
alwaysShow: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
comment="""当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,
|
||||
只有一个时,会将那个子路由当做根路由显示在侧边栏,若你想不管路由下面的 children 声明的个数都显示你的根路由,
|
||||
你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由(默认 true)""",
|
||||
default=True
|
||||
)
|
30
apps/vadmin/auth/models/role.py
Normal file
30
apps/vadmin/auth/models/role.py
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : role.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 角色模型
|
||||
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Boolean, Integer
|
||||
from .menu import VadminMenu
|
||||
from .dept import VadminDept
|
||||
from .m2m import vadmin_auth_role_menus, vadmin_auth_role_depts
|
||||
|
||||
|
||||
class VadminRole(BaseModel):
|
||||
__tablename__ = "vadmin_auth_role"
|
||||
__table_args__ = ({'comment': '角色表'})
|
||||
|
||||
name: Mapped[str] = mapped_column(String(50), index=True, comment="名称")
|
||||
role_key: Mapped[str] = mapped_column(String(50), index=True, comment="权限字符")
|
||||
data_range: Mapped[int] = mapped_column(Integer, default=4, comment="数据权限范围")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||
order: Mapped[int | None] = mapped_column(Integer, comment="排序")
|
||||
desc: Mapped[str | None] = mapped_column(String(255), comment="描述")
|
||||
is_admin: Mapped[bool] = mapped_column(Boolean, comment="是否为超级角色", default=False)
|
||||
|
||||
menus: Mapped[set[VadminMenu]] = relationship(secondary=vadmin_auth_role_menus)
|
||||
depts: Mapped[set[VadminDept]] = relationship(secondary=vadmin_auth_role_depts)
|
72
apps/vadmin/auth/models/user.py
Normal file
72
apps/vadmin/auth/models/user.py
Normal file
@ -0,0 +1,72 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : user.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 用户模型
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Boolean, DateTime
|
||||
from passlib.context import CryptContext
|
||||
from .role import VadminRole
|
||||
from .dept import VadminDept
|
||||
from .m2m import vadmin_auth_user_roles, vadmin_auth_user_depts
|
||||
|
||||
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||
|
||||
|
||||
class VadminUser(BaseModel):
|
||||
__tablename__ = "vadmin_auth_user"
|
||||
__table_args__ = ({'comment': '用户表'})
|
||||
|
||||
avatar: Mapped[str | None] = mapped_column(String(500), comment='头像')
|
||||
telephone: Mapped[str] = mapped_column(String(11), nullable=False, index=True, comment="手机号", unique=False)
|
||||
email: Mapped[str | None] = mapped_column(String(50), comment="邮箱地址")
|
||||
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="姓名")
|
||||
nickname: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="昵称")
|
||||
password: Mapped[str] = mapped_column(String(255), nullable=True, comment="密码")
|
||||
gender: Mapped[str | None] = mapped_column(String(8), nullable=True, comment="性别")
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可用")
|
||||
is_reset_password: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
default=False,
|
||||
comment="是否已经重置密码,没有重置的,登陆系统后必须重置密码"
|
||||
)
|
||||
last_ip: Mapped[str | None] = mapped_column(String(50), comment="最后一次登录IP")
|
||||
last_login: Mapped[datetime | None] = mapped_column(DateTime, comment="最近一次登录时间")
|
||||
is_staff: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否为工作人员")
|
||||
wx_server_openid: Mapped[str | None] = mapped_column(String(255), comment="服务端微信平台openid")
|
||||
is_wx_server_openid: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否已有服务端微信平台openid")
|
||||
|
||||
roles: Mapped[set[VadminRole]] = relationship(secondary=vadmin_auth_user_roles)
|
||||
depts: Mapped[set[VadminDept]] = relationship(secondary=vadmin_auth_user_depts)
|
||||
|
||||
@staticmethod
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""
|
||||
生成哈希密码
|
||||
:param password: 原始密码
|
||||
:return: 哈希密码
|
||||
"""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
@staticmethod
|
||||
def verify_password(password: str, hashed_password: str) -> bool:
|
||||
"""
|
||||
验证原始密码是否与哈希密码一致
|
||||
:param password: 原始密码
|
||||
:param hashed_password: 哈希密码
|
||||
:return:
|
||||
"""
|
||||
return pwd_context.verify(password, hashed_password)
|
||||
|
||||
def is_admin(self) -> bool:
|
||||
"""
|
||||
获取该用户是否拥有最高权限
|
||||
以最高权限为准
|
||||
:return:
|
||||
"""
|
||||
return any([i.is_admin for i in self.roles])
|
3
apps/vadmin/auth/params/__init__.py
Normal file
3
apps/vadmin/auth/params/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .user import UserParams
|
||||
from .role import RoleParams
|
||||
from .dept import DeptParams
|
31
apps/vadmin/auth/params/dept.py
Normal file
31
apps/vadmin/auth/params/dept.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/12/18 10:19
|
||||
# @File : dept.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends, Query
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class DeptParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str | None = Query(None, title="部门名称"),
|
||||
dept_key: str | None = Query(None, title="部门标识"),
|
||||
disabled: bool | None = Query(None, title="是否禁用"),
|
||||
params: Paging = Depends()
|
||||
):
|
||||
super().__init__(params)
|
||||
self.name = ("like", name)
|
||||
self.dept_key = ("like", dept_key)
|
||||
self.disabled = disabled
|
31
apps/vadmin/auth/params/role.py
Normal file
31
apps/vadmin/auth/params/role.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : role.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends, Query
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class RoleParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str | None = Query(None, title="角色名称"),
|
||||
role_key: str | None = Query(None, title="权限字符"),
|
||||
disabled: bool | None = Query(None, title="是否禁用"),
|
||||
params: Paging = Depends()
|
||||
):
|
||||
super().__init__(params)
|
||||
self.name = ("like", name)
|
||||
self.role_key = ("like", role_key)
|
||||
self.disabled = disabled
|
37
apps/vadmin/auth/params/user.py
Normal file
37
apps/vadmin/auth/params/user.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : user.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends, Query
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class UserParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str | None = Query(None, title="用户名称"),
|
||||
telephone: str | None = Query(None, title="手机号"),
|
||||
email: str | None = Query(None, title="邮箱"),
|
||||
is_active: bool | None = Query(None, title="是否可用"),
|
||||
is_staff: bool | None = Query(None, title="是否为工作人员"),
|
||||
params: Paging = Depends()
|
||||
):
|
||||
super().__init__(params)
|
||||
self.name = ("like", name)
|
||||
self.telephone = ("like", telephone)
|
||||
self.email = ("like", email)
|
||||
self.is_active = is_active
|
||||
self.is_staff = is_staff
|
||||
|
||||
|
4
apps/vadmin/auth/schemas/__init__.py
Normal file
4
apps/vadmin/auth/schemas/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd, UserUpdateBaseInfo, UserPasswordOut
|
||||
from .role import Role, RoleOut, RoleIn, RoleOptionsOut, RoleSimpleOut
|
||||
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, MenuTreeListOut
|
||||
from .dept import Dept, DeptSimpleOut, DeptTreeListOut
|
39
apps/vadmin/auth/schemas/dept.py
Normal file
39
apps/vadmin/auth/schemas/dept.py
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/10/25 12:19
|
||||
# @File : dept.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class Dept(BaseModel):
|
||||
name: str
|
||||
dept_key: str
|
||||
disabled: bool = False
|
||||
order: int | None = None
|
||||
desc: str | None = None
|
||||
owner: str | None = None
|
||||
phone: str | None = None
|
||||
email: str | None = None
|
||||
|
||||
parent_id: int | None = None
|
||||
|
||||
|
||||
class DeptSimpleOut(Dept):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
||||
|
||||
class DeptTreeListOut(DeptSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
children: list[dict] = []
|
||||
|
66
apps/vadmin/auth/schemas/menu.py
Normal file
66
apps/vadmin/auth/schemas/menu.py
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : role.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class Menu(BaseModel):
|
||||
title: str
|
||||
icon: str | None = None
|
||||
component: str | None = None
|
||||
redirect: str | None = None
|
||||
path: str | None = None
|
||||
disabled: bool = False
|
||||
hidden: bool = False
|
||||
order: int | None = None
|
||||
perms: str | None = None
|
||||
parent_id: int | None = None
|
||||
menu_type: str
|
||||
alwaysShow: bool | None = True
|
||||
noCache: bool | None = False
|
||||
|
||||
|
||||
class MenuSimpleOut(Menu):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
||||
|
||||
class Meta(BaseModel):
|
||||
title: str
|
||||
icon: str | None = None
|
||||
hidden: bool = False
|
||||
noCache: bool | None = False
|
||||
breadcrumb: bool | None = True
|
||||
affix: bool | None = False
|
||||
noTagsView: bool | None = False
|
||||
canTo: bool | None = False
|
||||
alwaysShow: bool | None = True
|
||||
|
||||
|
||||
# 路由展示
|
||||
class RouterOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
name: str | None = None
|
||||
component: str | None = None
|
||||
path: str
|
||||
redirect: str | None = None
|
||||
meta: Meta | None = None
|
||||
order: int | None = None
|
||||
children: list[dict] = []
|
||||
|
||||
|
||||
class MenuTreeListOut(MenuSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
children: list[dict] = []
|
52
apps/vadmin/auth/schemas/role.py
Normal file
52
apps/vadmin/auth/schemas/role.py
Normal file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : role.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from core.data_types import DatetimeStr
|
||||
from .menu import MenuSimpleOut
|
||||
from .dept import DeptSimpleOut
|
||||
|
||||
|
||||
class Role(BaseModel):
|
||||
name: str
|
||||
disabled: bool = False
|
||||
order: int | None = None
|
||||
desc: str | None = None
|
||||
data_range: int = 4
|
||||
role_key: str
|
||||
is_admin: bool = False
|
||||
|
||||
|
||||
class RoleSimpleOut(Role):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
||||
|
||||
class RoleOut(RoleSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
menus: list[MenuSimpleOut] = []
|
||||
depts: list[DeptSimpleOut] = []
|
||||
|
||||
|
||||
class RoleIn(Role):
|
||||
menu_ids: list[int] = []
|
||||
dept_ids: list[int] = []
|
||||
|
||||
|
||||
class RoleOptionsOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
label: str = Field(alias='name')
|
||||
value: int = Field(alias='id')
|
||||
disabled: bool
|
||||
|
98
apps/vadmin/auth/schemas/user.py
Normal file
98
apps/vadmin/auth/schemas/user.py
Normal file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : user.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
from pydantic_core.core_schema import FieldValidationInfo
|
||||
from core.data_types import Telephone, DatetimeStr, Email
|
||||
from .role import RoleSimpleOut
|
||||
from .dept import DeptSimpleOut
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
name: str
|
||||
telephone: Telephone
|
||||
email: Email | None = None
|
||||
nickname: str | None = None
|
||||
avatar: str | None = None
|
||||
is_active: bool | None = True
|
||||
is_staff: bool | None = True
|
||||
gender: str | None = "0"
|
||||
is_wx_server_openid: bool | None = False
|
||||
|
||||
|
||||
class UserIn(User):
|
||||
"""
|
||||
创建用户
|
||||
"""
|
||||
role_ids: list[int] = []
|
||||
dept_ids: list[int] = []
|
||||
password: str | None = ""
|
||||
|
||||
|
||||
class UserUpdateBaseInfo(BaseModel):
|
||||
"""
|
||||
更新用户基本信息
|
||||
"""
|
||||
name: str
|
||||
telephone: Telephone
|
||||
email: Email | None = None
|
||||
nickname: str | None = None
|
||||
gender: str | None = "0"
|
||||
|
||||
|
||||
class UserUpdate(User):
|
||||
"""
|
||||
更新用户详细信息
|
||||
"""
|
||||
name: str | None = None
|
||||
telephone: Telephone
|
||||
email: Email | None = None
|
||||
nickname: str | None = None
|
||||
avatar: str | None = None
|
||||
is_active: bool | None = True
|
||||
is_staff: bool | None = False
|
||||
gender: str | None = "0"
|
||||
role_ids: list[int] = []
|
||||
dept_ids: list[int] = []
|
||||
|
||||
|
||||
class UserSimpleOut(User):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
update_datetime: DatetimeStr
|
||||
create_datetime: DatetimeStr
|
||||
|
||||
is_reset_password: bool | None = None
|
||||
last_login: DatetimeStr | None = None
|
||||
last_ip: str | None = None
|
||||
|
||||
|
||||
class UserPasswordOut(UserSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
password: str
|
||||
|
||||
|
||||
class UserOut(UserSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
roles: list[RoleSimpleOut] = []
|
||||
depts: list[DeptSimpleOut] = []
|
||||
|
||||
|
||||
class ResetPwd(BaseModel):
|
||||
password: str
|
||||
password_two: str
|
||||
|
||||
@field_validator('password_two')
|
||||
def check_passwords_match(cls, v, info: FieldValidationInfo):
|
||||
if 'password' in info.data and v != info.data['password']:
|
||||
raise ValueError('两次密码不一致!')
|
||||
return v
|
7
apps/vadmin/auth/utils/__init__.py
Normal file
7
apps/vadmin/auth/utils/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/8/8 11:02
|
||||
# @File : __init__.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
114
apps/vadmin/auth/utils/current.py
Normal file
114
apps/vadmin/auth/utils/current.py
Normal file
@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/24 16:44
|
||||
# @File : current.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 获取认证后的信息工具
|
||||
|
||||
from typing import Annotated
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
from apps.vadmin.auth.crud import UserDal
|
||||
from apps.vadmin.auth.models import VadminUser, VadminRole
|
||||
from core.exception import CustomException
|
||||
from utils import status
|
||||
from .validation import AuthValidation
|
||||
from fastapi import Request, Depends
|
||||
from application import settings
|
||||
from core.database import db_getter
|
||||
from .validation.auth import Auth
|
||||
|
||||
|
||||
class OpenAuth(AuthValidation):
|
||||
|
||||
"""
|
||||
开放认证,无认证也可以访问
|
||||
认证了以后可以获取到用户信息,无认证则获取不到
|
||||
"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
request: Request,
|
||||
token: Annotated[str, Depends(settings.oauth2_scheme)],
|
||||
db: AsyncSession = Depends(db_getter)
|
||||
):
|
||||
"""
|
||||
每次调用依赖此类的接口会执行该方法
|
||||
"""
|
||||
if not settings.OAUTH_ENABLE:
|
||||
return Auth(db=db)
|
||||
try:
|
||||
telephone, password = self.validate_token(request, token)
|
||||
user = await UserDal(db).get_data(telephone=telephone, password=password, v_return_none=True)
|
||||
return await self.validate_user(request, user, db, is_all=True)
|
||||
except CustomException:
|
||||
return Auth(db=db)
|
||||
|
||||
|
||||
class AllUserAuth(AuthValidation):
|
||||
|
||||
"""
|
||||
支持所有用户认证
|
||||
获取用户基本信息
|
||||
"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
request: Request,
|
||||
token: str = Depends(settings.oauth2_scheme),
|
||||
db: AsyncSession = Depends(db_getter)
|
||||
):
|
||||
"""
|
||||
每次调用依赖此类的接口会执行该方法
|
||||
"""
|
||||
if not settings.OAUTH_ENABLE:
|
||||
return Auth(db=db)
|
||||
telephone, password = self.validate_token(request, token)
|
||||
user = await UserDal(db).get_data(telephone=telephone, password=password, v_return_none=True)
|
||||
return await self.validate_user(request, user, db, is_all=True)
|
||||
|
||||
|
||||
class FullAdminAuth(AuthValidation):
|
||||
|
||||
"""
|
||||
只支持员工用户认证
|
||||
获取员工用户完整信息
|
||||
如果有权限,那么会验证该用户是否包括权限列表中的其中一个权限
|
||||
"""
|
||||
|
||||
def __init__(self, permissions: list[str] | None = None):
|
||||
if permissions:
|
||||
self.permissions = set(permissions)
|
||||
else:
|
||||
self.permissions = None
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
request: Request,
|
||||
token: str = Depends(settings.oauth2_scheme),
|
||||
db: AsyncSession = Depends(db_getter)
|
||||
) -> Auth:
|
||||
"""
|
||||
每次调用依赖此类的接口会执行该方法
|
||||
"""
|
||||
if not settings.OAUTH_ENABLE:
|
||||
return Auth(db=db)
|
||||
telephone, password = self.validate_token(request, token)
|
||||
options = [
|
||||
joinedload(VadminUser.roles).subqueryload(VadminRole.menus),
|
||||
joinedload(VadminUser.roles).subqueryload(VadminRole.depts),
|
||||
joinedload(VadminUser.depts)
|
||||
]
|
||||
user = await UserDal(db).get_data(
|
||||
telephone=telephone,
|
||||
password=password,
|
||||
v_return_none=True,
|
||||
v_options=options,
|
||||
is_staff=True
|
||||
)
|
||||
result = await self.validate_user(request, user, db, is_all=False)
|
||||
permissions = self.get_user_permissions(user)
|
||||
if permissions != {'*.*.*'} and self.permissions:
|
||||
if not (self.permissions & permissions):
|
||||
raise CustomException(msg="无权限操作", code=status.HTTP_403_FORBIDDEN)
|
||||
return result
|
180
apps/vadmin/auth/utils/login.py
Normal file
180
apps/vadmin/auth/utils/login.py
Normal file
@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/24 16:44
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 安全认证视图
|
||||
|
||||
"""
|
||||
JWT 表示 「JSON Web Tokens」。https://jwt.io/
|
||||
|
||||
它是一个将 JSON 对象编码为密集且没有空格的长字符串的标准。
|
||||
|
||||
通过这种方式,你可以创建一个有效期为 1 周的令牌。然后当用户第二天使用令牌重新访问时,你知道该用户仍然处于登入状态。
|
||||
一周后令牌将会过期,用户将不会通过认证,必须再次登录才能获得一个新令牌。
|
||||
|
||||
我们需要安装 python-jose 以在 Python 中生成和校验 JWT 令牌:pip install python-jose[cryptography]
|
||||
|
||||
PassLib 是一个用于处理哈希密码的很棒的 Python 包。它支持许多安全哈希算法以及配合算法使用的实用程序。
|
||||
推荐的算法是 「Bcrypt」:pip install passlib[bcrypt]
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
from redis.asyncio import Redis
|
||||
from fastapi import APIRouter, Depends, Request, Body
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from core.database import db_getter, redis_getter
|
||||
from core.exception import CustomException
|
||||
from utils import status
|
||||
from utils.response import SuccessResponse, ErrorResponse
|
||||
from application import settings
|
||||
from .login_manage import LoginManage
|
||||
from .validation import LoginForm, WXLoginForm
|
||||
from apps.vadmin.record.models import VadminLoginRecord
|
||||
from apps.vadmin.auth.crud import MenuDal, UserDal
|
||||
from apps.vadmin.auth.models import VadminUser
|
||||
from .current import FullAdminAuth
|
||||
from .validation.auth import Auth
|
||||
from utils.wx.oauth import WXOAuth
|
||||
import jwt
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
@app.post("/api/login", summary="API 手机号密码登录", description="Swagger API 文档登录认证")
|
||||
async def api_login_for_access_token(
|
||||
request: Request,
|
||||
data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: AsyncSession = Depends(db_getter)
|
||||
):
|
||||
user = await UserDal(db).get_data(telephone=data.username, v_return_none=True)
|
||||
error_code = status.HTTP_401_UNAUTHORIZED
|
||||
if not user:
|
||||
raise CustomException(status_code=error_code, code=error_code, msg="该手机号不存在")
|
||||
result = VadminUser.verify_password(data.password, user.password)
|
||||
if not result:
|
||||
raise CustomException(status_code=error_code, code=error_code, msg="手机号或密码错误")
|
||||
if not user.is_active:
|
||||
raise CustomException(status_code=error_code, code=error_code, msg="此手机号已被冻结")
|
||||
elif not user.is_staff:
|
||||
raise CustomException(status_code=error_code, code=error_code, msg="此手机号无权限")
|
||||
access_token = LoginManage.create_token({"sub": user.telephone, "password": user.password})
|
||||
record = LoginForm(platform='2', method='0', telephone=data.username, password=data.password)
|
||||
resp = {"access_token": access_token, "token_type": "bearer"}
|
||||
await VadminLoginRecord.create_login_record(db, record, True, request, resp)
|
||||
return resp
|
||||
|
||||
|
||||
@app.post("/login", summary="手机号密码登录", description="员工登录通道,限制最多输错次数,达到最大值后将is_active=False")
|
||||
async def login_for_access_token(
|
||||
request: Request,
|
||||
data: LoginForm,
|
||||
manage: LoginManage = Depends(),
|
||||
db: AsyncSession = Depends(db_getter)
|
||||
):
|
||||
try:
|
||||
result = await manage.password_login(data, db, request)
|
||||
|
||||
if not result.status:
|
||||
raise ValueError(result.msg)
|
||||
|
||||
access_token = LoginManage.create_token(
|
||||
{"sub": result.user.telephone, "is_refresh": False, "password": result.user.password}
|
||||
)
|
||||
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||
refresh_token = LoginManage.create_token(
|
||||
payload={"sub": result.user.telephone, "is_refresh": True, "password": result.user.password},
|
||||
expires=expires
|
||||
)
|
||||
resp = {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"token_type": "bearer",
|
||||
"is_reset_password": result.user.is_reset_password,
|
||||
"is_wx_server_openid": result.user.is_wx_server_openid
|
||||
}
|
||||
await VadminLoginRecord.create_login_record(db, data, True, request, resp)
|
||||
return SuccessResponse(resp)
|
||||
except ValueError as e:
|
||||
await VadminLoginRecord.create_login_record(db, data, False, request, {"message": str(e)})
|
||||
return ErrorResponse(msg=str(e))
|
||||
|
||||
|
||||
@app.post("/wx/login", summary="微信服务端一键登录", description="员工登录通道")
|
||||
async def wx_login_for_access_token(
|
||||
request: Request,
|
||||
data: WXLoginForm,
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
rd: Redis = Depends(redis_getter)
|
||||
):
|
||||
try:
|
||||
if data.platform != "1" or data.method != "2":
|
||||
raise ValueError("无效参数")
|
||||
wx = WXOAuth(rd, 0)
|
||||
telephone = await wx.parsing_phone_number(data.code)
|
||||
if not telephone:
|
||||
raise ValueError("无效Code")
|
||||
data.telephone = telephone
|
||||
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
|
||||
if not user:
|
||||
raise ValueError("手机号不存在")
|
||||
elif not user.is_active:
|
||||
raise ValueError("手机号已被冻结")
|
||||
except ValueError as e:
|
||||
await VadminLoginRecord.create_login_record(db, data, False, request, {"message": str(e)})
|
||||
return ErrorResponse(msg=str(e))
|
||||
|
||||
# 更新登录时间
|
||||
await UserDal(db).update_login_info(user, request.client.host)
|
||||
|
||||
# 登录成功创建 token
|
||||
access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False, "password": user.password})
|
||||
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||
refresh_token = LoginManage.create_token(
|
||||
payload={"sub": user.telephone, "is_refresh": True, "password": user.password},
|
||||
expires=expires
|
||||
)
|
||||
resp = {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"token_type": "bearer",
|
||||
"is_reset_password": user.is_reset_password,
|
||||
"is_wx_server_openid": user.is_wx_server_openid
|
||||
}
|
||||
await VadminLoginRecord.create_login_record(db, data, True, request, resp)
|
||||
return SuccessResponse(resp)
|
||||
|
||||
|
||||
@app.get("/getMenuList", summary="获取当前用户菜单树")
|
||||
async def get_menu_list(auth: Auth = Depends(FullAdminAuth())):
|
||||
return SuccessResponse(await MenuDal(auth.db).get_routers(auth.user))
|
||||
|
||||
|
||||
@app.post("/token/refresh", summary="刷新Token")
|
||||
async def token_refresh(refresh: str = Body(..., title="刷新Token")):
|
||||
error_code = status.HTTP_401_UNAUTHORIZED
|
||||
try:
|
||||
payload = jwt.decode(refresh, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
telephone: str = payload.get("sub")
|
||||
is_refresh: bool = payload.get("is_refresh")
|
||||
password: str = payload.get("password")
|
||||
if not telephone or not is_refresh or not password:
|
||||
return ErrorResponse("未认证,请您重新登录", code=error_code, status=error_code)
|
||||
except jwt.exceptions.InvalidSignatureError:
|
||||
return ErrorResponse("无效认证,请您重新登录", code=error_code, status=error_code)
|
||||
except jwt.exceptions.ExpiredSignatureError:
|
||||
return ErrorResponse("登录已超时,请您重新登录", code=error_code, status=error_code)
|
||||
|
||||
access_token = LoginManage.create_token({"sub": telephone, "is_refresh": False, "password": password})
|
||||
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||
refresh_token = LoginManage.create_token(
|
||||
payload={"sub": telephone, "is_refresh": True, "password": password},
|
||||
expires=expires
|
||||
)
|
||||
resp = {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"token_type": "bearer"
|
||||
}
|
||||
return SuccessResponse(resp)
|
62
apps/vadmin/auth/utils/login_manage.py
Normal file
62
apps/vadmin/auth/utils/login_manage.py
Normal file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/8/8 11:02
|
||||
# @File : auth_util.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi import Request
|
||||
from application import settings
|
||||
import jwt
|
||||
from apps.vadmin.auth import models
|
||||
from core.database import redis_getter
|
||||
from utils.sms.code import CodeSMS
|
||||
from .validation import LoginValidation, LoginForm, LoginResult
|
||||
|
||||
|
||||
class LoginManage:
|
||||
"""
|
||||
登录认证工具
|
||||
"""
|
||||
|
||||
@LoginValidation
|
||||
async def password_login(self, data: LoginForm, user: models.VadminUser, **kwargs) -> LoginResult:
|
||||
"""
|
||||
验证用户密码
|
||||
"""
|
||||
result = models.VadminUser.verify_password(data.password, user.password)
|
||||
if result:
|
||||
return LoginResult(status=True, msg="验证成功")
|
||||
return LoginResult(status=False, msg="手机号或密码错误")
|
||||
|
||||
@LoginValidation
|
||||
async def sms_login(self, data: LoginForm, request: Request, **kwargs) -> LoginResult:
|
||||
"""
|
||||
验证用户短信验证码
|
||||
"""
|
||||
rd = redis_getter(request)
|
||||
sms = CodeSMS(data.telephone, rd)
|
||||
result = await sms.check_sms_code(data.password)
|
||||
if result:
|
||||
return LoginResult(status=True, msg="验证成功")
|
||||
return LoginResult(status=False, msg="验证码错误")
|
||||
|
||||
@staticmethod
|
||||
def create_token(payload: dict, expires: timedelta = None):
|
||||
"""
|
||||
创建一个生成新的访问令牌的工具函数。
|
||||
|
||||
pyjwt:https://github.com/jpadilla/pyjwt/blob/master/docs/usage.rst
|
||||
jwt 博客:https://geek-docs.com/python/python-tutorial/j_python-jwt.html
|
||||
|
||||
#TODO 传入的时间为UTC时间datetime.datetime类型,但是在解码时获取到的是本机时间的时间戳
|
||||
"""
|
||||
if expires:
|
||||
expire = datetime.utcnow() + expires
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
payload.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
return encoded_jwt
|
10
apps/vadmin/auth/utils/validation/__init__.py
Normal file
10
apps/vadmin/auth/utils/validation/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/11/9 10:14
|
||||
# @File : __init__.py.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from .auth import Auth, AuthValidation
|
||||
from .login import LoginValidation, LoginForm, LoginResult, WXLoginForm
|
159
apps/vadmin/auth/utils/validation/auth.py
Normal file
159
apps/vadmin/auth/utils/validation/auth.py
Normal file
@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/24 16:44
|
||||
# @File : auth.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 用户凭证验证装饰器
|
||||
|
||||
from fastapi import Request
|
||||
import jwt
|
||||
from pydantic import BaseModel
|
||||
from application import settings
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from apps.vadmin.auth.models import VadminUser
|
||||
from core.exception import CustomException
|
||||
from utils import status
|
||||
from datetime import timedelta, datetime
|
||||
from apps.vadmin.auth.crud import UserDal
|
||||
|
||||
|
||||
class Auth(BaseModel):
|
||||
user: VadminUser = None
|
||||
db: AsyncSession
|
||||
data_range: int | None = None
|
||||
dept_ids: list | None = []
|
||||
|
||||
class Config:
|
||||
# 接收任意类型
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class AuthValidation:
|
||||
"""
|
||||
用于用户每次调用接口时,验证用户提交的token是否正确,并从token中获取用户信息
|
||||
"""
|
||||
|
||||
# status_code = 401 时,表示强制要求重新登录,因账号已冻结,账号已过期,手机号码错误,刷新token无效等问题导致
|
||||
# 只有 code = 401 时,表示 token 过期,要求刷新 token
|
||||
# 只有 code = 错误值时,只是报错,不重新登陆
|
||||
error_code = status.HTTP_401_UNAUTHORIZED
|
||||
warning_code = status.HTTP_ERROR
|
||||
|
||||
# status_code = 403 时,表示强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
|
||||
|
||||
@classmethod
|
||||
def validate_token(cls, request: Request, token: str | None) -> tuple[str, bool]:
|
||||
"""
|
||||
验证用户 token
|
||||
"""
|
||||
if not token:
|
||||
raise CustomException(
|
||||
msg="请您先登录!",
|
||||
code=status.HTTP_403_FORBIDDEN,
|
||||
status_code=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
telephone: str = payload.get("sub")
|
||||
exp: int = payload.get("exp")
|
||||
is_refresh: bool = payload.get("is_refresh")
|
||||
password: bool = payload.get("password")
|
||||
if not telephone or is_refresh or not password:
|
||||
raise CustomException(
|
||||
msg="未认证,请您重新登录",
|
||||
code=status.HTTP_403_FORBIDDEN,
|
||||
status_code=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
# 计算当前时间 + 缓冲时间是否大于等于 JWT 过期时间
|
||||
buffer_time = (datetime.now() + timedelta(minutes=settings.ACCESS_TOKEN_CACHE_MINUTES)).timestamp()
|
||||
# print("过期时间", exp, datetime.fromtimestamp(exp))
|
||||
# print("当前时间", buffer_time, datetime.fromtimestamp(buffer_time))
|
||||
# print("剩余时间", exp - buffer_time)
|
||||
if buffer_time >= exp:
|
||||
request.scope["if-refresh"] = 1
|
||||
else:
|
||||
request.scope["if-refresh"] = 0
|
||||
except (jwt.exceptions.InvalidSignatureError, jwt.exceptions.DecodeError):
|
||||
raise CustomException(
|
||||
msg="无效认证,请您重新登录",
|
||||
code=status.HTTP_403_FORBIDDEN,
|
||||
status_code=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
except jwt.exceptions.ExpiredSignatureError:
|
||||
raise CustomException(msg="认证已失效,请您重新登录", code=cls.error_code, status_code=cls.error_code)
|
||||
return telephone, password
|
||||
|
||||
@classmethod
|
||||
async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession, is_all: bool = True) -> Auth:
|
||||
"""
|
||||
验证用户信息
|
||||
:param request:
|
||||
:param user:
|
||||
:param db:
|
||||
:param is_all: 是否所有人访问,不加权限
|
||||
:return:
|
||||
"""
|
||||
if user is None:
|
||||
raise CustomException(msg="未认证,请您重新登陆", code=cls.error_code, status_code=cls.error_code)
|
||||
elif not user.is_active:
|
||||
raise CustomException(msg="用户已被冻结!", code=cls.error_code, status_code=cls.error_code)
|
||||
request.scope["telephone"] = user.telephone
|
||||
request.scope["user_id"] = user.id
|
||||
request.scope["user_name"] = user.name
|
||||
try:
|
||||
request.scope["body"] = await request.body()
|
||||
except RuntimeError:
|
||||
request.scope["body"] = "获取失败"
|
||||
if is_all:
|
||||
return Auth(user=user, db=db)
|
||||
data_range, dept_ids = await cls.get_user_data_range(user, db)
|
||||
return Auth(user=user, db=db, data_range=data_range, dept_ids=dept_ids)
|
||||
|
||||
@classmethod
|
||||
def get_user_permissions(cls, user: VadminUser) -> set:
|
||||
"""
|
||||
获取员工用户所有权限列表
|
||||
:param user: 用户实例
|
||||
:return:
|
||||
"""
|
||||
if user.is_admin():
|
||||
return {'*.*.*'}
|
||||
permissions = set()
|
||||
for role_obj in user.roles:
|
||||
for menu in role_obj.menus:
|
||||
if menu.perms and not menu.disabled:
|
||||
permissions.add(menu.perms)
|
||||
return permissions
|
||||
|
||||
@classmethod
|
||||
async def get_user_data_range(cls, user: VadminUser, db: AsyncSession) -> tuple:
|
||||
"""
|
||||
获取用户数据范围
|
||||
0 仅本人数据权限 create_user_id 查询
|
||||
1 本部门数据权限 部门 id 左连接查询
|
||||
2 本部门及以下数据权限 部门 id 左连接查询
|
||||
3 自定义数据权限 部门 id 左连接查询
|
||||
4 全部数据权限 无
|
||||
:param user:
|
||||
:param db:
|
||||
:return:
|
||||
"""
|
||||
if user.is_admin():
|
||||
return 4, ["*"]
|
||||
data_range = max([i.data_range for i in user.roles])
|
||||
dept_ids = set()
|
||||
if data_range == 0:
|
||||
pass
|
||||
elif data_range == 1:
|
||||
for dept in user.depts:
|
||||
dept_ids.add(dept.id)
|
||||
elif data_range == 2:
|
||||
# 递归获取部门列表
|
||||
dept_ids = await UserDal(db).recursion_get_dept_ids(user)
|
||||
elif data_range == 3:
|
||||
for role_obj in user.roles:
|
||||
for dept in role_obj.depts:
|
||||
dept_ids.add(dept.id)
|
||||
elif data_range == 4:
|
||||
dept_ids.add("*")
|
||||
return data_range, list(dept_ids)
|
92
apps/vadmin/auth/utils/validation/login.py
Normal file
92
apps/vadmin/auth/utils/validation/login.py
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/11/9 10:15
|
||||
# @File : login.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 登录验证装饰器
|
||||
|
||||
from fastapi import Request
|
||||
from pydantic import BaseModel, field_validator
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from application.settings import DEFAULT_AUTH_ERROR_MAX_NUMBER, DEMO, REDIS_DB_ENABLE
|
||||
from apps.vadmin.auth import crud, schemas
|
||||
from core.database import redis_getter
|
||||
from core.validator import vali_telephone
|
||||
from utils.count import Count
|
||||
|
||||
|
||||
class LoginForm(BaseModel):
|
||||
telephone: str
|
||||
password: str
|
||||
method: str = '0' # 认证方式,0:密码登录,1:短信登录,2:微信一键登录
|
||||
platform: str = '0' # 登录平台,0:PC端管理系统,1:移动端管理系统
|
||||
|
||||
# 重用验证器:https://docs.pydantic.dev/dev-v2/usage/validators/#reuse-validators
|
||||
normalize_telephone = field_validator('telephone')(vali_telephone)
|
||||
|
||||
|
||||
class WXLoginForm(BaseModel):
|
||||
telephone: str | None = None
|
||||
code: str
|
||||
method: str = '2' # 认证方式,0:密码登录,1:短信登录,2:微信一键登录
|
||||
platform: str = '1' # 登录平台,0:PC端管理系统,1:移动端管理系统
|
||||
|
||||
|
||||
class LoginResult(BaseModel):
|
||||
status: bool | None = False
|
||||
user: schemas.UserPasswordOut | None = None
|
||||
msg: str | None = None
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class LoginValidation:
|
||||
|
||||
"""
|
||||
验证用户登录时提交的数据是否有效
|
||||
"""
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult:
|
||||
self.result = LoginResult()
|
||||
if data.platform not in ["0", "1"] or data.method not in ["0", "1"]:
|
||||
self.result.msg = "无效参数"
|
||||
return self.result
|
||||
user = await crud.UserDal(db).get_data(telephone=data.telephone, v_return_none=True)
|
||||
if not user:
|
||||
self.result.msg = "该手机号不存在!"
|
||||
return self.result
|
||||
|
||||
result = await self.func(self, data=data, user=user, request=request)
|
||||
|
||||
if REDIS_DB_ENABLE:
|
||||
count_key = f"{data.telephone}_password_auth" if data.method == '0' else f"{data.telephone}_sms_auth"
|
||||
count = Count(redis_getter(request), count_key)
|
||||
else:
|
||||
count = None
|
||||
|
||||
if not result.status:
|
||||
self.result.msg = result.msg
|
||||
if not DEMO and count:
|
||||
number = await count.add(ex=86400)
|
||||
if number >= DEFAULT_AUTH_ERROR_MAX_NUMBER:
|
||||
await count.reset()
|
||||
# 如果等于最大次数,那么就将用户 is_active=False
|
||||
user.is_active = False
|
||||
await db.flush()
|
||||
elif not user.is_active:
|
||||
self.result.msg = "此手机号已被冻结!"
|
||||
elif data.platform in ["0", "1"] and not user.is_staff:
|
||||
self.result.msg = "此手机号无权限!"
|
||||
else:
|
||||
if not DEMO and count:
|
||||
await count.delete()
|
||||
self.result.msg = "OK"
|
||||
self.result.status = True
|
||||
self.result.user = schemas.UserPasswordOut.model_validate(user)
|
||||
await crud.UserDal(db).update_login_info(user, request.client.host)
|
||||
return self.result
|
307
apps/vadmin/auth/views.py
Normal file
307
apps/vadmin/auth/views.py
Normal file
@ -0,0 +1,307 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/2/24 17:02
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from redis.asyncio import Redis
|
||||
from fastapi import APIRouter, Depends, Body, UploadFile, Request
|
||||
from sqlalchemy.orm import joinedload
|
||||
from core.database import redis_getter
|
||||
from utils.response import SuccessResponse, ErrorResponse
|
||||
from . import schemas, crud, models
|
||||
from core.dependencies import IdList
|
||||
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
from .params import UserParams, RoleParams, DeptParams
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 接口测试
|
||||
###########################################################
|
||||
@app.get("/test", summary="接口测试")
|
||||
async def test(auth: Auth = Depends(OpenAuth())):
|
||||
return SuccessResponse(await crud.TestDal(auth.db).relationship_where_operations_has())
|
||||
|
||||
|
||||
###########################################################
|
||||
# 用户管理
|
||||
###########################################################
|
||||
@app.get("/users", summary="获取用户列表")
|
||||
async def get_users(
|
||||
params: UserParams = Depends(),
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.list"]))
|
||||
):
|
||||
model = models.VadminUser
|
||||
options = [joinedload(model.roles), joinedload(model.depts)]
|
||||
schema = schemas.UserOut
|
||||
datas, count = await crud.UserDal(auth.db).get_datas(
|
||||
**params.dict(),
|
||||
v_options=options,
|
||||
v_schema=schema,
|
||||
v_return_count=True
|
||||
)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.post("/users", summary="创建用户")
|
||||
async def create_user(data: schemas.UserIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create"]))):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.delete("/users", summary="批量删除用户", description="软删除,删除后清空所关联的角色")
|
||||
async def delete_users(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.delete"]))):
|
||||
if auth.user.id in ids.ids:
|
||||
return ErrorResponse("不能删除当前登录用户")
|
||||
elif 1 in ids.ids:
|
||||
return ErrorResponse("不能删除超级管理员用户")
|
||||
await crud.UserDal(auth.db).delete_datas(ids=ids.ids, v_soft=True, is_active=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/users/{data_id}", summary="更新用户信息")
|
||||
async def put_user(
|
||||
data_id: int,
|
||||
data: schemas.UserUpdate,
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.update"]))
|
||||
):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/users/{data_id}", summary="获取用户信息")
|
||||
async def get_user(
|
||||
data_id: int,
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.view", "auth.user.update"]))
|
||||
):
|
||||
model = models.VadminUser
|
||||
options = [joinedload(model.roles), joinedload(model.depts)]
|
||||
schema = schemas.UserOut
|
||||
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
|
||||
|
||||
|
||||
@app.post("/user/current/reset/password", summary="重置当前用户密码")
|
||||
async def user_current_reset_password(data: schemas.ResetPwd, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).reset_current_password(auth.user, data))
|
||||
|
||||
|
||||
@app.post("/user/current/update/info", summary="更新当前用户基本信息")
|
||||
async def post_user_current_update_info(data: schemas.UserUpdateBaseInfo, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).update_current_info(auth.user, data))
|
||||
|
||||
|
||||
@app.post("/user/current/update/avatar", summary="更新当前用户头像")
|
||||
async def post_user_current_update_avatar(file: UploadFile, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).update_current_avatar(auth.user, file))
|
||||
|
||||
|
||||
@app.get("/user/admin/current/info", summary="获取当前管理员信息")
|
||||
async def get_user_admin_current_info(auth: Auth = Depends(FullAdminAuth())):
|
||||
result = schemas.UserOut.model_validate(auth.user).model_dump()
|
||||
result["permissions"] = list(FullAdminAuth.get_user_permissions(auth.user))
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
@app.post("/user/export/query/list/to/excel", summary="导出用户查询列表为excel")
|
||||
async def post_user_export_query_list(
|
||||
header: list = Body(..., title="表头与对应字段"),
|
||||
params: UserParams = Depends(),
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.export"]))
|
||||
):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).export_query_list(header, params))
|
||||
|
||||
|
||||
@app.get("/user/download/import/template", summary="下载最新批量导入用户模板")
|
||||
async def get_user_download_new_import_template(auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).download_import_template())
|
||||
|
||||
|
||||
@app.post("/import/users", summary="批量导入用户")
|
||||
async def post_import_users(file: UploadFile, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.import"]))):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).import_users(file))
|
||||
|
||||
|
||||
@app.post("/users/init/password/send/sms", summary="初始化所选用户密码并发送通知短信")
|
||||
async def post_users_init_password(
|
||||
request: Request,
|
||||
ids: IdList = Depends(),
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"])),
|
||||
rd: Redis = Depends(redis_getter)
|
||||
):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_sms(ids.ids, rd))
|
||||
|
||||
|
||||
@app.post("/users/init/password/send/email", summary="初始化所选用户密码并发送通知邮件")
|
||||
async def post_users_init_password_send_email(
|
||||
request: Request,
|
||||
ids: IdList = Depends(),
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"])),
|
||||
rd: Redis = Depends(redis_getter)
|
||||
):
|
||||
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_email(ids.ids, rd))
|
||||
|
||||
|
||||
@app.put("/users/wx/server/openid", summary="更新当前用户服务端微信平台openid")
|
||||
async def put_user_wx_server_openid(code: str, auth: Auth = Depends(AllUserAuth()), rd: Redis = Depends(redis_getter)):
|
||||
result = await crud.UserDal(auth.db).update_wx_server_openid(code, auth.user, rd)
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
###########################################################
|
||||
# 角色管理
|
||||
###########################################################
|
||||
@app.get("/roles", summary="获取角色列表")
|
||||
async def get_roles(
|
||||
params: RoleParams = Depends(),
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.list"]))
|
||||
):
|
||||
datas, count = await crud.RoleDal(auth.db).get_datas(**params.dict(), v_return_count=True)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.post("/roles", summary="创建角色信息")
|
||||
async def create_role(role: schemas.RoleIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create"]))):
|
||||
return SuccessResponse(await crud.RoleDal(auth.db).create_data(data=role))
|
||||
|
||||
|
||||
@app.delete("/roles", summary="批量删除角色", description="硬删除, 如果存在用户关联则无法删除")
|
||||
async def delete_roles(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.delete"]))):
|
||||
if 1 in ids.ids:
|
||||
return ErrorResponse("不能删除管理员角色")
|
||||
await crud.RoleDal(auth.db).delete_datas(ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/roles/{data_id}", summary="更新角色信息")
|
||||
async def put_role(
|
||||
data_id: int,
|
||||
data: schemas.RoleIn,
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.update"]))
|
||||
):
|
||||
if 1 == data_id:
|
||||
return ErrorResponse("不能修改管理员角色")
|
||||
return SuccessResponse(await crud.RoleDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/roles/options", summary="获取角色选择项")
|
||||
async def get_role_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create", "auth.user.update"]))):
|
||||
return SuccessResponse(await crud.RoleDal(auth.db).get_select_datas())
|
||||
|
||||
|
||||
@app.get("/roles/{data_id}", summary="获取角色信息")
|
||||
async def get_role(
|
||||
data_id: int,
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.view", "auth.role.update"]))
|
||||
):
|
||||
model = models.VadminRole
|
||||
options = [joinedload(model.menus), joinedload(model.depts)]
|
||||
schema = schemas.RoleOut
|
||||
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, v_options=options, v_schema=schema))
|
||||
|
||||
|
||||
###########################################################
|
||||
# 菜单管理
|
||||
###########################################################
|
||||
@app.get("/menus", summary="获取菜单列表")
|
||||
async def get_menus(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.list"]))):
|
||||
datas = await crud.MenuDal(auth.db).get_tree_list(mode=1)
|
||||
return SuccessResponse(datas)
|
||||
|
||||
|
||||
@app.get("/menus/tree/options", summary="获取菜单树选择项,添加/修改菜单时使用")
|
||||
async def get_menus_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create", "auth.menu.update"]))):
|
||||
datas = await crud.MenuDal(auth.db).get_tree_list(mode=2)
|
||||
return SuccessResponse(datas)
|
||||
|
||||
|
||||
@app.get("/menus/role/tree/options", summary="获取菜单列表树信息,角色权限使用")
|
||||
async def get_menus_treeselect(
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create", "auth.role.update"]))
|
||||
):
|
||||
return SuccessResponse(await crud.MenuDal(auth.db).get_tree_list(mode=3))
|
||||
|
||||
|
||||
@app.post("/menus", summary="创建菜单信息")
|
||||
async def create_menu(menu: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create"]))):
|
||||
if menu.parent_id:
|
||||
menu.alwaysShow = False
|
||||
return SuccessResponse(await crud.MenuDal(auth.db).create_data(data=menu))
|
||||
|
||||
|
||||
@app.delete("/menus", summary="批量删除菜单", description="硬删除, 如果存在角色关联则无法删除")
|
||||
async def delete_menus(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.delete"]))):
|
||||
await crud.MenuDal(auth.db).delete_datas(ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/menus/{data_id}", summary="更新菜单信息")
|
||||
async def put_menus(
|
||||
data_id: int,
|
||||
data: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.update"]))
|
||||
):
|
||||
return SuccessResponse(await crud.MenuDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/menus/{data_id}", summary="获取菜单信息")
|
||||
async def get_menus(
|
||||
data_id: int,
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.view", "auth.menu.update"]))
|
||||
):
|
||||
schema = schemas.MenuSimpleOut
|
||||
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, v_schema=schema))
|
||||
|
||||
|
||||
@app.get("/role/menus/tree/{role_id}", summary="获取菜单列表树信息以及角色菜单权限ID,角色权限使用")
|
||||
async def get_role_menu_tree(
|
||||
role_id: int,
|
||||
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create", "auth.role.update"]))
|
||||
):
|
||||
tree_data = await crud.MenuDal(auth.db).get_tree_list(mode=3)
|
||||
role_menu_tree = await crud.RoleDal(auth.db).get_role_menu_tree(role_id)
|
||||
return SuccessResponse({"role_menu_tree": role_menu_tree, "menus": tree_data})
|
||||
|
||||
|
||||
###########################################################
|
||||
# 部门管理
|
||||
###########################################################
|
||||
@app.get("/depts", summary="获取部门列表")
|
||||
async def get_depts(
|
||||
params: DeptParams = Depends(),
|
||||
auth: Auth = Depends(FullAdminAuth())
|
||||
):
|
||||
datas = await crud.DeptDal(auth.db).get_tree_list(1)
|
||||
return SuccessResponse(datas)
|
||||
|
||||
|
||||
@app.get("/dept/tree/options", summary="获取部门树选择项,添加/修改部门时使用")
|
||||
async def get_dept_options(auth: Auth = Depends(FullAdminAuth())):
|
||||
datas = await crud.DeptDal(auth.db).get_tree_list(mode=2)
|
||||
return SuccessResponse(datas)
|
||||
|
||||
|
||||
@app.get("/dept/user/tree/options", summary="获取部门树选择项,添加/修改用户时使用")
|
||||
async def get_dept_treeselect(auth: Auth = Depends(FullAdminAuth())):
|
||||
return SuccessResponse(await crud.DeptDal(auth.db).get_tree_list(mode=3))
|
||||
|
||||
|
||||
@app.post("/depts", summary="创建部门信息")
|
||||
async def create_dept(data: schemas.Dept, auth: Auth = Depends(FullAdminAuth())):
|
||||
return SuccessResponse(await crud.DeptDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.delete("/depts", summary="批量删除部门", description="硬删除, 如果存在用户关联则无法删除")
|
||||
async def delete_depts(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth())):
|
||||
await crud.DeptDal(auth.db).delete_datas(ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/depts/{data_id}", summary="更新部门信息")
|
||||
async def put_dept(
|
||||
data_id: int,
|
||||
data: schemas.Dept,
|
||||
auth: Auth = Depends(FullAdminAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.DeptDal(auth.db).put_data(data_id, data))
|
7
apps/vadmin/help/__init__.py
Normal file
7
apps/vadmin/help/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023-02-15 20:03:49
|
||||
# @File : __init__.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 帮助中心
|
37
apps/vadmin/help/crud.py
Normal file
37
apps/vadmin/help/crud.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023-02-15 20:03:49
|
||||
# @File : crud.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 帮助中心 - 增删改查
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from core.crud import DalBase
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
class IssueDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(IssueDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminIssue
|
||||
self.schema = schemas.IssueSimpleOut
|
||||
|
||||
async def add_view_number(self, data_id: int) -> None:
|
||||
"""
|
||||
更新常见问题查看次数+1
|
||||
"""
|
||||
obj: models.VadminIssue = await self.get_data(data_id)
|
||||
obj.view_number = obj.view_number + 1 if obj.view_number else 1
|
||||
await self.flush(obj)
|
||||
|
||||
|
||||
class IssueCategoryDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(IssueCategoryDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminIssueCategory
|
||||
self.schema = schemas.IssueCategorySimpleOut
|
10
apps/vadmin/help/models/__init__.py
Normal file
10
apps/vadmin/help/models/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023-02-15 20:03:49
|
||||
# @File : __init__.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 初始化文件
|
||||
|
||||
|
||||
from .issue import VadminIssue, VadminIssueCategory
|
54
apps/vadmin/help/models/issue.py
Normal file
54
apps/vadmin/help/models/issue.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : issue.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 常见问题
|
||||
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
from apps.vadmin.auth.models import VadminUser
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Boolean, Integer, ForeignKey, Text
|
||||
|
||||
|
||||
class VadminIssueCategory(BaseModel):
|
||||
__tablename__ = "vadmin_help_issue_category"
|
||||
__table_args__ = ({'comment': '常见问题类别表'})
|
||||
|
||||
name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="类别名称")
|
||||
platform: Mapped[str] = mapped_column(String(8), index=True, nullable=False, comment="展示平台")
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
|
||||
|
||||
issues: Mapped[list["VadminIssue"]] = relationship(back_populates='category')
|
||||
|
||||
create_user_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
|
||||
comment="创建人"
|
||||
)
|
||||
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)
|
||||
|
||||
|
||||
class VadminIssue(BaseModel):
|
||||
__tablename__ = "vadmin_help_issue"
|
||||
__table_args__ = ({'comment': '常见问题记录表'})
|
||||
|
||||
category_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_help_issue_category.id", ondelete='CASCADE'),
|
||||
comment="类别"
|
||||
)
|
||||
category: Mapped[list["VadminIssueCategory"]] = relationship(foreign_keys=category_id, back_populates='issues')
|
||||
|
||||
title: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="标题")
|
||||
content: Mapped[str] = mapped_column(Text, comment="内容")
|
||||
view_number: Mapped[int] = mapped_column(Integer, default=0, comment="查看次数")
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
|
||||
|
||||
create_user_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
|
||||
comment="创建人"
|
||||
)
|
||||
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)
|
10
apps/vadmin/help/params/__init__.py
Normal file
10
apps/vadmin/help/params/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023-02-15 20:03:49
|
||||
# @File : __init__.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 初始化文件
|
||||
|
||||
|
||||
from .issue import IssueParams, IssueCategoryParams
|
51
apps/vadmin/help/params/issue.py
Normal file
51
apps/vadmin/help/params/issue.py
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : issue.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 常见问题
|
||||
|
||||
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class IssueParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
params: Paging = Depends(),
|
||||
is_active: bool = None,
|
||||
title: str = None,
|
||||
category_id: int = None
|
||||
):
|
||||
super().__init__(params)
|
||||
self.v_order = "desc"
|
||||
self.v_order_field = "create_datetime"
|
||||
self.is_active = is_active
|
||||
self.category_id = category_id
|
||||
self.title = ("like", title)
|
||||
|
||||
|
||||
class IssueCategoryParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
params: Paging = Depends(),
|
||||
is_active: bool = None,
|
||||
platform: str = None,
|
||||
name: str = None
|
||||
):
|
||||
super().__init__(params)
|
||||
self.v_order = "desc"
|
||||
self.v_order_field = "create_datetime"
|
||||
self.is_active = is_active
|
||||
self.platform = platform
|
||||
self.name = ("like", name)
|
12
apps/vadmin/help/schemas/__init__.py
Normal file
12
apps/vadmin/help/schemas/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023-02-15 20:03:49
|
||||
# @File : __init__.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 初始化文件
|
||||
|
||||
|
||||
from .issue import Issue, IssueSimpleOut, IssueListOut
|
||||
from .issue_category import IssueCategory, IssueCategorySimpleOut, IssueCategoryListOut, IssueCategoryOptionsOut
|
||||
from .issue_m2m import IssueCategoryPlatformOut
|
38
apps/vadmin/help/schemas/issue.py
Normal file
38
apps/vadmin/help/schemas/issue.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : issue.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 常见问题
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
from apps.vadmin.auth.schemas import UserSimpleOut
|
||||
from .issue_category import IssueCategorySimpleOut
|
||||
|
||||
|
||||
class Issue(BaseModel):
|
||||
category_id: int | None = None
|
||||
create_user_id: int | None = None
|
||||
|
||||
title: str | None = None
|
||||
content: str | None = None
|
||||
view_number: int | None = None
|
||||
is_active: bool | None = None
|
||||
|
||||
|
||||
class IssueSimpleOut(Issue):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
update_datetime: DatetimeStr
|
||||
create_datetime: DatetimeStr
|
||||
|
||||
|
||||
class IssueListOut(IssueSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
create_user: UserSimpleOut
|
||||
category: IssueCategorySimpleOut
|
43
apps/vadmin/help/schemas/issue_category.py
Normal file
43
apps/vadmin/help/schemas/issue_category.py
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : issue_category.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 常见问题类别
|
||||
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
from apps.vadmin.auth.schemas import UserSimpleOut
|
||||
|
||||
|
||||
class IssueCategory(BaseModel):
|
||||
name: str | None = None
|
||||
platform: str | None = None
|
||||
is_active: bool | None = None
|
||||
|
||||
create_user_id: int | None = None
|
||||
|
||||
|
||||
class IssueCategorySimpleOut(IssueCategory):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
update_datetime: DatetimeStr
|
||||
create_datetime: DatetimeStr
|
||||
|
||||
|
||||
class IssueCategoryListOut(IssueCategorySimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
create_user: UserSimpleOut
|
||||
|
||||
|
||||
class IssueCategoryOptionsOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
label: str = Field(alias='name')
|
||||
value: int = Field(alias='id')
|
||||
|
27
apps/vadmin/help/schemas/issue_m2m.py
Normal file
27
apps/vadmin/help/schemas/issue_m2m.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/2/17 15:18
|
||||
# @File : issue_m2m.py.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
from .issue import IssueSimpleOut
|
||||
|
||||
|
||||
class IssueCategoryPlatformOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
name: str | None = None
|
||||
platform: str | None = None
|
||||
is_active: bool | None = None
|
||||
create_user_id: int | None = None
|
||||
|
||||
id: int
|
||||
update_datetime: DatetimeStr
|
||||
create_datetime: DatetimeStr
|
||||
|
||||
issues: list[IssueSimpleOut] = None
|
||||
|
125
apps/vadmin/help/views.py
Normal file
125
apps/vadmin/help/views.py
Normal file
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023-02-15 20:03:49
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 帮助中心视图
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
from core.database import db_getter
|
||||
from utils.response import SuccessResponse
|
||||
from . import schemas, crud, params, models
|
||||
from core.dependencies import IdList
|
||||
from apps.vadmin.auth.utils.current import AllUserAuth
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 类别管理
|
||||
###########################################################
|
||||
@app.get("/issue/categorys", summary="获取类别列表")
|
||||
async def get_issue_categorys(p: params.IssueCategoryParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
model = models.VadminIssueCategory
|
||||
options = [joinedload(model.create_user)]
|
||||
schema = schemas.IssueCategoryListOut
|
||||
datas, count = await crud.IssueCategoryDal(auth.db).get_datas(
|
||||
**p.dict(),
|
||||
v_options=options,
|
||||
v_schema=schema,
|
||||
v_return_count=True
|
||||
)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.get("/issue/categorys/options", summary="获取类别选择项")
|
||||
async def get_issue_categorys_options(auth: Auth = Depends(AllUserAuth())):
|
||||
schema = schemas.IssueCategoryOptionsOut
|
||||
return SuccessResponse(await crud.IssueCategoryDal(auth.db).get_datas(limit=0, is_active=True, v_schema=schema))
|
||||
|
||||
|
||||
@app.post("/issue/categorys", summary="创建类别")
|
||||
async def create_issue_category(data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())):
|
||||
data.create_user_id = auth.user.id
|
||||
return SuccessResponse(await crud.IssueCategoryDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.delete("/issue/categorys", summary="批量删除类别", description="硬删除")
|
||||
async def delete_issue_categorys(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
await crud.IssueCategoryDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/issue/categorys/{data_id}", summary="更新类别信息")
|
||||
async def put_issue_category(data_id: int, data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.IssueCategoryDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/issue/categorys/{data_id}", summary="获取类别信息")
|
||||
async def get_issue_category(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
||||
schema = schemas.IssueCategorySimpleOut
|
||||
return SuccessResponse(await crud.IssueCategoryDal(auth.db).get_data(data_id, v_schema=schema))
|
||||
|
||||
|
||||
@app.get("/issue/categorys/platform/{platform}", summary="获取平台中的常见问题类别列表")
|
||||
async def get_issue_category_platform(platform: str, db: AsyncSession = Depends(db_getter)):
|
||||
model = models.VadminIssueCategory
|
||||
options = [joinedload(model.issues)]
|
||||
schema = schemas.IssueCategoryPlatformOut
|
||||
result = await crud.IssueCategoryDal(db).get_datas(
|
||||
limit=0,
|
||||
platform=platform,
|
||||
is_active=True,
|
||||
v_schema=schema,
|
||||
v_options=options
|
||||
)
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
###########################################################
|
||||
# 问题管理
|
||||
###########################################################
|
||||
@app.get("/issues", summary="获取问题列表")
|
||||
async def get_issues(p: params.IssueParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
model = models.VadminIssue
|
||||
options = [joinedload(model.create_user), joinedload(model.category)]
|
||||
schema = schemas.IssueListOut
|
||||
datas, count = await crud.IssueDal(auth.db).get_datas(
|
||||
**p.dict(),
|
||||
v_options=options,
|
||||
v_schema=schema,
|
||||
v_return_count=True
|
||||
)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.post("/issues", summary="创建问题")
|
||||
async def create_issue(data: schemas.Issue, auth: Auth = Depends(AllUserAuth())):
|
||||
data.create_user_id = auth.user.id
|
||||
return SuccessResponse(await crud.IssueDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.delete("/issues", summary="批量删除问题", description="硬删除")
|
||||
async def delete_issues(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
await crud.IssueDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/issues/{data_id}", summary="更新问题信息")
|
||||
async def put_issue(data_id: int, data: schemas.Issue, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.IssueDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/issues/{data_id}", summary="获取问题信息")
|
||||
async def get_issue(data_id: int, db: AsyncSession = Depends(db_getter)):
|
||||
schema = schemas.IssueSimpleOut
|
||||
return SuccessResponse(await crud.IssueDal(db).get_data(data_id, v_schema=schema))
|
||||
|
||||
|
||||
@app.get("/issues/add/view/number/{data_id}", summary="更新常见问题查看次数+1")
|
||||
async def issue_add_view_number(data_id: int, db: AsyncSession = Depends(db_getter)):
|
||||
return SuccessResponse(await crud.IssueDal(db).add_view_number(data_id))
|
0
apps/vadmin/record/__init__.py
Normal file
0
apps/vadmin/record/__init__.py
Normal file
88
apps/vadmin/record/crud.py
Normal file
88
apps/vadmin/record/crud.py
Normal file
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:18
|
||||
# @File : crud.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 数据库 增删改查操作
|
||||
|
||||
import random
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from . import models, schemas
|
||||
from core.crud import DalBase
|
||||
from core.mongo_manage import MongoManage
|
||||
|
||||
|
||||
class LoginRecordDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(LoginRecordDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminLoginRecord
|
||||
self.schema = schemas.LoginRecordSimpleOut
|
||||
|
||||
async def get_user_distribute(self) -> list[dict]:
|
||||
"""
|
||||
获取用户登录分布情况
|
||||
高德经纬度查询:https://lbs.amap.com/tools/picker
|
||||
|
||||
{
|
||||
name: '北京',
|
||||
center: [116.407394, 39.904211],
|
||||
total: 20
|
||||
}
|
||||
|
||||
:return: List[dict]
|
||||
"""
|
||||
result = [{
|
||||
"name": '北京',
|
||||
"center": [116.407394, 39.904211],
|
||||
},
|
||||
{
|
||||
"name": '重庆',
|
||||
"center": [106.551643, 29.562849],
|
||||
},
|
||||
{
|
||||
"name": '郑州',
|
||||
"center": [113.778584, 34.759197],
|
||||
},
|
||||
{
|
||||
"name": '南京',
|
||||
"center": [118.796624, 32.059344],
|
||||
},
|
||||
{
|
||||
"name": '武汉',
|
||||
"center": [114.304569, 30.593354],
|
||||
},
|
||||
{
|
||||
"name": '乌鲁木齐',
|
||||
"center": [87.616824, 43.825377],
|
||||
},
|
||||
{
|
||||
"name": '新乡',
|
||||
"center": [113.92679, 35.303589],
|
||||
}]
|
||||
for data in result:
|
||||
assert isinstance(data, dict)
|
||||
data["total"] = random.randint(2, 80)
|
||||
return result
|
||||
|
||||
|
||||
class SMSSendRecordDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(SMSSendRecordDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminSMSSendRecord
|
||||
self.schema = schemas.SMSSendRecordSimpleOut
|
||||
|
||||
|
||||
class OperationRecordDal(MongoManage):
|
||||
|
||||
def __init__(self, db: AsyncIOMotorDatabase):
|
||||
super(OperationRecordDal, self).__init__()
|
||||
self.db = db
|
||||
self.collection = db["operation_record"]
|
||||
self.schema = schemas.OperationRecordSimpleOut
|
||||
self.is_object_id = True
|
11
apps/vadmin/record/models/__init__.py
Normal file
11
apps/vadmin/record/models/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/2/14 21:11
|
||||
# @File : __init__.py.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : xpath例子
|
||||
|
||||
|
||||
from .login import VadminLoginRecord
|
||||
from .sms import VadminSMSSendRecord
|
86
apps/vadmin/record/models/login.py
Normal file
86
apps/vadmin/record/models/login.py
Normal file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : login.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 登录记录模型
|
||||
import json
|
||||
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from application.settings import LOGIN_LOG_RECORD
|
||||
from apps.vadmin.auth.utils.validation import LoginForm, WXLoginForm
|
||||
from utils.ip_manage import IPManage
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Boolean, Text
|
||||
from fastapi import Request
|
||||
from starlette.requests import Request as StarletteRequest
|
||||
from user_agents import parse
|
||||
|
||||
|
||||
class VadminLoginRecord(BaseModel):
|
||||
__tablename__ = "vadmin_record_login"
|
||||
__table_args__ = ({'comment': '登录记录表'})
|
||||
|
||||
telephone: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="手机号")
|
||||
status: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否登录成功")
|
||||
platform: Mapped[str] = mapped_column(String(8), comment="登陆平台")
|
||||
login_method: Mapped[str] = mapped_column(String(8), comment="认证方式")
|
||||
ip: Mapped[str | None] = mapped_column(String(50), comment="登陆地址")
|
||||
address: Mapped[str | None] = mapped_column(String(255), comment="登陆地点")
|
||||
country: Mapped[str | None] = mapped_column(String(255), comment="国家")
|
||||
province: Mapped[str | None] = mapped_column(String(255), comment="县")
|
||||
city: Mapped[str | None] = mapped_column(String(255), comment="城市")
|
||||
county: Mapped[str | None] = mapped_column(String(255), comment="区/县")
|
||||
operator: Mapped[str | None] = mapped_column(String(255), comment="运营商")
|
||||
postal_code: Mapped[str | None] = mapped_column(String(255), comment="邮政编码")
|
||||
area_code: Mapped[str | None] = mapped_column(String(255), comment="地区区号")
|
||||
browser: Mapped[str | None] = mapped_column(String(50), comment="浏览器")
|
||||
system: Mapped[str | None] = mapped_column(String(50), comment="操作系统")
|
||||
response: Mapped[str | None] = mapped_column(Text, comment="响应信息")
|
||||
request: Mapped[str | None] = mapped_column(Text, comment="请求信息")
|
||||
|
||||
@classmethod
|
||||
async def create_login_record(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
data: LoginForm | WXLoginForm,
|
||||
status: bool,
|
||||
req: Request | StarletteRequest,
|
||||
resp: dict
|
||||
):
|
||||
"""
|
||||
创建登录记录
|
||||
:return:
|
||||
"""
|
||||
if not LOGIN_LOG_RECORD:
|
||||
return None
|
||||
header = {}
|
||||
for k, v in req.headers.items():
|
||||
header[k] = v
|
||||
if isinstance(req, StarletteRequest):
|
||||
form = (await req.form()).multi_items()
|
||||
params = json.dumps({"form": form, "headers": header})
|
||||
else:
|
||||
body = json.loads((await req.body()).decode())
|
||||
params = json.dumps({"body": body, "headers": header})
|
||||
user_agent = parse(req.headers.get("user-agent"))
|
||||
system = f"{user_agent.os.family} {user_agent.os.version_string}"
|
||||
browser = f"{user_agent.browser.family} {user_agent.browser.version_string}"
|
||||
ip = IPManage(req.client.host)
|
||||
location = await ip.parse()
|
||||
obj = VadminLoginRecord(
|
||||
**location.dict(),
|
||||
telephone=data.telephone if data.telephone else data.code,
|
||||
status=status,
|
||||
browser=browser,
|
||||
system=system,
|
||||
response=json.dumps(resp),
|
||||
request=params,
|
||||
platform=data.platform,
|
||||
login_method=data.method
|
||||
)
|
||||
db.add(obj)
|
||||
await db.flush()
|
23
apps/vadmin/record/models/sms.py
Normal file
23
apps/vadmin/record/models/sms.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/3/21 17:36
|
||||
# @File : sms.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 短信发送记录模型
|
||||
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import Integer, String, Boolean, ForeignKey
|
||||
|
||||
|
||||
class VadminSMSSendRecord(BaseModel):
|
||||
__tablename__ = "vadmin_record_sms_send"
|
||||
__table_args__ = ({'comment': '短信发送记录表'})
|
||||
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("vadmin_auth_user.id", ondelete='CASCADE'), comment="操作人")
|
||||
status: Mapped[bool] = mapped_column(Boolean, default=True, comment="发送状态")
|
||||
content: Mapped[str] = mapped_column(String(255), comment="发送内容")
|
||||
telephone: Mapped[str] = mapped_column(String(11), comment="目标手机号")
|
||||
desc: Mapped[str | None] = mapped_column(String(255), comment="失败描述")
|
||||
scene: Mapped[str | None] = mapped_column(String(50), comment="发送场景")
|
3
apps/vadmin/record/params/__init__.py
Normal file
3
apps/vadmin/record/params/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .login import LoginParams
|
||||
from .operation import OperationParams
|
||||
from .sms import SMSParams
|
35
apps/vadmin/record/params/login.py
Normal file
35
apps/vadmin/record/params/login.py
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : login.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class LoginParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
ip: str = None,
|
||||
address: str = None,
|
||||
telephone: str = None,
|
||||
status: bool = None,
|
||||
platform: str = None,
|
||||
params: Paging = Depends()
|
||||
):
|
||||
super().__init__(params)
|
||||
self.ip = ("like", ip)
|
||||
self.telephone = ("like", telephone)
|
||||
self.address = ("like", address)
|
||||
self.status = status
|
||||
self.platform = platform
|
||||
self.v_order = "desc"
|
31
apps/vadmin/record/params/operation.py
Normal file
31
apps/vadmin/record/params/operation.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : operation.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class OperationParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
summary: str = None,
|
||||
telephone: str = None,
|
||||
request_method: str = None,
|
||||
params: Paging = Depends()
|
||||
):
|
||||
super().__init__(params)
|
||||
self.summary = ("like", summary)
|
||||
self.telephone = ("like", telephone)
|
||||
self.request_method = request_method
|
||||
self.v_order = "desc"
|
22
apps/vadmin/record/params/sms.py
Normal file
22
apps/vadmin/record/params/sms.py
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : sms.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class SMSParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(self, telephone: str = None, params: Paging = Depends()):
|
||||
super().__init__(params)
|
||||
self.telephone = ("like", telephone)
|
3
apps/vadmin/record/schemas/__init__.py
Normal file
3
apps/vadmin/record/schemas/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .login import LoginRecord, LoginRecordSimpleOut
|
||||
from .sms import SMSSendRecord, SMSSendRecordSimpleOut
|
||||
from .operation import OperationRecord, OperationRecordSimpleOut
|
38
apps/vadmin/record/schemas/login.py
Normal file
38
apps/vadmin/record/schemas/login.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : login.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class LoginRecord(BaseModel):
|
||||
telephone: str
|
||||
status: bool
|
||||
ip: str | None = None
|
||||
address: str | None = None
|
||||
browser: str | None = None
|
||||
system: str | None = None
|
||||
response: str | None = None
|
||||
request: str | None = None
|
||||
postal_code: str | None = None
|
||||
area_code: str | None = None
|
||||
country: str | None = None
|
||||
province: str | None = None
|
||||
city: str | None = None
|
||||
county: str | None = None
|
||||
operator: str | None = None
|
||||
platform: str | None = None
|
||||
login_method: str | None = None
|
||||
|
||||
|
||||
class LoginRecordSimpleOut(LoginRecord):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
35
apps/vadmin/record/schemas/operation.py
Normal file
35
apps/vadmin/record/schemas/operation.py
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : operation.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class OperationRecord(BaseModel):
|
||||
telephone: str | None = None
|
||||
user_id: int | None = None
|
||||
user_name: str | None = None
|
||||
status_code: int | None = None
|
||||
client_ip: str | None = None
|
||||
request_method: str | None = None
|
||||
api_path: str | None = None
|
||||
system: str | None = None
|
||||
browser: str | None = None
|
||||
summary: str | None = None
|
||||
route_name: str | None = None
|
||||
description: str | None = None
|
||||
tags: list[str] | None = None
|
||||
process_time: float | None = None
|
||||
params: str | None = None
|
||||
|
||||
|
||||
class OperationRecordSimpleOut(OperationRecord):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
create_datetime: DatetimeStr
|
28
apps/vadmin/record/schemas/sms.py
Normal file
28
apps/vadmin/record/schemas/sms.py
Normal file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/3/21 17:54
|
||||
# @File : sms.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class SMSSendRecord(BaseModel):
|
||||
telephone: str
|
||||
status: bool = True
|
||||
user_id: int | None = None
|
||||
content: str | None = None
|
||||
desc: str | None = None
|
||||
scene: str | None = None
|
||||
|
||||
|
||||
class SMSSendRecordSimpleOut(SMSSendRecord):
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
51
apps/vadmin/record/views.py
Normal file
51
apps/vadmin/record/views.py
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/24 16:44
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 主要接口文件
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||
from utils.response import SuccessResponse
|
||||
from . import crud
|
||||
from apps.vadmin.auth.utils.current import AllUserAuth
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
from .params import LoginParams, OperationParams, SMSParams
|
||||
from core.database import mongo_getter
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 日志管理
|
||||
###########################################################
|
||||
@app.get("/logins", summary="获取登录日志列表")
|
||||
async def get_record_login(p: LoginParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
datas, count = await crud.LoginRecordDal(auth.db).get_datas(**p.dict(), v_return_count=True)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.get("/operations", summary="获取操作日志列表")
|
||||
async def get_record_operation(
|
||||
p: OperationParams = Depends(),
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
count = await crud.OperationRecordDal(db).get_count(**p.to_count())
|
||||
datas = await crud.OperationRecordDal(db).get_datas(**p.dict())
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.get("/sms/send/list", summary="获取短信发送列表")
|
||||
async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
datas, count = await crud.SMSSendRecordDal(auth.db).get_datas(**p.dict(), v_return_count=True)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
###########################################################
|
||||
# 日志分析
|
||||
###########################################################
|
||||
@app.get("/analysis/user/login/distribute", summary="获取用户登录分布情况列表")
|
||||
async def get_user_login_distribute(auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.LoginRecordDal(auth.db).get_user_distribute())
|
0
apps/vadmin/resource/__init__.py
Normal file
0
apps/vadmin/resource/__init__.py
Normal file
20
apps/vadmin/resource/crud.py
Normal file
20
apps/vadmin/resource/crud.py
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/8/25 13:15
|
||||
# @File : crud.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from core.crud import DalBase
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
class ImagesDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(ImagesDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminImages
|
||||
self.schema = schemas.ImagesSimpleOut
|
1
apps/vadmin/resource/models/__init__.py
Normal file
1
apps/vadmin/resource/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .images import VadminImages
|
27
apps/vadmin/resource/models/images.py
Normal file
27
apps/vadmin/resource/models/images.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/8/25 13:41
|
||||
# @File : images.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 图片素材表
|
||||
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
from apps.vadmin.auth.models import VadminUser
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, ForeignKey, Integer
|
||||
|
||||
|
||||
class VadminImages(BaseModel):
|
||||
__tablename__ = "vadmin_resource_images"
|
||||
__table_args__ = ({'comment': '图片素材表'})
|
||||
|
||||
filename: Mapped[str] = mapped_column(String(255), nullable=False, comment="原图片名称")
|
||||
image_url: Mapped[str] = mapped_column(String(500), nullable=False, comment="图片链接")
|
||||
|
||||
create_user_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
|
||||
comment="创建人"
|
||||
)
|
||||
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)
|
1
apps/vadmin/resource/params/__init__.py
Normal file
1
apps/vadmin/resource/params/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .images import ImagesParams
|
27
apps/vadmin/resource/params/images.py
Normal file
27
apps/vadmin/resource/params/images.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/8/25 14:59
|
||||
# @File : images.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class ImagesParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filename: str = None,
|
||||
params: Paging = Depends()
|
||||
):
|
||||
super().__init__(params)
|
||||
self.filename = ('like', filename)
|
||||
self.v_order = "desc"
|
||||
self.v_order_field = "create_datetime"
|
1
apps/vadmin/resource/schemas/__init__.py
Normal file
1
apps/vadmin/resource/schemas/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .images import Images, ImagesOut, ImagesSimpleOut
|
33
apps/vadmin/resource/schemas/images.py
Normal file
33
apps/vadmin/resource/schemas/images.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/8/25 14:49
|
||||
# @File : images.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
from apps.vadmin.auth.schemas import UserSimpleOut
|
||||
|
||||
|
||||
class Images(BaseModel):
|
||||
filename: str
|
||||
image_url: str
|
||||
|
||||
create_user_id: int
|
||||
|
||||
|
||||
class ImagesSimpleOut(Images):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
||||
|
||||
class ImagesOut(ImagesSimpleOut):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
create_user: UserSimpleOut
|
60
apps/vadmin/resource/views.py
Normal file
60
apps/vadmin/resource/views.py
Normal file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/8/25 9:29
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from fastapi import APIRouter, Depends, UploadFile
|
||||
from sqlalchemy.orm import joinedload
|
||||
from core.dependencies import IdList
|
||||
from utils.file.aliyun_oss import AliyunOSS, BucketConf
|
||||
from utils.response import SuccessResponse
|
||||
from . import schemas, crud, params, models
|
||||
from apps.vadmin.auth.utils.current import FullAdminAuth
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
from application.settings import ALIYUN_OSS
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 图片资源管理
|
||||
###########################################################
|
||||
@app.get("/images", summary="获取图片列表")
|
||||
async def get_images_list(p: params.ImagesParams = Depends(), auth: Auth = Depends(FullAdminAuth())):
|
||||
model = models.VadminImages
|
||||
v_options = [joinedload(model.create_user)]
|
||||
v_schema = schemas.ImagesOut
|
||||
datas, count = await crud.ImagesDal(auth.db).get_datas(
|
||||
**p.dict(),
|
||||
v_options=v_options,
|
||||
v_schema=v_schema,
|
||||
v_return_count=True
|
||||
)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.post("/images", summary="创建图片")
|
||||
async def create_images(file: UploadFile, auth: Auth = Depends(FullAdminAuth())):
|
||||
filepath = f"/resource/images/"
|
||||
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(filepath, file)
|
||||
data = schemas.Images(
|
||||
filename=file.filename,
|
||||
image_url=result,
|
||||
create_user_id=auth.user.id
|
||||
)
|
||||
|
||||
return SuccessResponse(await crud.ImagesDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.delete("/images", summary="删除图片", description="硬删除")
|
||||
async def delete_images(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth())):
|
||||
await crud.ImagesDal(auth.db).delete_datas(ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.get("/images/{data_id}", summary="获取图片信息")
|
||||
async def get_images(data_id: int, auth: Auth = Depends(FullAdminAuth())):
|
||||
return SuccessResponse(await crud.ImagesDal(auth.db).get_data(data_id, v_schema=schemas.ImagesSimpleOut))
|
0
apps/vadmin/system/__init__.py
Normal file
0
apps/vadmin/system/__init__.py
Normal file
508
apps/vadmin/system/crud.py
Normal file
508
apps/vadmin/system/crud.py
Normal file
@ -0,0 +1,508 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:18
|
||||
# @File : crud.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 数据库 增删改查操作
|
||||
|
||||
import json
|
||||
import os
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from redis.asyncio import Redis
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
from application.settings import STATIC_ROOT, SUBSCRIBE, REDIS_DB_ENABLE
|
||||
from core.database import redis_getter
|
||||
from core.mongo_manage import MongoManage
|
||||
from utils.file.file_manage import FileManage
|
||||
from . import models, schemas
|
||||
from core.crud import DalBase
|
||||
from core.exception import CustomException
|
||||
from utils import status
|
||||
from fastapi import Request
|
||||
|
||||
|
||||
class DictTypeDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(DictTypeDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminDictType
|
||||
self.schema = schemas.DictTypeSimpleOut
|
||||
|
||||
async def get_dicts_details(self, dict_types: list[str]) -> dict:
|
||||
"""
|
||||
获取多个字典类型下的字典元素列表
|
||||
"""
|
||||
data = {}
|
||||
options = [joinedload(self.model.details)]
|
||||
objs = await DictTypeDal(self.db).get_datas(
|
||||
limit=0,
|
||||
v_return_objs=True,
|
||||
v_options=options,
|
||||
dict_type=("in", dict_types)
|
||||
)
|
||||
for obj in objs:
|
||||
if not obj:
|
||||
data[obj.dict_type] = []
|
||||
continue
|
||||
else:
|
||||
data[obj.dict_type] = [schemas.DictDetailsSimpleOut.model_validate(i).model_dump() for i in obj.details]
|
||||
return data
|
||||
|
||||
async def get_select_datas(self) -> list:
|
||||
"""获取选择数据,全部数据"""
|
||||
sql = select(self.model)
|
||||
queryset = await self.db.execute(sql)
|
||||
return [schemas.DictTypeOptionsOut.model_validate(i).model_dump() for i in queryset.scalars().all()]
|
||||
|
||||
|
||||
class DictDetailsDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(DictDetailsDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminDictDetails
|
||||
self.schema = schemas.DictDetailsSimpleOut
|
||||
|
||||
|
||||
class SettingsDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(SettingsDal, self).__init__()
|
||||
self.db = db
|
||||
self.model = models.VadminSystemSettings
|
||||
self.schema = schemas.SettingsSimpleOut
|
||||
|
||||
async def get_tab_values(self, tab_id: int) -> dict:
|
||||
"""
|
||||
获取系统配置标签下的信息
|
||||
"""
|
||||
datas = await self.get_datas(limit=0, tab_id=tab_id, v_return_objs=True)
|
||||
result = {}
|
||||
for data in datas:
|
||||
if not data.disabled:
|
||||
result[data.config_key] = data.config_value
|
||||
return result
|
||||
|
||||
async def update_datas(self, datas: dict, request: Request) -> None:
|
||||
"""
|
||||
更新系统配置信息
|
||||
|
||||
更新ico图标步骤:先将文件上传到本地,然后点击提交后,获取到文件地址,将上传的新文件覆盖原有文件
|
||||
原因:ico图标的路径是在前端的index.html中固定的,所以目前只能改变图片,不改变路径
|
||||
"""
|
||||
for key, value in datas.items():
|
||||
if key == "web_ico":
|
||||
continue
|
||||
elif key == "web_ico_local_path":
|
||||
if not value:
|
||||
continue
|
||||
ico = await self.get_data(config_key="web_ico", tab_id=1)
|
||||
web_ico = datas.get("web_ico")
|
||||
if ico.config_value == web_ico:
|
||||
continue
|
||||
# 将上传的ico路径替换到static/system/favicon.ico文件
|
||||
await FileManage.async_copy_file(value, os.path.join(STATIC_ROOT, "system/favicon.ico"))
|
||||
sql = update(self.model).where(self.model.config_key == "web_ico").values(config_value=web_ico)
|
||||
await self.db.execute(sql)
|
||||
else:
|
||||
sql = update(self.model).where(self.model.config_key == str(key)).values(config_value=value)
|
||||
await self.db.execute(sql)
|
||||
if "wx_server_app_id" in datas and REDIS_DB_ENABLE:
|
||||
rd = redis_getter(request)
|
||||
await rd.client().set("wx_server", json.dumps(datas))
|
||||
elif "sms_access_key" in datas and REDIS_DB_ENABLE:
|
||||
rd = redis_getter(request)
|
||||
await rd.client().set('aliyun_sms', json.dumps(datas))
|
||||
|
||||
async def get_base_config(self) -> dict:
|
||||
"""
|
||||
获取系统基本信息
|
||||
"""
|
||||
ignore_configs = ["wx_server_app_id", "wx_server_app_secret"]
|
||||
datas = await self.get_datas(limit=0, tab_id=("in", ["1", "9"]), disabled=False, v_return_objs=True)
|
||||
result = {}
|
||||
for config in datas:
|
||||
if config.config_key not in ignore_configs:
|
||||
result[config.config_key] = config.config_value
|
||||
return result
|
||||
|
||||
|
||||
class SettingsTabDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
super(SettingsTabDal, self).__init__(db, models.VadminSystemSettingsTab, schemas.SettingsTabSimpleOut)
|
||||
|
||||
async def get_classify_tab_values(self, classify: list[str], hidden: bool | None = False) -> dict:
|
||||
"""
|
||||
获取系统配置分类下的标签信息
|
||||
"""
|
||||
model = models.VadminSystemSettingsTab
|
||||
options = [joinedload(model.settings)]
|
||||
datas = await self.get_datas(
|
||||
limit=0,
|
||||
v_options=options,
|
||||
classify=("in", classify),
|
||||
disabled=False,
|
||||
v_return_objs=True,
|
||||
hidden=hidden
|
||||
)
|
||||
return self.__generate_values(datas)
|
||||
|
||||
async def get_tab_name_values(self, tab_names: list[str], hidden: bool | None = False) -> dict:
|
||||
"""
|
||||
获取系统配置标签下的标签信息
|
||||
"""
|
||||
model = models.VadminSystemSettingsTab
|
||||
options = [joinedload(model.settings)]
|
||||
datas = await self.get_datas(
|
||||
limit=0,
|
||||
v_options=options,
|
||||
tab_name=("in", tab_names),
|
||||
disabled=False,
|
||||
v_return_objs=True,
|
||||
hidden=hidden
|
||||
)
|
||||
return self.__generate_values(datas)
|
||||
|
||||
@classmethod
|
||||
def __generate_values(cls, datas: list[models.VadminSystemSettingsTab]) -> dict:
|
||||
"""
|
||||
生成字典值
|
||||
"""
|
||||
return {
|
||||
tab.tab_name: {
|
||||
item.config_key: item.config_value
|
||||
for item in tab.settings
|
||||
if not item.disabled
|
||||
}
|
||||
for tab in datas
|
||||
}
|
||||
|
||||
|
||||
class TaskDal(MongoManage):
|
||||
|
||||
class JobOperation(Enum):
|
||||
add = "add_job"
|
||||
|
||||
def __init__(self, db: AsyncIOMotorDatabase):
|
||||
super(TaskDal, self).__init__(db, "vadmin_system_task", schemas.TaskSimpleOut)
|
||||
|
||||
async def get_task(
|
||||
self,
|
||||
_id: str = None,
|
||||
v_return_none: bool = False,
|
||||
v_schema: Any = None,
|
||||
**kwargs
|
||||
) -> dict | None:
|
||||
"""
|
||||
获取单个数据,默认使用 ID 查询,否则使用关键词查询
|
||||
|
||||
包括临时字段 last_run_datetime,is_active
|
||||
is_active: 只有在 scheduler_task_jobs 任务运行表中存在相同 _id 才表示任务添加成功,任务状态才为 True
|
||||
last_run_datetime: 在 scheduler_task_record 中获取该任务最近一次执行完成的时间
|
||||
|
||||
:param _id: 数据 ID
|
||||
:param v_return_none: 是否返回空 None,否则抛出异常,默认抛出异常
|
||||
:param v_schema: 指定使用的序列化对象
|
||||
"""
|
||||
if _id:
|
||||
kwargs["_id"] = ("ObjectId", _id)
|
||||
|
||||
params = self.filter_condition(**kwargs)
|
||||
pipeline = [
|
||||
{
|
||||
'$addFields': {
|
||||
'str_id': {'$toString': '$_id'}
|
||||
}
|
||||
},
|
||||
{
|
||||
'$lookup': {
|
||||
'from': 'scheduler_task_jobs',
|
||||
'localField': 'str_id',
|
||||
'foreignField': '_id',
|
||||
'as': 'matched_jobs'
|
||||
}
|
||||
},
|
||||
{
|
||||
'$lookup': {
|
||||
'from': 'scheduler_task_record',
|
||||
'localField': 'str_id',
|
||||
'foreignField': 'job_id',
|
||||
'as': 'matched_records'
|
||||
}
|
||||
},
|
||||
{
|
||||
'$addFields': {
|
||||
'is_active': {
|
||||
'$cond': {
|
||||
'if': {'$ne': ['$matched_jobs', []]},
|
||||
'then': True,
|
||||
'else': False
|
||||
}
|
||||
},
|
||||
'last_run_datetime': {
|
||||
'$ifNull': [
|
||||
{'$arrayElemAt': ['$matched_records.create_datetime', -1]},
|
||||
None
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'$business': {
|
||||
'matched_records': 0,
|
||||
'matched_jobs': 0
|
||||
}
|
||||
},
|
||||
{
|
||||
'$match': params
|
||||
},
|
||||
{
|
||||
'$facet': {
|
||||
'documents': [
|
||||
{'$limit': 1},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
# 执行聚合查询
|
||||
cursor = self.collection.aggregate(pipeline)
|
||||
result = await cursor.to_list(length=None)
|
||||
data = result[0]['documents']
|
||||
if not data and v_return_none:
|
||||
return None
|
||||
elif not data:
|
||||
raise CustomException("未查找到对应数据", code=status.HTTP_404_NOT_FOUND)
|
||||
data = data[0]
|
||||
if data and v_schema:
|
||||
return jsonable_encoder(v_schema(**data))
|
||||
return data
|
||||
|
||||
async def get_tasks(
|
||||
self,
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
v_schema: Any = None,
|
||||
v_order: str = None,
|
||||
v_order_field: str = None,
|
||||
**kwargs
|
||||
) -> tuple:
|
||||
"""
|
||||
获取任务信息列表
|
||||
|
||||
添加了两个临时字段
|
||||
is_active: 只有在 scheduler_task_jobs 任务运行表中存在相同 _id 才表示任务添加成功,任务状态才为 True
|
||||
last_run_datetime: 在 scheduler_task_record 中获取该任务最近一次执行完成的时间
|
||||
"""
|
||||
v_order_field = v_order_field if v_order_field else 'create_datetime'
|
||||
v_order = -1 if v_order in self.ORDER_FIELD else 1
|
||||
params = self.filter_condition(**kwargs)
|
||||
pipeline = [
|
||||
{
|
||||
'$addFields': {
|
||||
'str_id': {'$toString': '$_id'}
|
||||
}
|
||||
},
|
||||
{
|
||||
'$lookup': {
|
||||
'from': 'scheduler_task_jobs',
|
||||
'localField': 'str_id',
|
||||
'foreignField': '_id',
|
||||
'as': 'matched_jobs'
|
||||
}
|
||||
},
|
||||
{
|
||||
'$lookup': {
|
||||
'from': 'scheduler_task_record',
|
||||
'localField': 'str_id',
|
||||
'foreignField': 'job_id',
|
||||
'as': 'matched_records'
|
||||
}
|
||||
},
|
||||
{
|
||||
'$addFields': {
|
||||
'is_active': {
|
||||
'$cond': {
|
||||
'if': {'$ne': ['$matched_jobs', []]},
|
||||
'then': True,
|
||||
'else': False
|
||||
}
|
||||
},
|
||||
'last_run_datetime': {
|
||||
'$ifNull': [
|
||||
{'$arrayElemAt': ['$matched_records.create_datetime', -1]},
|
||||
None
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'$business': {
|
||||
'matched_records': 0,
|
||||
'matched_jobs': 0
|
||||
}
|
||||
},
|
||||
{
|
||||
'$match': params
|
||||
},
|
||||
{
|
||||
'$facet': {
|
||||
'documents': [
|
||||
{'$sort': {v_order_field: v_order}},
|
||||
{'$limit': limit},
|
||||
{'$skip': (page - 1) * limit}
|
||||
],
|
||||
'count': [{'$count': 'total'}]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# 执行聚合查询
|
||||
cursor = self.collection.aggregate(pipeline)
|
||||
result = await cursor.to_list(length=None)
|
||||
datas = result[0]['documents']
|
||||
count = result[0]['count'][0]['total'] if result[0]['count'] else 0
|
||||
if count == 0:
|
||||
return [], 0
|
||||
elif v_schema:
|
||||
datas = [jsonable_encoder(v_schema(**data)) for data in datas]
|
||||
elif self.schema:
|
||||
datas = [jsonable_encoder(self.schema(**data)) for data in datas]
|
||||
return datas, count
|
||||
|
||||
async def add_task(self, rd: Redis, data: dict) -> int:
|
||||
"""
|
||||
添加任务到消息队列
|
||||
|
||||
使用消息无保留策略:无保留是指当发送者向某个频道发送消息时,如果没有订阅该频道的调用方,就直接将该消息丢弃。
|
||||
|
||||
:param rd: redis 对象
|
||||
:param data: 行数据字典
|
||||
:return: 接收到消息的订阅者数量。
|
||||
"""
|
||||
exec_strategy = data.get("exec_strategy")
|
||||
job_params = {
|
||||
"name": data.get("_id"),
|
||||
"job_class": data.get("job_class"),
|
||||
"expression": data.get("expression")
|
||||
}
|
||||
if exec_strategy == "interval" or exec_strategy == "cron":
|
||||
job_params["start_date"] = data.get("start_date")
|
||||
job_params["end_date"] = data.get("end_date")
|
||||
message = {
|
||||
"operation": self.JobOperation.add.value,
|
||||
"task": {
|
||||
"exec_strategy": data.get("exec_strategy"),
|
||||
"job_params": job_params
|
||||
}
|
||||
}
|
||||
return await rd.publish(SUBSCRIBE, json.dumps(message).encode('utf-8'))
|
||||
|
||||
async def create_task(self, rd: Redis, data: schemas.Task) -> dict:
|
||||
"""
|
||||
创建任务
|
||||
"""
|
||||
data_dict = data.model_dump()
|
||||
is_active = data_dict.pop('is_active')
|
||||
insert_result = await super().create_data(data_dict)
|
||||
obj = await self.get_task(insert_result.inserted_id, v_schema=schemas.TaskSimpleOut)
|
||||
|
||||
# 如果分组不存在则新增分组
|
||||
group = await TaskGroupDal(self.db).get_data(value=data.group, v_return_none=True)
|
||||
if not group:
|
||||
await TaskGroupDal(self.db).create_data({"value": data.group})
|
||||
|
||||
result = {
|
||||
"subscribe_number": 0,
|
||||
"is_active": is_active
|
||||
}
|
||||
|
||||
if is_active:
|
||||
# 创建任务成功后, 如果任务状态为 True,则向消息队列中发送任务
|
||||
result['subscribe_number'] = await self.add_task(rd, obj)
|
||||
return result
|
||||
|
||||
async def put_task(self, rd: Redis, _id: str, data: schemas.Task) -> dict:
|
||||
"""
|
||||
更新任务
|
||||
"""
|
||||
data_dict = data.model_dump()
|
||||
is_active = data_dict.pop('is_active')
|
||||
await super(TaskDal, self).put_data(_id, data)
|
||||
obj: dict = await self.get_task(_id, v_schema=schemas.TaskSimpleOut)
|
||||
|
||||
# 如果分组不存在则新增分组
|
||||
group = await TaskGroupDal(self.db).get_data(value=data.group, v_return_none=True)
|
||||
if not group:
|
||||
await TaskGroupDal(self.db).create_data({"value": data.group})
|
||||
|
||||
try:
|
||||
# 删除正在运行中的 Job
|
||||
await SchedulerTaskJobsDal(self.db).delete_data(_id)
|
||||
except CustomException as e:
|
||||
pass
|
||||
|
||||
result = {
|
||||
"subscribe_number": 0,
|
||||
"is_active": is_active
|
||||
}
|
||||
|
||||
if is_active:
|
||||
# 更新任务成功后, 如果任务状态为 True,则向消息队列中发送任务
|
||||
result['subscribe_number'] = await self.add_task(rd, obj)
|
||||
return result
|
||||
|
||||
async def delete_task(self, _id: str) -> bool:
|
||||
"""
|
||||
删除任务
|
||||
"""
|
||||
result = await super(TaskDal, self).delete_data(_id)
|
||||
|
||||
try:
|
||||
# 删除正在运行中的 Job
|
||||
await SchedulerTaskJobsDal(self.db).delete_data(_id)
|
||||
except CustomException as e:
|
||||
pass
|
||||
return result
|
||||
|
||||
async def run_once_task(self, rd: Redis, _id: str) -> int:
|
||||
"""
|
||||
执行一次任务
|
||||
"""
|
||||
obj: dict = await self.get_data(_id, v_schema=schemas.TaskSimpleOut)
|
||||
message = {
|
||||
"operation": self.JobOperation.add.value,
|
||||
"task": {
|
||||
"exec_strategy": "once",
|
||||
"job_params": {
|
||||
"name": obj.get("_id"),
|
||||
"job_class": obj.get("job_class")
|
||||
}
|
||||
}
|
||||
}
|
||||
return await rd.publish(SUBSCRIBE, json.dumps(message).encode('utf-8'))
|
||||
|
||||
|
||||
class TaskGroupDal(MongoManage):
|
||||
|
||||
def __init__(self, db: AsyncIOMotorDatabase):
|
||||
super(TaskGroupDal, self).__init__(db, "vadmin_system_task_group")
|
||||
|
||||
|
||||
class TaskRecordDal(MongoManage):
|
||||
|
||||
def __init__(self, db: AsyncIOMotorDatabase):
|
||||
super(TaskRecordDal, self).__init__(db, "scheduler_task_record")
|
||||
|
||||
|
||||
class SchedulerTaskJobsDal(MongoManage):
|
||||
|
||||
def __init__(self, db: AsyncIOMotorDatabase):
|
||||
super(SchedulerTaskJobsDal, self).__init__(db, "scheduler_task_jobs", is_object_id=False)
|
2
apps/vadmin/system/models/__init__.py
Normal file
2
apps/vadmin/system/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .dict import VadminDictType, VadminDictDetails
|
||||
from .settings import VadminSystemSettings, VadminSystemSettingsTab
|
40
apps/vadmin/system/models/dict.py
Normal file
40
apps/vadmin/system/models/dict.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : user.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 系统字典模型
|
||||
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import Column, String, Boolean, ForeignKey, Integer
|
||||
|
||||
|
||||
class VadminDictType(BaseModel):
|
||||
__tablename__ = "vadmin_system_dict_type"
|
||||
__table_args__ = ({'comment': '字典类型表'})
|
||||
|
||||
dict_name: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典名称")
|
||||
dict_type: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典类型")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="字典状态,是否禁用")
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
||||
details: Mapped[list["VadminDictDetails"]] = relationship(back_populates="dict_type")
|
||||
|
||||
|
||||
class VadminDictDetails(BaseModel):
|
||||
__tablename__ = "vadmin_system_dict_details"
|
||||
__table_args__ = ({'comment': '字典详情表'})
|
||||
|
||||
label: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典标签")
|
||||
value: Mapped[str] = mapped_column(String(50), index=True, nullable=False, comment="字典键值")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="字典状态,是否禁用")
|
||||
is_default: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否默认")
|
||||
order: Mapped[int] = mapped_column(Integer, comment="字典排序")
|
||||
dict_type_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_system_dict_type.id", ondelete='CASCADE'),
|
||||
comment="关联字典类型"
|
||||
)
|
||||
dict_type: Mapped[VadminDictType] = relationship(foreign_keys=dict_type_id, back_populates="details")
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
42
apps/vadmin/system/models/settings.py
Normal file
42
apps/vadmin/system/models/settings.py
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/7/7 13:41
|
||||
# @File : settings.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 系统字典模型
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
from db.db_base import BaseModel
|
||||
from sqlalchemy import String, Integer, ForeignKey, Boolean, Text
|
||||
|
||||
|
||||
class VadminSystemSettingsTab(BaseModel):
|
||||
__tablename__ = "vadmin_system_settings_tab"
|
||||
__table_args__ = ({'comment': '系统配置分类表'})
|
||||
|
||||
title: Mapped[str] = mapped_column(String(255), comment="标题")
|
||||
classify: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="分类键")
|
||||
tab_label: Mapped[str] = mapped_column(String(255), comment="tab标题")
|
||||
tab_name: Mapped[str] = mapped_column(String(255), index=True, nullable=False, unique=True, comment="tab标识符")
|
||||
hidden: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否隐藏")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||
|
||||
settings: Mapped[list["VadminSystemSettings"]] = relationship(back_populates="tab")
|
||||
|
||||
|
||||
class VadminSystemSettings(BaseModel):
|
||||
__tablename__ = "vadmin_system_settings"
|
||||
__table_args__ = ({'comment': '系统配置表'})
|
||||
|
||||
config_label: Mapped[str] = mapped_column(String(255), comment="配置表标签")
|
||||
config_key: Mapped[str] = mapped_column(String(255), index=True, nullable=False, unique=True, comment="配置表键")
|
||||
config_value: Mapped[str | None] = mapped_column(Text, comment="配置表内容")
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注信息")
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否禁用")
|
||||
|
||||
tab_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("vadmin_system_settings_tab.id", ondelete='CASCADE'),
|
||||
comment="关联tab标签"
|
||||
)
|
||||
tab: Mapped[VadminSystemSettingsTab] = relationship(foreign_keys=tab_id, back_populates="settings")
|
3
apps/vadmin/system/params/__init__.py
Normal file
3
apps/vadmin/system/params/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .dict_type import DictTypeParams
|
||||
from .dict_detail import DictDetailParams
|
||||
from .task import TaskParams
|
23
apps/vadmin/system/params/dict_detail.py
Normal file
23
apps/vadmin/system/params/dict_detail.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : dict_type.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class DictDetailParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(self, dict_type_id: int = None, label: str = None, params: Paging = Depends()):
|
||||
super().__init__(params)
|
||||
self.dict_type_id = dict_type_id
|
||||
self.label = ("like", label)
|
23
apps/vadmin/system/params/dict_type.py
Normal file
23
apps/vadmin/system/params/dict_type.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : dict_type.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 查询参数-类依赖项
|
||||
|
||||
"""
|
||||
类依赖项-官方文档:https://fastapi.tiangolo.com/zh/tutorial/dependencies/classes-as-dependencies/
|
||||
"""
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class DictTypeParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(self, dict_name: str = None, dict_type: str = None, params: Paging = Depends()):
|
||||
super().__init__(params)
|
||||
self.dict_name = ("like", dict_name)
|
||||
self.dict_type = ("like", dict_type)
|
32
apps/vadmin/system/params/task.py
Normal file
32
apps/vadmin/system/params/task.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/6/25 14:50
|
||||
# @File : task.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
from fastapi import Depends
|
||||
from core.dependencies import Paging, QueryParams
|
||||
|
||||
|
||||
class TaskParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(self, name: str = None, _id: str = None, group: str = None, params: Paging = Depends()):
|
||||
super().__init__(params)
|
||||
self.name = ("like", name)
|
||||
self.group = group
|
||||
self._id = ("ObjectId", _id)
|
||||
self.v_order = "desc"
|
||||
|
||||
|
||||
class TaskRecordParams(QueryParams):
|
||||
"""
|
||||
列表分页
|
||||
"""
|
||||
def __init__(self, job_id: str = None, name: str = None, params: Paging = Depends()):
|
||||
super().__init__(params)
|
||||
self.job_id = ("like", job_id)
|
||||
self.name = ("like", name)
|
||||
self.v_order = "desc"
|
4
apps/vadmin/system/schemas/__init__.py
Normal file
4
apps/vadmin/system/schemas/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .dict import DictType, DictDetails, DictTypeSimpleOut, DictDetailsSimpleOut, DictTypeOptionsOut
|
||||
from .settings_tab import SettingsTab, SettingsTabSimpleOut
|
||||
from .settings import Settings, SettingsSimpleOut
|
||||
from .task import Task, TaskSimpleOut
|
53
apps/vadmin/system/schemas/dict.py
Normal file
53
apps/vadmin/system/schemas/dict.py
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : dict.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class DictType(BaseModel):
|
||||
dict_name: str
|
||||
dict_type: str
|
||||
disabled: bool | None = False
|
||||
remark: str | None = None
|
||||
|
||||
|
||||
class DictTypeSimpleOut(DictType):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
||||
|
||||
class DictTypeOptionsOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
label: str = Field(alias='dict_name')
|
||||
value: int = Field(alias='id')
|
||||
disabled: bool
|
||||
|
||||
|
||||
class DictDetails(BaseModel):
|
||||
label: str
|
||||
value: str
|
||||
disabled: bool | None = False
|
||||
is_default: bool | None = False
|
||||
remark: str | None = None
|
||||
order: int | None = None
|
||||
dict_type_id: int
|
||||
|
||||
|
||||
class DictDetailsSimpleOut(DictDetails):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
29
apps/vadmin/system/schemas/settings.py
Normal file
29
apps/vadmin/system/schemas/settings.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : settings.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class Settings(BaseModel):
|
||||
config_label: str | None = None
|
||||
config_key: str
|
||||
config_value: str | None = None
|
||||
remark: str | None = None
|
||||
disabled: bool | None = None
|
||||
tab_id: int
|
||||
|
||||
|
||||
class SettingsSimpleOut(Settings):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
28
apps/vadmin/system/schemas/settings_tab.py
Normal file
28
apps/vadmin/system/schemas/settings_tab.py
Normal file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/18 22:19
|
||||
# @File : settings_tab.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : pydantic 模型,用于数据库序列化操作
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from core.data_types import DatetimeStr
|
||||
|
||||
|
||||
class SettingsTab(BaseModel):
|
||||
title: str
|
||||
classify: str
|
||||
tab_label: str
|
||||
tab_name: str
|
||||
hidden: bool
|
||||
|
||||
|
||||
class SettingsTabSimpleOut(SettingsTab):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
|
32
apps/vadmin/system/schemas/task.py
Normal file
32
apps/vadmin/system/schemas/task.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2023/6/25 15:08
|
||||
# @File : task.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from core.data_types import DatetimeStr, ObjectIdStr
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
name: str
|
||||
group: str | None = None
|
||||
job_class: str
|
||||
exec_strategy: str
|
||||
expression: str
|
||||
is_active: bool | None = True # 临时字段,不在表中创建
|
||||
remark: str | None = None
|
||||
start_date: DatetimeStr | None = None
|
||||
end_date: DatetimeStr | None = None
|
||||
|
||||
|
||||
class TaskSimpleOut(Task):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: ObjectIdStr = Field(..., alias='_id')
|
||||
create_datetime: DatetimeStr
|
||||
update_datetime: DatetimeStr
|
||||
last_run_datetime: DatetimeStr | None = None # 临时字段,不在表中创建
|
||||
|
263
apps/vadmin/system/views.py
Normal file
263
apps/vadmin/system/views.py
Normal file
@ -0,0 +1,263 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2021/10/24 16:44
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 主要接口文件
|
||||
|
||||
from redis.asyncio import Redis
|
||||
from fastapi import APIRouter, Depends, Body, UploadFile, Form, Request
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from application.settings import ALIYUN_OSS
|
||||
from core.database import db_getter, redis_getter, mongo_getter
|
||||
from utils.file.aliyun_oss import AliyunOSS, BucketConf
|
||||
from utils.file.file_manage import FileManage
|
||||
from utils.response import SuccessResponse, ErrorResponse
|
||||
from utils.sms.code import CodeSMS
|
||||
from . import schemas, crud
|
||||
from core.dependencies import IdList
|
||||
from apps.vadmin.auth.utils.current import AllUserAuth, FullAdminAuth, OpenAuth
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
from .params import DictTypeParams, DictDetailParams, TaskParams
|
||||
from apps.vadmin.auth import crud as vadmin_auth_crud
|
||||
from .params.task import TaskRecordParams
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 字典类型管理
|
||||
###########################################################
|
||||
@app.get("/dict/types", summary="获取字典类型列表")
|
||||
async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
datas, count = await crud.DictTypeDal(auth.db).get_datas(**p.dict(), v_return_count=True)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.post("/dict/types", summary="创建字典类型")
|
||||
async def create_dict_types(data: schemas.DictType, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.DictTypeDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.delete("/dict/types", summary="批量删除字典类型")
|
||||
async def delete_dict_types(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
await crud.DictTypeDal(auth.db).delete_datas(ids=ids.ids)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.post("/dict/types/details", summary="获取多个字典类型下的字典元素列表")
|
||||
async def post_dicts_details(
|
||||
auth: Auth = Depends(AllUserAuth()),
|
||||
dict_types: list[str] = Body(None, title="字典元素列表", description="查询字典元素列表")
|
||||
):
|
||||
datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types)
|
||||
return SuccessResponse(datas)
|
||||
|
||||
|
||||
@app.get("/dict/types/options", summary="获取字典类型选择项")
|
||||
async def get_dicts_options(auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.DictTypeDal(auth.db).get_select_datas())
|
||||
|
||||
|
||||
@app.put("/dict/types/{data_id}", summary="更新字典类型")
|
||||
async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.DictTypeDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/dict/types/{data_id}", summary="获取字典类型详细")
|
||||
async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
||||
schema = schemas.DictTypeSimpleOut
|
||||
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, v_schema=schema))
|
||||
|
||||
|
||||
###########################################################
|
||||
# 字典元素管理
|
||||
###########################################################
|
||||
@app.post("/dict/details", summary="创建字典元素")
|
||||
async def create_dict_details(data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.DictDetailsDal(auth.db).create_data(data=data))
|
||||
|
||||
|
||||
@app.get("/dict/details", summary="获取单个字典类型下的字典元素列表,分页")
|
||||
async def get_dict_details(params: DictDetailParams = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
datas, count = await crud.DictDetailsDal(auth.db).get_datas(**params.dict(), v_return_count=True)
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.delete("/dict/details", summary="批量删除字典元素", description="硬删除")
|
||||
async def delete_dict_details(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
|
||||
await crud.DictDetailsDal(auth.db).delete_datas(ids.ids, v_soft=False)
|
||||
return SuccessResponse("删除成功")
|
||||
|
||||
|
||||
@app.put("/dict/details/{data_id}", summary="更新字典元素")
|
||||
async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.DictDetailsDal(auth.db).put_data(data_id, data))
|
||||
|
||||
|
||||
@app.get("/dict/details/{data_id}", summary="获取字典元素详情")
|
||||
async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
|
||||
schema = schemas.DictDetailsSimpleOut
|
||||
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, v_schema=schema))
|
||||
|
||||
|
||||
###########################################################
|
||||
# 文件上传管理
|
||||
###########################################################
|
||||
@app.post("/upload/image/to/oss", summary="上传图片到阿里云OSS")
|
||||
async def upload_image_to_oss(file: UploadFile, path: str = Form(...)):
|
||||
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(path, file)
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
@app.post("/upload/video/to/oss", summary="上传视频到阿里云OSS")
|
||||
async def upload_video_to_oss(file: UploadFile, path: str = Form(...)):
|
||||
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_video(path, file)
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
@app.post("/upload/file/to/oss", summary="上传文件到阿里云OSS")
|
||||
async def upload_file_to_oss(file: UploadFile, path: str = Form(...)):
|
||||
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_file(path, file)
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
@app.post("/upload/image/to/local", summary="上传图片到本地")
|
||||
async def upload_image_to_local(file: UploadFile, path: str = Form(...)):
|
||||
manage = FileManage(file, path)
|
||||
path = await manage.save_image_local()
|
||||
return SuccessResponse(path)
|
||||
|
||||
|
||||
###########################################################
|
||||
# 短信服务管理
|
||||
###########################################################
|
||||
@app.post("/sms/send", summary="发送短信验证码(阿里云服务)")
|
||||
async def sms_send(telephone: str, rd: Redis = Depends(redis_getter), auth: Auth = Depends(OpenAuth())):
|
||||
user = await vadmin_auth_crud.UserDal(auth.db).get_data(telephone=telephone, v_return_none=True)
|
||||
if not user:
|
||||
return ErrorResponse("手机号不存在!")
|
||||
sms = CodeSMS(telephone, rd)
|
||||
return SuccessResponse(await sms.main_async())
|
||||
|
||||
|
||||
###########################################################
|
||||
# 系统配置管理
|
||||
###########################################################
|
||||
@app.post("/settings/tabs", summary="获取系统配置标签列表")
|
||||
async def get_settings_tabs(classifys: list[str] = Body(...), auth: Auth = Depends(FullAdminAuth())):
|
||||
return SuccessResponse(await crud.SettingsTabDal(auth.db).get_datas(limit=0, classify=("in", classifys)))
|
||||
|
||||
|
||||
@app.get("/settings/tabs/values", summary="获取系统配置标签下的信息")
|
||||
async def get_settings_tabs_values(tab_id: int, auth: Auth = Depends(FullAdminAuth())):
|
||||
return SuccessResponse(await crud.SettingsDal(auth.db).get_tab_values(tab_id=tab_id))
|
||||
|
||||
|
||||
@app.put("/settings/tabs/values", summary="更新系统配置信息")
|
||||
async def put_settings_tabs_values(
|
||||
request: Request,
|
||||
datas: dict = Body(...),
|
||||
auth: Auth = Depends(FullAdminAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.SettingsDal(auth.db).update_datas(datas, request))
|
||||
|
||||
|
||||
@app.get("/settings/base/config", summary="获取系统基础配置", description="每次进入系统中时使用")
|
||||
async def get_setting_base_config(db: AsyncSession = Depends(db_getter)):
|
||||
return SuccessResponse(await crud.SettingsDal(db).get_base_config())
|
||||
|
||||
|
||||
@app.get("/settings/privacy", summary="获取隐私协议")
|
||||
async def get_settings_privacy(auth: Auth = Depends(OpenAuth())):
|
||||
return SuccessResponse((await crud.SettingsDal(auth.db).get_data(config_key="web_privacy")).config_value)
|
||||
|
||||
|
||||
@app.get("/settings/agreement", summary="获取用户协议")
|
||||
async def get_settings_agreement(auth: Auth = Depends(OpenAuth())):
|
||||
return SuccessResponse((await crud.SettingsDal(auth.db).get_data(config_key="web_agreement")).config_value)
|
||||
|
||||
|
||||
###########################################################
|
||||
# 定时任务管理
|
||||
###########################################################
|
||||
@app.get("/tasks", summary="获取定时任务列表")
|
||||
async def get_tasks(
|
||||
p: TaskParams = Depends(),
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
datas, count = await crud.TaskDal(db).get_tasks(**p.dict())
|
||||
return SuccessResponse(datas, count=count)
|
||||
|
||||
|
||||
@app.post("/tasks", summary="添加定时任务")
|
||||
async def post_tasks(
|
||||
data: schemas.Task,
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
rd: Redis = Depends(redis_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.TaskDal(db).create_task(rd, data))
|
||||
|
||||
|
||||
@app.put("/tasks", summary="更新定时任务")
|
||||
async def put_tasks(
|
||||
_id: str,
|
||||
data: schemas.Task,
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
rd: Redis = Depends(redis_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.TaskDal(db).put_task(rd, _id, data))
|
||||
|
||||
|
||||
@app.delete("/tasks", summary="删除单个定时任务")
|
||||
async def delete_task(
|
||||
_id: str,
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.TaskDal(db).delete_task(_id))
|
||||
|
||||
|
||||
@app.get("/task", summary="获取定时任务详情")
|
||||
async def get_task(
|
||||
_id: str,
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.TaskDal(db).get_task(_id, v_schema=schemas.TaskSimpleOut))
|
||||
|
||||
|
||||
@app.post("/task", summary="执行一次定时任务")
|
||||
async def run_once_task(
|
||||
_id: str,
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
rd: Redis = Depends(redis_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
return SuccessResponse(await crud.TaskDal(db).run_once_task(rd, _id))
|
||||
|
||||
|
||||
###########################################################
|
||||
# 定时任务分组管理
|
||||
###########################################################
|
||||
@app.get("/task/group/options", summary="获取定时任务分组选择项列表")
|
||||
async def get_task_group_options(db: AsyncIOMotorDatabase = Depends(mongo_getter), auth: Auth = Depends(AllUserAuth())):
|
||||
return SuccessResponse(await crud.TaskGroupDal(db).get_datas(limit=0))
|
||||
|
||||
|
||||
###########################################################
|
||||
# 定时任务调度日志
|
||||
###########################################################
|
||||
@app.get("/task/records", summary="获取定时任务调度日志列表")
|
||||
async def get_task_records(
|
||||
p: TaskRecordParams = Depends(),
|
||||
db: AsyncIOMotorDatabase = Depends(mongo_getter),
|
||||
auth: Auth = Depends(AllUserAuth())
|
||||
):
|
||||
count = await crud.TaskRecordDal(db).get_count(**p.to_count())
|
||||
datas = await crud.TaskRecordDal(db).get_datas(**p.dict())
|
||||
return SuccessResponse(datas, count=count)
|
7
apps/vadmin/workplace/__init__.py
Normal file
7
apps/vadmin/workplace/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/10/19 15:41
|
||||
# @File : __init__.py.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
159
apps/vadmin/workplace/views.py
Normal file
159
apps/vadmin/workplace/views.py
Normal file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @version : 1.0
|
||||
# @Create Time : 2022/10/19 15:41
|
||||
# @File : views.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from apps.vadmin.auth.utils.current import AllUserAuth
|
||||
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||
from utils.response import SuccessResponse
|
||||
import datetime
|
||||
from apps.vadmin.record.crud import LoginRecordDal
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
###########################################################
|
||||
# 工作区管理
|
||||
###########################################################
|
||||
@app.get("/project", summary="获取项目")
|
||||
async def get_project():
|
||||
data = [
|
||||
{
|
||||
"name": 'Mysql',
|
||||
"icon": 'vscode-icons:file-type-mysql',
|
||||
"message": '最流行的关系型数据库管理系统',
|
||||
"personal": 'kinit',
|
||||
"link": "https://www.mysql.com/",
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
},
|
||||
{
|
||||
"name": 'FastAPI',
|
||||
"icon": 'simple-icons:fastapi',
|
||||
"message": '一个现代、快速(高性能)的 web 框架',
|
||||
"personal": 'kinit',
|
||||
"link": "https://fastapi.tiangolo.com/zh/",
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
},
|
||||
{
|
||||
"name": 'Vue',
|
||||
"icon": 'logos:vue',
|
||||
"message": '渐进式 JavaScript 框架',
|
||||
"personal": 'kinit',
|
||||
"link": "https://cn.vuejs.org/",
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
},
|
||||
{
|
||||
"name": 'Element-plus',
|
||||
"icon": 'logos:element',
|
||||
"message": '面向设计师和开发者的组件库',
|
||||
"personal": 'kinit',
|
||||
"link": "https://element-plus.org/zh-CN/",
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
},
|
||||
{
|
||||
"name": 'Typescript',
|
||||
"icon": 'vscode-icons:file-type-typescript-official',
|
||||
"message": 'TypeScript是JavaScript类型的超集',
|
||||
"personal": 'kinit',
|
||||
"link": "https://www.typescriptlang.org/",
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
},
|
||||
{
|
||||
"name": 'Vite',
|
||||
"icon": 'vscode-icons:file-type-vite',
|
||||
"message": 'Vite 下一代的前端工具链',
|
||||
"personal": 'kinit',
|
||||
"link": "https://cn.vitejs.dev/",
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
}
|
||||
]
|
||||
return SuccessResponse(data)
|
||||
|
||||
|
||||
@app.get("/dynamic", summary="获取动态")
|
||||
async def get_dynamic():
|
||||
data = [
|
||||
{
|
||||
"keys": ['workplace.push', 'Github'],
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
},
|
||||
{
|
||||
"keys": ['workplace.push', 'Github'],
|
||||
"time": datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
}
|
||||
]
|
||||
return SuccessResponse(data)
|
||||
|
||||
|
||||
@app.get("/team", summary="获取团队信息")
|
||||
async def get_team():
|
||||
data = [
|
||||
{
|
||||
"name": 'Mysql',
|
||||
"icon": 'vscode-icons:file-type-mysql'
|
||||
},
|
||||
{
|
||||
"name": 'Vue',
|
||||
"icon": 'logos:vue'
|
||||
},
|
||||
{
|
||||
"name": 'Element-plus',
|
||||
"icon": 'logos:element'
|
||||
},
|
||||
{
|
||||
"name": 'Fastapi',
|
||||
"icon": 'simple-icons:fastapi'
|
||||
},
|
||||
{
|
||||
"name": 'Typescript',
|
||||
"icon": 'vscode-icons:file-type-typescript-official'
|
||||
},
|
||||
{
|
||||
"name": 'Vite',
|
||||
"icon": 'vscode-icons:file-type-vite'
|
||||
}
|
||||
]
|
||||
return SuccessResponse(data)
|
||||
|
||||
|
||||
@app.get("/shortcuts", summary="获取快捷操作")
|
||||
async def get_shortcuts():
|
||||
data = [
|
||||
{
|
||||
"name": "Gitee 项目仓库",
|
||||
"link": "https://gitee.com/ktianc/kinit"
|
||||
},
|
||||
{
|
||||
"name": "GitHub 项目仓库",
|
||||
"link": "https://github.com/vvandk/kinit"
|
||||
},
|
||||
{
|
||||
"name": "前端文档",
|
||||
"link": "https://element-plus-admin-doc.cn/"
|
||||
},
|
||||
{
|
||||
"name": "Swagger UI 接口文档",
|
||||
"link": "http://kinit.ktianc.top/api/docs"
|
||||
},
|
||||
{
|
||||
"name": "Redoc 接口文档",
|
||||
"link": "http://kinit.ktianc.top/api/redoc"
|
||||
},
|
||||
{
|
||||
"name": "UnoCSS 中文文档",
|
||||
"link": "https://unocss.nodejs.cn/guide/"
|
||||
},
|
||||
{
|
||||
"name": "Iconify 文档",
|
||||
"link": "https://icon-sets.iconify.design/"
|
||||
},
|
||||
{
|
||||
"name": "echarts 文档",
|
||||
"link": "https://echarts.apache.org/zh/index.html"
|
||||
},
|
||||
]
|
||||
return SuccessResponse(data)
|
Reference in New Issue
Block a user