项目初次提交

This commit is contained in:
2025-04-11 08:54:28 +08:00
commit 9e14a3256f
220 changed files with 15673 additions and 0 deletions

View File

View 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

View 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

View 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()

View 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="发送场景")

View File

@ -0,0 +1,3 @@
from .login import LoginParams
from .operation import OperationParams
from .sms import SMSParams

View 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"

View 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"

View 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)

View File

@ -0,0 +1,3 @@
from .login import LoginRecord, LoginRecordSimpleOut
from .sms import SMSSendRecord, SMSSendRecordSimpleOut
from .operation import OperationRecord, OperationRecordSimpleOut

View 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

View 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

View 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)

View 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())