项目初次提交
This commit is contained in:
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())
|
Reference in New Issue
Block a user