commit cb1a26b2705a5ceef54fb184053a1f1b684b2cce
Author: Cxx0822 <1556464090@qq.com>
Date: Sun Oct 19 22:16:27 2025 +0800
feat:初始化工程
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/log-alert-service.iml b/.idea/log-alert-service.iml
new file mode 100644
index 0000000..74d515a
--- /dev/null
+++ b/.idea/log-alert-service.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..f447362
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..d88adc6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc
new file mode 100644
index 0000000..1c2a5f3
Binary files /dev/null and b/__pycache__/main.cpython-312.pyc differ
diff --git a/apis/__init__.py b/apis/__init__.py
new file mode 100644
index 0000000..e751abf
--- /dev/null
+++ b/apis/__init__.py
@@ -0,0 +1,6 @@
+from fastapi import FastAPI
+from .log import router as log_router
+
+
+def register_routers(app: FastAPI):
+ app.include_router(log_router, prefix="")
diff --git a/apis/__pycache__/__init__.cpython-312.pyc b/apis/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..1247962
Binary files /dev/null and b/apis/__pycache__/__init__.cpython-312.pyc differ
diff --git a/apis/__pycache__/log.cpython-312.pyc b/apis/__pycache__/log.cpython-312.pyc
new file mode 100644
index 0000000..703c24d
Binary files /dev/null and b/apis/__pycache__/log.cpython-312.pyc differ
diff --git a/apis/log.py b/apis/log.py
new file mode 100644
index 0000000..1bfcc1d
--- /dev/null
+++ b/apis/log.py
@@ -0,0 +1,15 @@
+from fastapi import APIRouter
+
+from schemas.log_alert import LogAlertTrigger
+from service import log_alert_service
+
+router = APIRouter(
+ prefix="/log-alter",
+ tags=["日志告警"],
+ responses={404: {"description": "Not found"}}
+)
+
+
+@router.post("", summary="触发日志告警")
+def trigger_log_alter(log_alter: LogAlertTrigger):
+ return log_alert_service.trigger_alert(log_alter)
diff --git a/config/__pycache__/database.cpython-312.pyc b/config/__pycache__/database.cpython-312.pyc
new file mode 100644
index 0000000..bedea5f
Binary files /dev/null and b/config/__pycache__/database.cpython-312.pyc differ
diff --git a/config/__pycache__/setting.cpython-312.pyc b/config/__pycache__/setting.cpython-312.pyc
new file mode 100644
index 0000000..ca8ddbf
Binary files /dev/null and b/config/__pycache__/setting.cpython-312.pyc differ
diff --git a/config/database.py b/config/database.py
new file mode 100644
index 0000000..3440129
--- /dev/null
+++ b/config/database.py
@@ -0,0 +1,26 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+from config.setting import settings
+from models.log_alert import Base
+
+engine = create_engine(
+ settings.database_url, connect_args={"check_same_thread": False}
+)
+
+# 会话工厂
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+def init_db():
+ Base.metadata.create_all(bind=engine)
+
+
+def get_db():
+ # 创建数据库会话实例
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
diff --git a/config/setting.py b/config/setting.py
new file mode 100644
index 0000000..1ee8784
--- /dev/null
+++ b/config/setting.py
@@ -0,0 +1,18 @@
+from pydantic_settings import BaseSettings
+
+
+class Settings(BaseSettings):
+ # sqlite连接方式:sqlite:///
+ database_url: str = "sqlite:///./logs.db"
+ wechat_webhook: str = ""
+ smtp_server: str = ""
+ smtp_port: int = 587
+ smtp_user: str = ""
+ smtp_password: str = ""
+ smtp_sender: str = "log-alert@example.com"
+
+ class Config:
+ env_file = ".env"
+
+
+settings = Settings()
\ No newline at end of file
diff --git a/id_generator/__init__.py b/id_generator/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/id_generator/__pycache__/__init__.cpython-312.pyc b/id_generator/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..934c604
Binary files /dev/null and b/id_generator/__pycache__/__init__.cpython-312.pyc differ
diff --git a/id_generator/__pycache__/generator.cpython-312.pyc b/id_generator/__pycache__/generator.cpython-312.pyc
new file mode 100644
index 0000000..3ef7f83
Binary files /dev/null and b/id_generator/__pycache__/generator.cpython-312.pyc differ
diff --git a/id_generator/__pycache__/options.cpython-312.pyc b/id_generator/__pycache__/options.cpython-312.pyc
new file mode 100644
index 0000000..781eaf9
Binary files /dev/null and b/id_generator/__pycache__/options.cpython-312.pyc differ
diff --git a/id_generator/__pycache__/snowflake.cpython-312.pyc b/id_generator/__pycache__/snowflake.cpython-312.pyc
new file mode 100644
index 0000000..6fca833
Binary files /dev/null and b/id_generator/__pycache__/snowflake.cpython-312.pyc differ
diff --git a/id_generator/__pycache__/snowflake_m1.cpython-312.pyc b/id_generator/__pycache__/snowflake_m1.cpython-312.pyc
new file mode 100644
index 0000000..9e662f0
Binary files /dev/null and b/id_generator/__pycache__/snowflake_m1.cpython-312.pyc differ
diff --git a/id_generator/generator.py b/id_generator/generator.py
new file mode 100644
index 0000000..fc995d7
--- /dev/null
+++ b/id_generator/generator.py
@@ -0,0 +1,38 @@
+"""
+雪花算法生成器IdGenerator
+"""
+
+# !/usr/bin/python
+# coding=UTF-8
+
+
+from . import options
+from . import snowflake_m1
+
+
+class DefaultIdGenerator:
+ """
+ ID生成器
+ """
+
+ def __init__(self):
+ self.snowflake = None
+
+ def set_id_generator(self, option: options.IdGeneratorOptions):
+ """
+ 设置id生成规则信息
+ """
+
+ if option.base_time < 100000:
+ raise ValueError("base time error.")
+
+ self.snowflake = snowflake_m1.SnowFlakeM1(option)
+
+ def next_id(self) -> int:
+ """
+ 获取新的UUID
+ """
+
+ if self.snowflake is None:
+ raise ValueError("please set id generator at first.")
+ return self.snowflake.next_id()
diff --git a/id_generator/idregister.py b/id_generator/idregister.py
new file mode 100644
index 0000000..787faac
--- /dev/null
+++ b/id_generator/idregister.py
@@ -0,0 +1,134 @@
+"""
+worker id generator
+"""
+
+# !/usr/bin/python
+# coding=UTF-8
+
+
+from threading import Thread
+import time
+import logging
+import redis
+
+
+class Register:
+ """
+ redis封装
+ - host 代表redis ip
+ - port 代表redis端口
+ - max_worker_id worker_id的最大值, 默认为100
+ - password redis的密码, 默认为空
+ """
+
+ def __init__(self, host, port, max_worker_id=100, password=None):
+ self.redis_impl = redis.StrictRedis(host=host, port=port, db=0, password=password)
+ self.loop_count = 0
+ self.max_loop_count = 10
+ self.worker_id_expire_time = 15
+ self.max_worker_id = max_worker_id
+ self.worker_id = -1
+ self.is_stop = False
+
+ def get_lock(self, key):
+ """
+ 获取分布式全局锁,并设置过期时间为30秒
+ """
+
+ if self.redis_impl.setnx(key, 1):
+ self.redis_impl.expire(key, 30)
+ return True
+ if self.redis_impl.ttl(key) < 0:
+ self.redis_impl.expire(key, 30)
+ return False
+
+ def stop(self):
+ """
+ 退出注册器的线程
+ """
+
+ self.is_stop = True
+
+ def get_worker_id(self):
+ """
+ 获取全局唯一worker_id, 会创建一个线程给worker id续期
+ 失败返回-1
+ """
+
+ self.loop_count = 0
+
+ def extern_life(my_id):
+ while 1:
+ time.sleep(self.worker_id_expire_time / 3)
+ # 是否关闭了
+ if self.is_stop:
+ return
+ # 更新生命周期
+ if self.worker_id != my_id:
+ break
+ try:
+ self.redis_impl.expire(
+ f"IdGen:WorkerId:Value:{my_id}",
+ self.worker_id_expire_time)
+ except Exception as exe:
+ logging.error(exe)
+ continue
+
+ self.worker_id = self.__get_next_worker_id()
+ if self.worker_id > -1:
+ Thread(target=extern_life, args=[self.worker_id]).start()
+ return self.worker_id
+
+ def __get_next_worker_id(self):
+ """
+ 获取全局唯一worker id内部实现
+ """
+
+ cur = self.redis_impl.incrby("IdGen:WorkerId:Index", 1)
+
+ def can_reset():
+ try:
+ reset_value = self.redis_impl.incr("IdGen:WorkerId:Value:Edit")
+ return reset_value != 1
+ except Exception as ept:
+ logging.error(ept)
+ return False
+
+ def end_reset():
+ try:
+ self.redis_impl.set("IdGen:WorkerId:Value:Edit", 0)
+ except Exception as ept:
+ logging.error(ept)
+
+ def is_available(worker_id: int):
+ try:
+ rst = self.redis_impl.get(f"IdGen:WorkerId:Value:{worker_id}")
+ return rst != "Y"
+ except Exception as ept:
+ logging.error(ept)
+ return False
+
+ if cur > self.max_worker_id:
+ if can_reset():
+ self.redis_impl.set("IdGen:WorkerId:Index", -1)
+ end_reset()
+ self.loop_count += 1
+
+ if self.loop_count > self.max_loop_count:
+ self.loop_count = 0
+ return -1
+
+ time.sleep(0.2 * self.loop_count)
+ return self.__get_next_worker_id()
+ time.sleep(0.2)
+ return self.__get_next_worker_id()
+ if is_available(cur):
+ self.redis_impl.setex(
+ f"IdGen:WorkerId:Value:{cur}",
+ self.worker_id_expire_time,
+ "Y"
+ )
+ self.loop_count = 0
+ return cur
+
+ return self.__get_next_worker_id()
diff --git a/id_generator/options.py b/id_generator/options.py
new file mode 100644
index 0000000..8558381
--- /dev/null
+++ b/id_generator/options.py
@@ -0,0 +1,43 @@
+"""
+生成器IdGenerator配置选项
+"""
+
+# !/usr/bin/python
+# coding=UTF-8
+
+
+class IdGeneratorOptions:
+ """
+ ID生成器配置
+ - worker_id 全局唯一id, 区分不同uuid生成器实例
+ - worker_id_bit_length 生成的uuid中worker_id占用的位数
+ - seq_bit_length 生成的uuid中序列号占用的位数
+ """
+
+ def __init__(self, worker_id=0, worker_id_bit_length=6, seq_bit_length=6):
+
+ # 雪花计算方法,(1-漂移算法|2-传统算法), 默认1。目前只实现了1。
+ self.method = 1
+
+ # 基础时间(ms单位), 不能超过当前系统时间
+ self.base_time = 1582136402000
+
+ # 机器码, 必须由外部设定, 最大值 2^worker_id_bit_length-1
+ self.worker_id = worker_id
+
+ # 机器码位长, 默认值6, 取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)
+ self.worker_id_bit_length = worker_id_bit_length
+
+ # 序列数位长, 默认值6, 取值范围 [3, 21](要求:序列数位长+机器码位长不超过22)
+ self.seq_bit_length = seq_bit_length
+
+ # 最大序列数(含), 设置范围 [max_seq_number, 2^seq_bit_length-1]
+ # 默认值0, 表示最大序列数取最大值(2^seq_bit_length-1])
+ self.max_seq_number = 0
+
+ # 最小序列数(含), 默认值5, 取值范围 [5, max_seq_number], 每毫秒的前5个序列数对应编号0-4是保留位
+ # 其中1-4是时间回拨相应预留位, 0是手工新值预留位
+ self.min_seq_number = 5
+
+ # 最大漂移次数(含), 默认2000, 推荐范围500-10000(与计算能力有关)
+ self.top_over_cost_count = 2000
diff --git a/id_generator/snowflake.py b/id_generator/snowflake.py
new file mode 100644
index 0000000..ea36cfa
--- /dev/null
+++ b/id_generator/snowflake.py
@@ -0,0 +1,20 @@
+
+"""
+雪花算法生成器接口声明
+"""
+
+# !/usr/bin/python
+# coding=UTF-8
+
+
+class SnowFlake():
+
+ def __init__(self, options):
+ self.options = options
+
+ def next_id(self) -> int:
+ """
+ 获取新的UUID
+ """
+
+ return 0
diff --git a/id_generator/snowflake_m1.py b/id_generator/snowflake_m1.py
new file mode 100644
index 0000000..596b76d
--- /dev/null
+++ b/id_generator/snowflake_m1.py
@@ -0,0 +1,147 @@
+"""
+M1生成器
+"""
+
+# !/usr/bin/python
+# coding=UTF-8
+
+import threading
+import time
+from .snowflake import SnowFlake
+from .options import IdGeneratorOptions
+
+
+class SnowFlakeM1(SnowFlake):
+ """
+ M1规则ID生成器配置
+ """
+
+ def __init__(self, options: IdGeneratorOptions):
+ # 1.base_time
+ self.base_time = 1582136402000
+ if options.base_time != 0:
+ self.base_time = int(options.base_time)
+
+ # 2.worker_id_bit_length
+ self.worker_id_bit_length = 6
+ if options.worker_id_bit_length != 0:
+ self.worker_id_bit_length = int(options.worker_id_bit_length)
+
+ # 3.worker_id
+ self.worker_id = options.worker_id
+
+ # 4.seq_bit_length
+ self.seq_bit_length = 6
+ if options.seq_bit_length != 0:
+ self.seq_bit_length = int(options.seq_bit_length)
+
+ # 5.max_seq_number
+ self.max_seq_number = int(options.max_seq_number)
+ if options.max_seq_number <= 0:
+ self.max_seq_number = (1 << self.seq_bit_length) - 1
+
+ # 6.min_seq_number
+ self.min_seq_number = int(options.min_seq_number)
+
+ # 7.top_over_cost_count
+ self.top_over_cost_count = int(options.top_over_cost_count)
+
+ # 8.Others
+ self.__timestamp_shift = self.worker_id_bit_length + self.seq_bit_length
+ self.__current_seq_number = self.min_seq_number
+ self.__last_time_tick: int = 0
+ self.__turn_back_time_tick: int = 0
+ self.__turn_back_index: int = 0
+ self.__is_over_cost = False
+ self.___over_cost_count_in_one_term: int = 0
+ self.__id_lock = threading.Lock()
+
+ def __next_over_cost_id(self) -> int:
+ current_time_tick = self.__get_current_time_tick()
+ if current_time_tick > self.__last_time_tick:
+ self.__last_time_tick = current_time_tick
+ self.__current_seq_number = self.min_seq_number
+ self.__is_over_cost = False
+ self.___over_cost_count_in_one_term = 0
+ return self.__calc_id(self.__last_time_tick)
+
+ if self.___over_cost_count_in_one_term >= self.top_over_cost_count:
+ self.__last_time_tick = self.__get_next_time_tick()
+ self.__current_seq_number = self.min_seq_number
+ self.__is_over_cost = False
+ self.___over_cost_count_in_one_term = 0
+ return self.__calc_id(self.__last_time_tick)
+
+ if self.__current_seq_number > self.max_seq_number:
+ self.__last_time_tick += 1
+ self.__current_seq_number = self.min_seq_number
+ self.__is_over_cost = True
+ self.___over_cost_count_in_one_term += 1
+ return self.__calc_id(self.__last_time_tick)
+
+ return self.__calc_id(self.__last_time_tick)
+
+ def __next_normal_id(self) -> int:
+ current_time_tick = self.__get_current_time_tick()
+ if current_time_tick < self.__last_time_tick:
+ if self.__turn_back_time_tick < 1:
+ self.__turn_back_time_tick = self.__last_time_tick - 1
+ self.__turn_back_index += 1
+ # 每毫秒序列数的前5位是预留位, 0用于手工新值, 1-4是时间回拨次序
+ # 支持4次回拨次序(避免回拨重叠导致ID重复), 可无限次回拨(次序循环使用)。
+ if self.__turn_back_index > 4:
+ self.__turn_back_index = 1
+
+ return self.__calc_turn_back_id(self.__turn_back_time_tick)
+
+ # 时间追平时, _TurnBackTimeTick清零
+ self.__turn_back_time_tick = min(self.__turn_back_time_tick, 0)
+
+ if current_time_tick > self.__last_time_tick:
+ self.__last_time_tick = current_time_tick
+ self.__current_seq_number = self.min_seq_number
+ return self.__calc_id(self.__last_time_tick)
+
+ if self.__current_seq_number > self.max_seq_number:
+ self.__last_time_tick += 1
+ self.__current_seq_number = self.min_seq_number
+ self.__is_over_cost = True
+ self.___over_cost_count_in_one_term = 1
+ return self.__calc_id(self.__last_time_tick)
+
+ return self.__calc_id(self.__last_time_tick)
+
+ def __calc_id(self, use_time_tick) -> int:
+ self.__current_seq_number += 1
+ return (
+ (use_time_tick << self.__timestamp_shift) +
+ (self.worker_id << self.seq_bit_length) +
+ self.__current_seq_number
+ ) % int(1e64)
+
+ def __calc_turn_back_id(self, use_time_tick) -> int:
+ self.__turn_back_time_tick -= 1
+ return (
+ (use_time_tick << self.__timestamp_shift) +
+ (self.worker_id << self.seq_bit_length) +
+ self.__turn_back_index
+ ) % int(1e64)
+
+ def __get_current_time_tick(self) -> int:
+ return int((time.time_ns() / 1e6) - self.base_time)
+
+ def __get_next_time_tick(self) -> int:
+ temp_time_ticker = self.__get_current_time_tick()
+ while temp_time_ticker <= self.__last_time_tick:
+ # 0.001 = 1 mili sec
+ time.sleep(0.001)
+ temp_time_ticker = self.__get_current_time_tick()
+ return temp_time_ticker
+
+ def next_id(self) -> int:
+ with self.__id_lock:
+ if self.__is_over_cost:
+ nextid = self.__next_over_cost_id()
+ else:
+ nextid = self.__next_normal_id()
+ return nextid
diff --git a/logs.db b/logs.db
new file mode 100644
index 0000000..763fad3
Binary files /dev/null and b/logs.db differ
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..0bbad46
--- /dev/null
+++ b/main.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI
+
+from apis import register_routers
+from config.database import init_db
+
+app = FastAPI(title="Log Alert Service")
+
+
+@app.on_event("startup")
+def on_startup():
+ init_db()
+
+
+register_routers(app)
diff --git a/models/__pycache__/base.cpython-312.pyc b/models/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000..bb5d27c
Binary files /dev/null and b/models/__pycache__/base.cpython-312.pyc differ
diff --git a/models/__pycache__/log_alert.cpython-312.pyc b/models/__pycache__/log_alert.cpython-312.pyc
new file mode 100644
index 0000000..b3d96b7
Binary files /dev/null and b/models/__pycache__/log_alert.cpython-312.pyc differ
diff --git a/models/base.py b/models/base.py
new file mode 100644
index 0000000..d35671e
--- /dev/null
+++ b/models/base.py
@@ -0,0 +1,40 @@
+from sqlalchemy import Column, BigInteger, DateTime, event
+from sqlalchemy.ext.declarative import declared_attr, declarative_base
+
+from datetime import datetime
+
+from config.database import Base
+from utils.common import camel_to_snake
+from id_generator import options, generator
+
+# https://github.com/yitter/IdGenerator/tree/master/Python
+options = options.IdGeneratorOptions(worker_id=23)
+idgen = generator.DefaultIdGenerator()
+idgen.set_id_generator(options)
+
+
+# 第二层基类:包含ID
+class IdBase(Base):
+ __abstract__ = True
+
+ id = Column(BigInteger, primary_key=True, index=True)
+
+ @declared_attr
+ def __tablename__(cls):
+ # 自动把数据库实体类名驼峰转为数据库表名下划线
+ return camel_to_snake(cls.__name__)
+
+
+# 自动填充id
+@event.listens_for(IdBase, 'before_insert', propagate=True)
+def before_insert_listener(mapper, connection, target):
+ if target.id is None:
+ target.id = idgen.next_id()
+
+
+# 第二层基类:包含ID和审计字段
+class AuditBase(IdBase):
+ __abstract__ = True
+
+ create_time = Column(DateTime, nullable=True, default=datetime.now)
+ update_time = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now)
diff --git a/models/log_alert.py b/models/log_alert.py
new file mode 100644
index 0000000..6d8f97e
--- /dev/null
+++ b/models/log_alert.py
@@ -0,0 +1,17 @@
+from sqlalchemy import Column, Integer, String, DateTime, Text
+from sqlalchemy.ext.declarative import declarative_base
+from datetime import datetime
+
+Base = declarative_base()
+
+
+class LogAlert(Base):
+ __tablename__ = "log_alerts"
+
+ id = Column(Integer, primary_key=True, index=True)
+ timestamp = Column(DateTime)
+ log_level = Column(String(20))
+ message = Column(Text)
+ source = Column(String(100))
+ context = Column(Text) # 存储JSON格式的上下文信息
+ status = Column(String(20), default="pending") # pending, notified, resolved
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e2eac61
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+fastapi~=0.119.0
+pydantic~=2.12.2
+SQLAlchemy~=2.0.44
+requests~=2.32.5
\ No newline at end of file
diff --git a/schemas/__pycache__/log_alert.cpython-312.pyc b/schemas/__pycache__/log_alert.cpython-312.pyc
new file mode 100644
index 0000000..77618c4
Binary files /dev/null and b/schemas/__pycache__/log_alert.cpython-312.pyc differ
diff --git a/schemas/log_alert.py b/schemas/log_alert.py
new file mode 100644
index 0000000..b626702
--- /dev/null
+++ b/schemas/log_alert.py
@@ -0,0 +1,14 @@
+from pydantic import BaseModel, Field
+
+
+class LogAlertTrigger(BaseModel):
+ alter_name: str
+ timestamp: int
+
+
+class OpenobserveQuery(BaseModel):
+ start_time: int
+ end_time: int
+ from_: int = Field(default=0, alias="from")
+ size: int = 10
+ sql: str = "select * from default"
diff --git a/service/__pycache__/log_alert_service.cpython-312.pyc b/service/__pycache__/log_alert_service.cpython-312.pyc
new file mode 100644
index 0000000..bfd3c6f
Binary files /dev/null and b/service/__pycache__/log_alert_service.cpython-312.pyc differ
diff --git a/service/__pycache__/openobserve_service.cpython-312.pyc b/service/__pycache__/openobserve_service.cpython-312.pyc
new file mode 100644
index 0000000..54bdd0c
Binary files /dev/null and b/service/__pycache__/openobserve_service.cpython-312.pyc differ
diff --git a/service/log_alert_service.py b/service/log_alert_service.py
new file mode 100644
index 0000000..2174280
--- /dev/null
+++ b/service/log_alert_service.py
@@ -0,0 +1,31 @@
+import requests
+from requests.auth import HTTPBasicAuth
+
+from schemas.log_alert import LogAlertTrigger, OpenobserveQuery
+from service import openobserve_service
+
+
+def trigger_alert(log_alter: LogAlertTrigger):
+ #print(log_alter)
+ # openobserve_service.get_log()
+
+ url = 'http://192.168.1.7:5080/api/default/_search'
+ username = 'njcxx0822@163.com'
+ password = '19940822Cxx'
+
+ response = requests.post(
+ url,
+ headers={
+ 'accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ json={
+ "query": OpenobserveQuery(
+ start_time=1760773184953420,
+ end_time=1760773189953431
+ ).model_dump()
+ },
+ auth=HTTPBasicAuth(username, password)
+ )
+
+ return response.json().get("hits")
diff --git a/service/openobserve_service.py b/service/openobserve_service.py
new file mode 100644
index 0000000..961d813
--- /dev/null
+++ b/service/openobserve_service.py
@@ -0,0 +1,27 @@
+import requests
+from requests.auth import HTTPBasicAuth
+
+from schemas.log_alert import OpenobserveQuery
+
+
+def get_log():
+ url = 'http://192.168.1.7:5080/api/default/_search'
+ username = 'njcxx0822@163.com'
+ password = '19940822Cxx'
+
+ response = requests.post(
+ url,
+ headers={
+ 'accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ json={
+ "query": OpenobserveQuery(
+ start_time=1760773184953420,
+ end_time=1760773189953431
+ ).model_dump()
+ },
+ auth=HTTPBasicAuth(username, password)
+ )
+
+ print(response)
diff --git a/test_main.http b/test_main.http
new file mode 100644
index 0000000..a2d81a9
--- /dev/null
+++ b/test_main.http
@@ -0,0 +1,11 @@
+# Test your FastAPI endpoints
+
+GET http://127.0.0.1:8000/
+Accept: application/json
+
+###
+
+GET http://127.0.0.1:8000/hello/User
+Accept: application/json
+
+###
diff --git a/utils/__pycache__/common.cpython-312.pyc b/utils/__pycache__/common.cpython-312.pyc
new file mode 100644
index 0000000..2e14836
Binary files /dev/null and b/utils/__pycache__/common.cpython-312.pyc differ
diff --git a/utils/common.py b/utils/common.py
new file mode 100644
index 0000000..bb599fe
--- /dev/null
+++ b/utils/common.py
@@ -0,0 +1,13 @@
+import re
+
+
+def camel_to_snake(name: str) -> str:
+ """将驼峰命名转换为蛇形命名(CamelCase → snake_case)"""
+ name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+ return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
+
+
+def snake_to_camel(name: str) -> str:
+ """将蛇形命名转换为驼峰命名(snake_case → CamelCase)"""
+ components = name.split('_')
+ return ''.join(x.title() for x in components)