diff --git a/main.py b/main.py index bfcb822..ea387cc 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ from aioredis import create_redis_pool from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from routers import point +from routers import point, user, event from settings import settings from utils.ta_sdk import TGAnalytics, ToKafka @@ -36,6 +36,8 @@ def register_ta(app: FastAPI) -> None: app.include_router(point.router, prefix='/v1') +app.include_router(user.router, prefix='/v1') +app.include_router(event.router, prefix='/v1') register_redis(app) register_ta(app) diff --git a/models/__init__.py b/models/__init__.py index 82da278..064a3bc 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1 +1,2 @@ -from .user import * \ No newline at end of file +from .user import * +from .event import * \ No newline at end of file diff --git a/models/base.py b/models/base.py index 9bab4ed..034d9f7 100644 --- a/models/base.py +++ b/models/base.py @@ -1,4 +1,6 @@ import hashlib +from enum import Enum + from pydantic import Field from pydantic import BaseModel, validator @@ -8,8 +10,52 @@ from settings import settings from ipaddress import IPv4Address FIELD_MAP = { + 'user_id': 'x01', + 'account_id': 'x02', + 'distinct_id': 'x03', + 'event_name': 'x04', + 'server_time':'x05', + 'ip': 'a01', + 'country': 'a02', + 'country_code': 'a03', + 'province': 'a04', + 'city': 'a05', + 'os_version': 'a06', + 'manufacturer': 'a07', 'os': 'a08', + 'device_id': 'a09', + 'screen_height': 'a10', + 'screen_width': 'a11', + 'device_model': 'a12', + 'app_version': 'a13', + 'bundle_id': 'a14', + 'lib': 'a15', + 'lib_version': 'a16', + 'network_type': 'a17', + 'carrier': 'a18', + 'browser': 'a19', + 'browser_version': 'a20', + 'duration': 'a21', + 'url': 'a22', + 'url_path': 'a23', + 'referrer': 'a24', + 'referrer_host': 'a25', + 'title': 'a26', + 'screen_name': 'a27', + 'element_id': 'a28', + 'element_type': 'a29', + 'resume_from_background': 'a30', + 'element_selector': 'a31', + 'element_position': 'a32', + 'element_content': 'a33', + 'scene': 'a34', + 'mp_platform': 'a35', + 'app_crashed_reason': 'a36', + 'zone_offset': 'a37', + + 'app_id':'b01', + 'event_time':'b06' } @@ -29,21 +75,33 @@ class IP4(str): return str(v) +class ActEnum(str, Enum): + track = 'track' + user_set = 'user_set' + user_setOnce = 'user_setOnce' + user_add = 'user_add' + user_unset = 'user_unset' + user_append = 'user_append' + user_del = 'user_del' + + class Base(BaseModel): - # sign = md5(distinct_id+account_id+act+ts+salt) - distinct_id: str = Field(..., title='访客 ID') - game: str - account_id: str - act: str - event_name: str = None - # preset: dict - properties: dict - ts: int - sign: str + # sign = md5(game+act+ts+salt) + game: str = Field(..., title='游戏代号') + act: ActEnum = Field(..., title='操作', description='同ta一致') + preset: BaseModel + properties: dict = Field(..., title='自定义属性') + ts: int = Field(..., title='时间戳') + sign: str = Field(..., title='签名') @validator('sign') def sign_validator(cls, v: str, values: dict): - s = f'{values.get("distinct_id")}{values.get("account_id", "")}{values.get("act", "")}{values.get("ts", "")}{settings.SALT}' + s = f'{values.get("game", "")}{values.get("act", "")}{values.get("ts", "")}{settings.SALT}' if hashlib.md5(s.encode()).hexdigest() == v: return v - raise ValueError(f'打击违法犯罪行为{hashlib.md5(s.encode()).hexdigest()}') + raise ValueError(f'sign {hashlib.md5(s.encode()).hexdigest()}') + + def dict(self, **kwargs): + kwargs.setdefault('exclude', {'preset', 'account_id', 'distinct_id', 'event_name'}) + self.properties.update(self.preset.dict(**kwargs)) + return super().dict(**kwargs) diff --git a/models/event.py b/models/event.py index e69de29..c89d690 100644 --- a/models/event.py +++ b/models/event.py @@ -0,0 +1,70 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + +from .base import Base, IP4, to_alias + +__all__ = ('EventModel',) + + +class Preset(BaseModel): + ip: IP4 = Field(None, title='ipv4',description='不传该字段默认使用源ip') + country: str = Field(None, title='国家',description='') + country_code: str = Field(None, title='国家代码',description='') + province: str = Field(None, title='省份',description='') + city: str = Field(None, title='城市',description='') + os_version: str = Field(None, title='操作系统版本',description='') + manufacturer: str = Field(None, title='设备制造商',description='') + os: str = Field(None, title='操作系统',description='') + device_id: str = Field(None, title='设备 ID',description='') + screen_height: int = Field(None, title='屏幕高度',description='') + screen_width: int = Field(None, title='屏幕宽度',description='') + device_model: str = Field(None, title='设备型号',description='') + app_version: str = Field(None, title='APP 版本',description='') + bundle_id: str = Field(None, title='APP包名',description='') + lib: str = Field(None, title='SDK 类型',description='') + lib_version: str = Field(None, title='SDK 版本',description='') + network_type: str = Field(None, title='网络状态',description='') + carrier: str = Field(None, title='网络运营商',description='') + browser: str = Field(None, title='浏览器类型',description='') + browser_version: str = Field(None, title='浏览器版本',description='') + duration: int = Field(None, title='事件时长',description='') + url: str = Field(None, title='页面地址',description='') + url_path: str = Field(None, title='页面路径',description='') + referrer: str = Field(None, title='前向地址',description='') + referrer_host: str = Field(None, title='前向路径',description='') + title: str = Field(None, title='页面标题',description='') + screen_name: str = Field(None, title='页面名称',description='') + element_id: str = Field(None, title='元素 ID',description='') + element_type: str = Field(None, title='元素类型',description='') + resume_from_background: str = Field(None, title='是否从后台唤醒',description='') + element_selector: str = Field(None, title='元素选择器',description='') + element_position: str = Field(None, title='元素位置',description='') + element_content: str = Field(None, title='元素内容',description='') + scene: str = Field(None, title='场景值',description='') + mp_platform: str = Field(None, title='小程序平台',description='') + app_crashed_reason: str = Field(None, title='异常信息',description='') + zone_offset: str = Field(None, title='时区偏移',description='') + + user_id: str = Field(..., title='用户唯一 ID',description='') + account_id: str = Field(..., title='账户 ID', description='') + distinct_id: str = Field(..., title='访客 ID',description='') + event_name: str = Field(..., title='事件名称',description='') + + # 事件 + app_id: str = Field(None, description='') + event_time: datetime = Field(None,title='事件时间', description='') + server_time: datetime = Field(None,title='服务端时间', description='') + + + def dict(self, **kwargs): + res = super().dict(**kwargs) + return {'#' + k: v for k, v in res.items() if v is not None} + + class Config: + alias_generator = to_alias + + +class EventModel(Base): + preset: Preset = Field(..., title='系统属性') + diff --git a/models/user.py b/models/user.py index 4196333..3877870 100644 --- a/models/user.py +++ b/models/user.py @@ -1,5 +1,6 @@ -from pydantic import BaseModel +from datetime import datetime +from pydantic import BaseModel, Field from .base import Base, IP4, to_alias @@ -7,9 +8,51 @@ __all__ = ('UserModel',) class Preset(BaseModel): - ip: IP4 - os: str - ttt: str = None + ip: IP4 = Field(None, title='ipv4', description='不传该字段默认使用源ip') + country: str = Field(None, title='国家', description='') + country_code: str = Field(None, title='国家代码', description='') + province: str = Field(None, title='省份', description='') + city: str = Field(None, title='城市', description='') + os_version: str = Field(None, title='操作系统版本', description='') + manufacturer: str = Field(None, title='设备制造商', description='') + os: str = Field(None, title='操作系统', description='') + device_id: str = Field(None, title='设备 ID', description='') + screen_height: int = Field(None, title='屏幕高度', description='') + screen_width: int = Field(None, title='屏幕宽度', description='') + device_model: str = Field(None, title='设备型号', description='') + app_version: str = Field(None, title='APP 版本', description='') + bundle_id: str = Field(None, title='APP包名', description='') + lib: str = Field(None, title='SDK 类型', description='') + lib_version: str = Field(None, title='SDK 版本', description='') + network_type: str = Field(None, title='网络状态', description='') + carrier: str = Field(None, title='网络运营商', description='') + browser: str = Field(None, title='浏览器类型', description='') + browser_version: str = Field(None, title='浏览器版本', description='') + duration: int = Field(None, title='事件时长', description='') + url: str = Field(None, title='页面地址', description='') + url_path: str = Field(None, title='页面路径', description='') + referrer: str = Field(None, title='前向地址', description='') + referrer_host: str = Field(None, title='前向路径', description='') + title: str = Field(None, title='页面标题', description='') + screen_name: str = Field(None, title='页面名称', description='') + element_id: str = Field(None, title='元素 ID', description='') + element_type: str = Field(None, title='元素类型', description='') + resume_from_background: str = Field(None, title='是否从后台唤醒', description='') + element_selector: str = Field(None, title='元素选择器', description='') + element_position: str = Field(None, title='元素位置', description='') + element_content: str = Field(None, title='元素内容', description='') + scene: str = Field(None, title='场景值', description='') + mp_platform: str = Field(None, title='小程序平台', description='') + app_crashed_reason: str = Field(None, title='异常信息', description='') + zone_offset: str = Field(None, title='时区偏移', description='') + + user_id: str = Field(..., title='用户唯一 ID', description='') + account_id: str = Field(..., title='账户 ID', description='') + distinct_id: str = Field(..., title='访客 ID', description='') + # event_name: str = Field(..., title='事件名称',description='') + + # 用户 + server_time: datetime = Field(None, title='服务端时间', description='') def dict(self, **kwargs): res = super().dict(**kwargs) @@ -20,9 +63,4 @@ class Preset(BaseModel): class UserModel(Base): - preset: Preset - - def dict(self, **kwargs): - kwargs.setdefault('exclude', {'preset'}) - self.properties.update(self.preset.dict(**kwargs)) - return super().dict(**kwargs) + preset: Preset = Field(..., title='系统属性') diff --git a/routers/event.py b/routers/event.py index e69de29..1bf4b3d 100644 --- a/routers/event.py +++ b/routers/event.py @@ -0,0 +1,23 @@ +import asyncio + +from fastapi import APIRouter, Request + +from handler_data import HandlerEvent + +router = APIRouter() +from models import EventModel + + +@router.post("/event/") +async def event(request: Request, item: EventModel): + item.preset.ip = item.preset.ip or request.client.host + ta = getattr(request.app.state.ta, item.act) + # 将不同游戏发送到不同 topic_name + request.app.state.ta.consumer.topic_name = item.game + rdb = request.app.state.redis + await asyncio.gather(*map(lambda o: asyncio.create_task(o(rdb, item)), HandlerEvent.handler_link)) + + properties = item.dict()['properties'] + ta(item.preset.distinct_id, item.preset.account_id, item.preset.event_name, properties) + results = {"code": 0, 'msg': 'ok'} + return results diff --git a/routers/point.py b/routers/point.py index 53dbafa..8e5d8fe 100644 --- a/routers/point.py +++ b/routers/point.py @@ -11,7 +11,7 @@ router = APIRouter() class Item(BaseModel): - # sign = md5(distinct_id+account_id+act+ts+salt) + # sign = md5(game+act+ts+salt) distinct_id: str game: str account_id: str @@ -23,14 +23,15 @@ class Item(BaseModel): @validator('sign') def sign_validator(cls, v: str, values: dict): - s = f'{values.get("distinct_id")}{values.get("account_id", "")}{values.get("act", "")}{values.get("ts", "")}{settings.SALT}' + s = f'{values.get("game")}{values.get("act", "")}{values.get("ts", "")}{settings.SALT}' if hashlib.md5(s.encode()).hexdigest() == v: return v - raise ValueError(f'打击违法犯罪行为{hashlib.md5(s.encode()).hexdigest()}') + raise ValueError(f'签名 {hashlib.md5(s.encode()).hexdigest()}') @router.post("/point/") async def point(request: Request, item: Item): + ip = request.client.host ta = getattr(request.app.state.ta, item.act) # 将不同游戏发送到不同 topic_name request.app.state.ta.consumer.topic_name = item.game @@ -42,7 +43,7 @@ async def point(request: Request, item: Item): else: await asyncio.gather(*map(lambda o: asyncio.create_task(o(rdb, item)), HandlerUser.handler_link)) await user_set(ta, item) - results = {"code": 0, 'msg': 'ok'} + results = {"code": 0, 'msg': 'ok','ip':ip} return results diff --git a/routers/user.py b/routers/user.py index 5b8d4e6..cd219f5 100644 --- a/routers/user.py +++ b/routers/user.py @@ -1,11 +1,8 @@ import asyncio -import hashlib from fastapi import APIRouter, Request -from pydantic import BaseModel, validator -from handler_data import HandlerUser, HandlerEvent -from settings import settings +from handler_data import HandlerUser router = APIRouter() from models import UserModel @@ -13,17 +10,13 @@ from models import UserModel @router.post("/user/") async def user(request: Request, item: UserModel): + item.preset.ip = item.preset.ip or request.client.host ta = getattr(request.app.state.ta, item.act) # 将不同游戏发送到不同 topic_name request.app.state.ta.consumer.topic_name = item.game rdb = request.app.state.redis await asyncio.gather(*map(lambda o: asyncio.create_task(o(rdb, item)), HandlerUser.handler_link)) - - ta(item.distinct_id, item.account_id, item.properties) + properties = item.dict()['properties'] + ta(item.preset.distinct_id, item.preset.account_id, properties) results = {"code": 0, 'msg': 'ok'} return results - - - - - diff --git a/settings.py b/settings.py index 2e6afbd..165ea17 100644 --- a/settings.py +++ b/settings.py @@ -1,6 +1,3 @@ -import json - - class Config: REDIS_CONF = { 'address': ('192.168.0.161', 6379),