en_Xsh#S{+_ z1jUOMgy5|gtCIQ~`~hAfJs1Xh5RKGZH9dLqO?DglVBfs=&D-z2@9locWYP%i?43dV zEk@{vOb$(tgFy>|HX?{%2f4U}v5XZ*an+JKj8#W-6Qu-33Zj^3BvHd8NpzC3v5mKl zjv6tQk~F!NjaiQzBu&iiv5pc!BtzAzLdKtIow%lyYGw-)zhzdy!dP6sx#EQZWsqcV zd$mP}vfv)GYqcSibG+KBC9zdb*(19`*VFTW6)gwe3CDotSNmQ6bGyIsa_`fY{RVUk zlTcn^wjbDDW8GNtf|;rpHb_AgNuSZG{a9#$RpY{7l>1(TQ!Y@L2dcgITm4rXM+spn zorkV#v8FJhS)gwPx{OTjLq3CZ;Myn{-L1tt7*#^>1a;#3yC8q5ps)m>A`?W4BdG<< zq%bZkXZ~HfexeRLPT*L$onI>^PZX2mc zvf-B$n(y&o*y `u17+LUSpGxN^$%?xP294yo*ltQE?ZY`EKNU=80Z< zvTIEBvia8!Hg9#0pYM)eIMB7U(n{`+BPI2G`swtGmF`rrHNAu9_7#jTZl3;&;25Y# Kng1h!VdMYRG1Sxm literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bedea5f69623dd71d90300888da477ad12cc4f51 GIT binary patch literal 1145 zcmah{O=uHA6rS1LB%5v0rdBJJB9)4|g1L!Bib5$B(H6mzp%6B^(`4gpR%bTWLWL-T ze^|kTJqdzdr1dED=-o>TN@cL1sGv7B9z;C&UN+I{#esSIy>H&kzW2TNJe#!$to!3% zD~?9UJ9IiL(FV;00E@&U9_1vU1xgWXTnqGq-oiRJf sFY1 zwkVx|6{89~=}q80H>}l|8x 2iV+|83< z7D*JdV5{vie-p@cgx*9($&Ry3muQSBnIpC~WmAy>!Zt(-KFSPO !?B@lg~oe{N2t z{F)yXy^_Ed=3vY1U>fALVP2cOJh^TRt{H=m_N*F%FAZC4fwRB#Fn}0_-9#l$5Q>_# zt0H6xI9!)Vn#_}X+9C|tJgK67|Ex|VDlz=um|1dBKSO5e6MdH2bjsFL=5#y gW!5Tn030k=+F(AC?+x-kz3tY{#@J^~PxQU&&fPTMnARQh zhI!lieqD>fPl;EqbdAsjW0>6S9ib1m9MqrcBalFkvgCR0aG14VPGEg%#~&ikE=KuH z!z)lU)x9Yo6oJ55QQdKe^6fDeI4mj`gdXEE5B)5*`Weqhrqjk^U2uV|6a8o~O@Sqf zUNqRUxO{UI9;$P8L<|9ky2#^TKI@dy4YGTK9NHjzKak-Kvg>WS|CNzhHwM;>fmM5S pg{|%z|E6n})<}M_NN(^!eI@a1;ziGiM&@gh()~>{N&8yA`UP284X*$I literal 0 HcmV?d00001 diff --git a/config/__pycache__/setting.cpython-312.pyc b/config/__pycache__/setting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca8ddbfbe1ed5716133755c5c3c0fd6cd41a1b1f GIT binary patch literal 983 zcmY*YO=}ZD7@qw|x=A*PwT)@odh(P)HU~Y0Qq&4XsznqpOJLaSPLtKmZaOn*P4N(+ zLd8pN{RtxdDIRlK1_Z&AH*E?+PtMG4BOS;y&ol3v_iJXq6^nVmSL@M5?~4M!Pa&>X zCMB~6oSXp 1v|h~Pk^T( ubcvI&znQ9M_ZD(vE@R4+5*y3*q%auE0wwVKVQ)eNF8vAhm5J%>6S z-Vhw(fB{xILT=BY@CbE!QS_D-NS_WMLHH11f0u`5Ie9XOFiq)@TUjwVbqG0%uy _XG45%tV^2ttYMa3mm;cE+y?9pZ12G zxtv$t5MBE&YP&LJn Rb3b-vG z%YkWxVpfLG3B!mw)Q>^}AvT0PCqTSnruiYo1tCoVMFAoxUKSv8+!0JXYzgA=k(V4k zf*&i5@w3UjsoscJ*T;L4?Ww*VS8L #g&!u(^E3lSK>-FVb!>{Hhw<2Kh@Xb zs+IUze|06ZdI2~j=|m 7v&Eqs?di~A;m f{MHmj`3^QNvjt`K^!+8^+n;q{E`M%)NJjY&1Lyjw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..934c604937c95018193a2cfae8ea0b7b19657675 GIT binary patch literal 166 zcmX@j%ge<81bc(SGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!a&@ta2`I|XN=+^) zj&aW{$tX>Nb93_3brW+^i%N8hQ;W(nlT%|dQ{vN8^HPfvOY)0i;^Q;(GE3s)^$IF) gaoFVMr V!Z literal 0 HcmV?d00001 diff --git a/id_generator/__pycache__/generator.cpython-312.pyc b/id_generator/__pycache__/generator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ef7f83d9b0a67a9d194a42baf8bea9652cc7fa8 GIT binary patch literal 1533 zcmZux&2Jl35PxsiAM1}a!GTIn8rT*>YD7XJaiKyHL7`C&1f-}vSdpGwzvp({wKu$7 zQ*1?5s4SEsK{!Cu!hv#v5-AjM;8gktaB;{X)e25DAH9Vdgv5!NUGLVE%wgWlyqWiA zelzd=+|!dJKzHWPUP!2f{EkXMk&e)Q8H6>$NuF@Z6_Zw!yh2e{L00oBB?_4#JklgQ zx~}+9`A9vMi`PzomVEHVxBDM{zqhfr`@?5@pMJT!^2yGPZ)W+7s0vwdESY>j!7WGK zxV7jQR@DYEZC9;JC9`l*u*yWyKhx)(3rPEQ(AS6{c?zj1oaR-oG)X?fRS_+xyF+}r zq(F+1e4Iyx267DKL_MC<+|0Bn6>6r_l~U~~h)L=|v(sV9o?gUU1HxPYHqy=jLx`3L z3-(AIN}7XG5@9K~s2j0`S+emA{h!m*rxxCpR#_Asdtt_KF4WG2s%f1cEto<&qqdOm z8bz^SaCSaGY$24!7VECYm{Bzx#u|hFuVgIP#1!oWuw`;DJG2t{=tvt=bz?s0#Q;|{ zVohFMCJ1X}iI&m&7;$ k Zc9Nu%#v!4|q>RD>?1wB*=aF6t)G z$k6B}&lb#@cmua2-sh&~VZct~fW0{(HL&CqiElw30cIl@x-L_&xP7*8SU38OXAgn- zaVH!gdmPv@IY^RpW;->wnHs!a`X%+;ubE@5p_jHY ?_z6 z*@KtHq;P7o`k!eFyTZW(xRxaE>gB7Kw|j>-dxy7r zM_Ss52kBfci7%yEs0hZ~Bx4ne*G!btjJ;DUn4O(CW85k-Ch-fAh`F2T`WfExzbM&{ z8>&%tyqs?l?+(8RoOg)~J88cFtV#Y*l&JPlqtT}iVp{an%}d)OQ=21GZ34={lkw=u z>-~QcSo~u!7zS7l!0P%*cvI`0Du=-O=sH(>)vshyB0Bg0?PI_WR7xpaOCx{v5qk7D QGVpgSOHV!|z`W>x0jFSbZvX%Q literal 0 HcmV?d00001 diff --git a/id_generator/__pycache__/options.cpython-312.pyc b/id_generator/__pycache__/options.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..781eaf9a7977dbc3bf5e5cdc16ba1f0d24cdd1f0 GIT binary patch literal 1142 zcmaJ=O=uHA6rSB|(ll*@+Jdzex>m&oOz(<%DwJN@g5V(rh9omKt4X$<-K|z~$RU-8 z4eg;)^x)5}B33 {6@vVBdb Y}k&t#5BMN_QJi-$pX+jICEY0crLFT_c<%Bq$*>DG^`_lgm; re1;=1#? krGQt8KpUi#XLM_0@F<)V8ZT_g}Sh7<}%3 zYG-TNsjNB6Z~mpuc+JOFVW$G{G)-`D5mJ#*K&~+vnB=h{#=OpwBF0eJvSI+3pO+yE zdNHN|g8^`j1%Y*VY=Oi=lMO79!(7wq)r;?g4U9HWuYH1@v9KM=b2F $j~*?m6T*AqMXXOHY?K6Q)YTEWVvxd5&e_VZp-pQZXd+-ycSOmJ4gj48Ha@Z2?l zlPtQ>b%QhTOL0Vgjg6g8P7D1y&zMGXTr;y)+NU{vCZ5W1Va5$E=Cuq@YK+c!!<*FI zc^jQwu)~yU1(*eTd+_gp9QA^SvGNqS64?!rqx}_SsSCis830iPC)?okxr*{Rd;*q+ z;)n)!!(>Q3h(hGlNJaS)R-2HLOvVBN@2G)J^nrul1I|@Q R literal 0 HcmV?d00001 diff --git a/id_generator/__pycache__/snowflake.cpython-312.pyc b/id_generator/__pycache__/snowflake.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fca833ce64b1ffd86686d8c597b74bd61bf9dc2 GIT binary patch literal 735 zcmY*X&ubG=5T3U`l5|P5X$3D*Lp|-G{R2cil@>2SDD@Bmk9GH@UES{1x4UXe4?W~i z1XB;%t$OfK1b={3Jb3ai@E?%9N|N>#8$s~oOg4#hV88dxo0;#;?0er!B?HjDt=`#~ zX8=FR8JjjDgDXf9aG(YXtboH>;3yxJELvkt)zso^=ozQ4Kb$=I-20Stzr5%@f79K2 z)_M7^+kV^GKk6JEcMsc!J(3(QBLx`Dp__n!8pAUb(yytGBD99$s4cCgH#JjFr&j&o z(XwaV7xuW2REVqi4Q3D$*kL=sk!98dlT}TY=7X)s4gAnlQY{o-L+Th0skBo0yS#M0 zep?3jgdK(T6*t<5?@nAVSYNO_A)|#*$cL^i>aN4rg)gKP1+pGuOATsiajTgYICp(F z;(U8%((2-PuSf?@A?(7B%X53$)8YUfNOMv|vGi^H_vdUl5?w$7O=c?bJW``FvXl1L z&i+C7;JEjywYGL^X%aXkjrTaT%pnO$v?B9}Y2Fu)Bknre<^R2nwk}dCCGA4rfa$WN zHmhci2J)>iUtqfJ~MMsD5~=q$!3#k1U);Xu+Ar z^sD`xu9pvIe*-RAKBZRie)60 tBkQ a{(*`S_ I0m4xA9}r-)ApigX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9e662f0ecc6ca439501403b4328a0b490a1c04a5 GIT binary patch literal 6929 zcmd5>UrZZE8lUw)3^ukC(*uLSe{c*7fdoPh3L#t)3Qd%dv_vPB%Vp(wH^jl%>Dn|2 z;;QPDO6gsqP_ 0d;U4-@seQf|)=spRFO`t)dcC|6(du;7J>2(=ch|eH zA|(=a(9OLdajRq9(gSY)$}im+)kU@C 2IyLab^=|e(6vCj z6uJ&*w?eysuD|YP>&q@p*;9QB?Vv+l7}BY}zyJQmH#hGcAJ-Q7^!?l4e(@RYE!Sj~ zm8ja|BG5+ow`qeMY|ShY4%=y3VlJzsuH-FqgM^69c^pTRIo|`cEF`@|-U>Bsei&z~ z@aS_>`6hfi`Fe>Cy{rlib85O$4K!(>fpa^*sRkx|It?_52CL9LZj`SZqwaG8T9|Nx z7R_MiM&7nW%(PsDHSxAZ7zyjsWEtjn8|TKFZhp;DhB2P!j;T?5K__j^Y8z@|swpsl zj+zC5%S1Eas8uEL7N}`9p*5GGPaDtMffLU*O_WnxK|Z9mUk I~6Zn&Bi=LC^U@!W+}o=Xdv z+5Dw)t=3xhGH4fV{3-cEQvFw>oW%PA_K6{1Bz5^nY*<2p71(>Ck(;az^ zPM^ys=C5$MOR0 7<=;zVurhrNY3e4wrZfU zq%yp4t=w#skjhn;Q0_AJReLIn<6!;S3}_;V^X1z7B~eK5smyHIb}|d9yuQc2+) zNSaka{#*{w|01G4H{aKL<$Br4fmYy7Zm~gU_+GWukF&lB0QVkQ55#ZUZh1 T9!4-MZ8eVCXBmGjOzu0=<=J>6t4F}l+jE|fV%Gn{gI#&A%uDwq>yH_XWy{}7q zUoUnZzd8QMy$4vEBXXci3Ur}lsD{EgvW1R}NUq4r8wFSVNp#Q3tQ =E7(VfvWSEvO{H0rM*oMDuPU)9#R zbD;BS7@;9mwNG8~|G|2+8>Olp=)AjHQz`0$UoMerjG2dc>mt}QY5A}G1Gi+=KUA`b zbv4N^@4FZ9IBTDppf?J=OwO`(<#r>u%?g0`3U+JYEWQF(6VdTv17=N_1t8lBjmbnh z3D=N__TG9UpyZ5jO^5lS57_Qo#8%|qwQW`fQ^hvy_H&iN)mtgWFNTc3NTur;Mqp&F zlVW5k7?y)WQgEmk9A0w Qmlc?br$W#)~%wg{s^qOEL&nWSw z)<;j4$vFqk%9%xdX#$C6d=`54Yv@ZuT3L>UbJWnrK`_(7a3K-C=Fk2a4W+4b>Gq z?_M~t?OxC#O?56B%j)A`6HGR@W>s_OH$i!5-3coJ`**-f)$aPc>_6y?FA80BjW?U# zk}tNN7Z?S2NRjX DZD4`k(#5hp3qRfE(NU$#OJt;5I|jzoZ~ zqNNyzssYt}3aVwY;(Q_v$hjCafi$i6ERG-j5qReH%U)LUva3f5-kwrVpWJg)>N)!5 z8&c22vh9(ttK@5teer@Xz8aBy-H&~7h^QKxWIrqUaf~0v^e&G-YUo<`H_HBQ$=|&? zF7KU?_D&T2lWXoth|1#ZYJ5E$0MMh9wZ(f~&5*eSrnhp{;zK&uHPA^##sg pJj6Zfp)|>X=t{9Wx*ECjY3V!Oj=UI)(U855Gnns^lnWnMz z#umA8ztp(@ncdRV2V1G3aXJ6dtIuj}zSa$5t8J~^LNDeZII9q~fp(SjKX8!t4MClw z;=1Zhu+>50KHy{>BE^y|PC;m)o)Py0WpD&UJljBKYs9mi0j21i6D>HSD+$Tj@z@#t z@y_Xy8hg+=y*oNbgL;@xJ3a71fYX!L`l=sw%&vX)(Ez6kpnr#R0RgnS+ouU@nVN+u z$a^<>kFYjswz7?W2C1Q;Y9=S*CBiZ^qRgQl;Y#$_$iYL~B(Pm 06?!;>FPtxQV|ovY)2n7ThD`G%ku~SY_V5W&g(AymK)l3*5kJEn%xg~u zGYsJw)lv*vQ}Y5$tR&Uq&v}^QJj|_wOH9Su|GNLIaO%RDF|c+!2&vqOKLx66&!q+7 z3abR~zPtb*g0%O7AR2aZL&f^W;D0f^-SkF|_hFvgn~(v}w1niAL8)c1*z)R{v#I21 zQ={;LXYj9X-<=a5oLF9X `_+JB0)R>V0cyA|t zB*$E}Fyt+mogvrBHS1gCD#J2U;FTZ C8zxQHis=lpNZ|B%B|dwbszhxIFm{fM__iuK^vuS^2I4RYl=j+@W&3u&a?9QXD@ zBCYh)a2%gaa-4{U7x7DL1xgPiHHKLXvu@1j_T7Wj5y Y zvsmoT4QJfmwHEH*Bv5XgVH)iRSEe@!6dV0PpxZYI6dQ*e_SRLPq1f<2_VAcOGxkHP z{!IeKhQnbGuOzWOj6BVs_`d_XxuOLsz&gd(P!BkOM5HIIHm80N6m8gu_fBP{RN)D% zhB2e>W9OlYkq(QmLmM9cTpY3uE5k6b7{<2gA 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 0000000000000000000000000000000000000000..763fad305eeb62fff6a0a3735804afbd6fd50f24 GIT binary patch literal 12288 zcmeI#K~KUk7zW^O7!6BMZ(KZl+#ped`UjlQC~*v(mB5KiR!NhM8CDEeW8yFKhuI-8 zHSyxnyiK 1?E;qA0Zj?g~yFzs(2qGe e zM3UeGk=I!|pN-`vb<+Hdjnk=?3thx?sOQ-xwwmlF=ycs+NY{M0T%ohvo10BP8U!E! z0SG_<0uX=z1Rwwb2tWV=|5(7u*M|N7KmNm58U!E!0SG_<0uX=z1Rwwb2ta_b538|g Aa{vGU literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bb5d27c68e0bf52b77c8e78be6fd9c8f2c1a0352 GIT binary patch literal 2066 zcmZux&2Q936d&8WUa$8nY(faMK-6vv+k_md3aJ6LDnyMCC4ibst J^k6IJT$$0lg3rDRk5ds)_@*v`H^L^^F~GmMSCp`Mr5>-pqUN z_uen1l8Ioo&;Px+l|krFDWogA%WRJ;2(2K3h~gn%u@qmmR9~|+KVxMSxvzRzU$=BW zXXRp9^9 UiV6V*(&=LtD+zk(Q u69J|ZSHSG7cM4T0xO^Syme4v_*Wu9Z?t zQofIRw++X>D&T8?4y6{>q0WJN^_6s0&j|f|=(YVonCINag@DrrWx|-UIh}QVDzfw@ z4R~D@B|;s~W|Ux?b0!87(dO<=is$W!))iq8IK|-<@R@VTUfHpI>R}$@D6p?naFh#M z+zo?B
q~;s$<(t#zd+=hX5k5i7IS@@b?|lC5m`4N^tQ8G!T4U8bNOwL#~1RR?k;Mo zvBhg7t=#A6kup WC7?+@AMn;`q)`64AtKyTfozGzg@Nc62mw?1A%x z_r3SDGWNt6+lFm4Q_nMLButFKf7^pMiXz50+P0TCmXbXmab`OlV 5ADsln6%>k8lP3XSeYC(sXP8bAGL_o68xy>4sNAgXh zqoZ*~#PDm;V9$-XggZM4Mn~a` 60<^H>C+NwkVEGr*(Q~r}Sae3NMM$q;= ziA<3rbir n5R*k>c_2ay!ds%?FbYSo z94MlYoR0-|TpI3SPtHUpNe#Z}10X5(sv{3Z9!~vu^5^lN#y6_d-HX5W?>xEs#pcy( z>sPOBRKL7;F>xr}%+yDO8Qt(~&sn5?XAl1@exa;^II^do0BlV3O~a%^46sUqsQ^+I z+{OgNZ_7qzrgTtjbJvR|oY42fAek6Nm*Cz=WJT$qBu%Rq$aFd>FYnII>Qs{EJ{5ag z8V^KNBDK#^+*^eNe-VZbiy;^%?0=aQ#@S>be8eWfmmFBp*Fd&3MNyuk$){-YIr{V^ zI{Ffg{*F#OM`K$$D!j9FV$-axo3)2*!+gJ+duG&@@|#9&-Kae^-tA_eq3k9aUPr^r zw>Hr69m7z@mu7bm{I*7rI@mq9sZ`gM>hkFaA3r7=${837N(}~tdUi{4JH?EGm1X@O JB>!=h{sWIS;f4SJ literal 0 HcmV?d00001 diff --git a/models/__pycache__/log_alert.cpython-312.pyc b/models/__pycache__/log_alert.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3d96b7630aad962fd1d1c8ec74c6e961cf6cc3a GIT binary patch literal 997 zcmZ8f&ubGw6rSDe&!lMs21P*)qK6#XLkkKbMQd%TKZ;;ZLuA=x#&mTyNoOY^@f4v4 z!AnaI?a`x>BK{v9Q^5`tDV};0s~1nc*(6)(z`pms_r87e=9~GF&888o`r@y(2RcGO z6c{ga6r7YH*g*sljiFG(TBu_^OyN{$U?ViKsi~UItkA}G9H*EQI@r;Wj;P%qwZX2A zmJu;F5HYFSPYu&J-L;DLu>wU?TK5~Q71pJ_QWvyFxwM`M9@J~nS@Z=h1tFD2iFQO$ zlQ}}G%;&xc+SIG~5rv#XfF*!c9qk#r ?jVY=MiADChEp(~K~16)eJ_| z?uXR#WZLt>25B)BPk7#X%V(oX*7Jm4VRQ(&x+rd_o_avIr2G)ntse zDdV=nHdG_OMx_-sTD(f7U2VXHb_67ZFIv$@bOJo|_wM5D@>AY;Nvk3%F9%|+RY@T9 zoQ_XB9Z}v6s t|p|=9jR(rRy{d$nQ252(BS$fBM%LmzWTT8oh+m8m>LT~9zdUpHa2X{X^`(}w> z9q+eV%*b@K&U{u~qha?{rdX0#r=b{^PDwK$!OVZ}maCz;3q~3X@Pvn_xuTNsJ_S{A z?hNl~o?i#39%Xa`%+HjjY2VTOS2TZ!ZXKe_NA`t*eX(c!wp?xQt@wk$#clopKl%iq literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..77618c4c5479a5a290242e7202b53221a8a92de2 GIT binary patch literal 905 zcmZ`&O>0v@6rFirNnSsaG$@E7L9z0LT0vJ*#A>w*ZK|M~N8mBdOp_8g+l zS$0={f>Qs9OS_1(5p>m^kh*c@+({#L;VjNO_ueyiX3os}JU8bfJ-$C1ckW??en2oC z?im?B)Z`FRL~(*r9OBdpEsQL*j;OVdsLk+^J<*1?$vQgg{*!h3UQjpzNnokcBO=&F zPFbSdr!h-tYch9&XN}PKnvM? -%B4PKd+&shUn7~`gf|5yVi8EY(U6Sb-H$vv{n zEEIaiLJy3ig|J|vqKQgjk(KmJ- +X z`3%53z#IS^onHdD3~&ViT;pW`V^ UCTwUIKGQ9i6 zSsqmu_SS~i2aQpAaqsc4cI+%pnn9mN;HqM`Pe~@@7Ke8^*@n40v(Wj)RdHqVq#p3A mTCD#jB Zsz57Q9q9_ BM*`pn zSJERU!umxfJOehc1rO9X7d^ojYofqq$&-A!Ci_ZF>FKhU@w2rolNBi67wybrxuz}( zdiFO@cU>2#HZwPO^RkIu>rzY{U8Kd?uyM(Qh|D3k(SV2!+=yTh-op?*gtuad7FpEp zaNAbr9@m02=Y@^B=@E!5fu~PSj&arvSbxt5d%4gOY_TQUQcEIR_nK*cNf2ktf-SR8 zdCuNVl+ VmWZxEc O_fUJ$%;`vT{X(anW<9~)rpcBxg{IkH)D^K*o;hMIZJ~< z(^^=Vs8%W$%coBmR_N10d2;e3w@fcAEDe}Z 4lDY0q?uBkh@=#r;oPE3J-N_`dI0 zXT *WXKHeG9Hxy}K|KAKK4y`nwc-@@RpWo;M{llx(7k58t#>S8~ zkZI3!M?v47r{`AAJ-P6uF#54Dy87r-VeErEw$2IzJ%@HNMzwU91|M>7baW6~4)jfI zZ1scq7B>f-O`1g+ZsOg%255xK2N $6LJtxOVY@qaH*9v-nORLj z0y!vHJb0`J58hPLZ{SDpsE1`l5Y&UWN& XSRv@mVN(b-uu0I@4Zi_6~O4-M&Ig* z0DR@fh?pHw{{j;Szyzk?fC`tQBh 2yVT=I^9R}4AOsOLhVR#4BMrDm0 zOtu*~`{b9R87K1Sz)YCQ!!iCWPPN3b{Ga!RhAj||9Z_3oi=&uBr58K)b|jJ-UI1;W zH>^egO`xVWS_>8-{#rg?SS@OW<&sv=R@N>rm6r0lZ|6;TUk@CTuVSbp!)i^sHKWm3 zDiw>@3d iL&VRiKq_pCP>tqI-t9ox`}?Ya3H_FPq@aosSWPg>*ao=c!h7Pp%| zr1AsaLWh@lC+AzQ g#t0AI)Wk%vs42m79j zA?6v*F#3J_=Gx8{@@mi^c;}W)tYG)A>3DmKx&ske mW}m?T zHI6b&(DbNV8gJimZzi!}!Mct$R$VoEQ`E$$(6<1cVsU7i2PdzvwgwKoigL{43x!WG Xb|eXc@Cjr;gY(~k){jpKQ&G?#)Rgou literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2e1483696f0b1c0661d4bf26f3f9632e45cde5f0 GIT binary patch literal 1171 zcmaiy&1(}u6u@V8H^DZh*bfzgV1x=?G_i@;Pze!jK~N}yp4ya nQv3CF+yLc(-5vcsh);O4hcxW5*kGUL$JUSe3KKnd2W I?^Y_>UDq&NK~n|1#> z;+y^XE%^Fn!LyS%)#yZ!X*()!NJSG!N{mbcey!Tqg!L+~1DD5??n=Y@xE zO^)6okr=r9TZ`#KeO|tA?4oCMd@SlokDubxwy-Y}_NP1hXrSLSI9^}pdDdW5Vz63j z!mvroG*Nl1(#L7^7u>&s!{@`-R7E5)T?=21>r<%-%axQ#PgEkR?$L;PJ02t9lpdF~ za7;-g6xo}cH9BIn%80H+%mP))my*={pt5Mk;e0T#I8f~B%}*7$lH*)<_+uk 0g<8 sT#p0U%I$0Iy8Y i%MF^Yo$10hjtXyF8Yy0#V+5PnD1Qn jS{-_HJbUFmv_W7o@RnX) z>hhHwz9Q#S9gxU#svQh;tgf2 %^5?t1L&$HQ_ZzDvEb9OO literal 0 HcmV?d00001 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)