From 6d46e70afad42879d4307b2f61bdb0a3162ec44d Mon Sep 17 00:00:00 2001 From: ui-beam-9 Date: Mon, 21 Apr 2025 13:40:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=EF=BC=9A=E7=BD=91=E6=98=93=E5=A4=A7=E7=A5=9E=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E6=95=B0=E6=8D=AE=E7=9B=91=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 - VERSION.txt | 1 - breeze_monitor.py | 1123 +++++++++- breeze_monitor_CHAT.py | 123 -- cms_monitor.py | 690 ++++++- dashboard.py | 1838 ++++++++++++++++- ...ad_auto_run.py => download_auto_run_dev.py | 159 +- inspect_monitor.py | 123 -- install_dependencies.bat | 11 +- templates/dashboard.html | 722 ++----- templates/login.html | 342 ++- 11 files changed, 3805 insertions(+), 1344 deletions(-) delete mode 100644 README.md delete mode 100644 VERSION.txt delete mode 100644 breeze_monitor_CHAT.py rename download_auto_run.py => download_auto_run_dev.py (69%) delete mode 100644 inspect_monitor.py diff --git a/README.md b/README.md deleted file mode 100644 index 7558953..0000000 --- a/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# 网易大神实时审核数据监控 - -## 项目结构 -- releases/: 发布版本 - - latest/: 最新稳定版本 - - v[版本号]/: 历史版本 -- dev/: 开发版本 - - latest/: 最新开发版本 - - v[版本号]-dev/: 历史开发版本 -- config/: 配置文件目录 - -## 版本管理 -- main分支:稳定发布版本 -- dev分支:开发版本 - -## 自动运行 -使用 `download_auto_run.py` 脚本拉取并启动最新版本 \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt deleted file mode 100644 index 6d31a5d..0000000 --- a/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -v20250414155609 \ No newline at end of file diff --git a/breeze_monitor.py b/breeze_monitor.py index 74d2d4c..64c8a93 100644 --- a/breeze_monitor.py +++ b/breeze_monitor.py @@ -1,123 +1,1022 @@ # -*- coding: utf-8 -*- -import base64,zlib,sys,os,getpass,json,time,random -from urllib import request as _req -import threading,importlib,subprocess +import requests +import time +import json +from datetime import datetime, timedelta +import logging +import os +import sys +import threading +from threading import Lock +import re +import subprocess -def _TnMzqipXoWNK(d,k): - return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) +# 配置日志 +def setup_logging(): + try: + # 获取当前脚本所在目录 + script_dir = os.path.dirname(os.path.abspath(__file__)) + log_file = os.path.join(script_dir, 'breeze_monitor.log') + + # 确保日志文件存在 + if not os.path.exists(log_file): + with open(log_file, 'w', encoding='utf-8') as f: + f.write('') + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_file, encoding='utf-8', mode='a'), + logging.StreamHandler() + ] + ) + logging.info("Breeze监控日志系统初始化成功") + except Exception as e: + print(f"日志系统初始化失败: {str(e)}") + # 如果文件日志失败,至少使用控制台日志 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.StreamHandler()] + ) -def _t7RzY7Aq4(t,m,is_error=False): - try: - try: - from playsound import playsound - except ImportError: - subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], - stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - from playsound import playsound - - # 播放系统声音 - try: - import winsound - sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION - winsound.MessageBeep(sound_type) - except: - print("\a") # 备用蜂鸣声 - - # 在控制台打印消息 - print("\n" + "="*50) - print(f"{t}: {m}") - print("="*50 + "\n") - - return True - except Exception as e: - print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") - return False +# 初始化日志系统 +setup_logging() -def _vgZ4DAeqFt8(t,m,e=0): - _t7RzY7Aq4(t,m,e==1) +# API配置 +API_BASE_URL = 'https://breeze.gameyw.netease.com/api/cms/issue/list' -def _HjXSwuCsz(): - _p=[104,116,116,112,58,47,47,99,111,115,46,117,105,45,98,101,97,109,46,99,111,109,47,119,111,114,107,95,115,99,114,105,112,116,115,47,109,111,110,105,116,111,114,47,99,111,110,102,105,103,47,115,116,97,102,102,46,106,115,111,110] - return ''.join([chr(int(c)) for c in _p]) +# 默认的各类工单的折算系数 +DEFAULT_COEFFICIENTS = { + 'NTES_GOD_IMAGES': 0.54, # 网易大神APP图片 + 'NTES_GOD_VIDEOS': 3.8, # 网易大神APP视频 + 'NTES_GOD_CHAT_IMAGES': 0.54, # 网易大神APP聊天图片 + 'NTES_GOD_CHAT_VIDEOS': 3.8, # 网易大神APP聊天视频 + 'NTES_DASONG': 139.19, # 大神大宋视频 + 'SPIDER_VIDEO': 3.8, # 大神普通供给视频 + 'SPIDER_VIDEO_SP': 13.3, # 大神高优供给视频 + 'NTES_GOD_AI': 0.54, # 大神AI图片 + 'NTES_GOD_TOP': 3.8, # 大神短视频 + 'T_SPIDER_VIDEO': 3.8, # 大神tiktok普通视频 + 'T_SPIDER_VIDEO_SP': 13.3, # 大神tiktok高优视频 + 'V_SPIDER_VIDEO': 3.8, # 大神ins普通供给视频 + 'V_SPIDER_VIDEO_SP': 13.3, # 大神ins高优供给视频 + 'NTES_GOD_XHS': 0.54, # 大神小红书图片 + 'XHS_SPIDER_VIDEO': 3.8, # 小红书供给视频 + 'Cupid': 0.54, # 大神交友 + 'CHAT_P2P': 0.55, # 大神聊天/风险用户_私聊/私聊频繁 + 'CHAT_TEAM': 0.55, # 大神聊天/风险用户_群聊/群聊频繁 + 'CHAT_ROOM': 0.55, # 大神聊天_聊天室 + 'CHAT_ROOM_MSG': 0.55 # 风险用户_聊天室频繁 +} -def _rQtAIc46(): - _e=[38750,25480,26435,29992,25143,65292,26080,26435,35775,38382] - return ''.join([chr(int(c)) for c in _e]) +# 系数配置文件路径 +COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_coefficients.json') -def _Vh6AC8aM(): - _e=[31243,24207,26080,27861,21551,21160,58,32] - return ''.join([chr(int(c)) for c in _e]) +# 全局变量 +user_credentials = { + 'cookie': None, + 'username': None +} +credentials_lock = Lock() +coefficients_lock = Lock() -def _eveMXRYK(): - _e=[39564,35777,25104,21151,65292,27426,36814,20351,29992] - return ''.join([chr(int(c)) for c in _e]) +# 定义全局系数变量 +COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() -def _jxLIMnM0bf(): - try: - _lfnTd3xE=getpass.getuser().upper() - _sLdSiJFn=os.path.basename(os.path.expanduser("~")).upper() - - # 转换为小写进行比较 - _trMcjKLs=_lfnTd3xE.lower() - - _bnYtbb3=None - _ndNNkcla=_HjXSwuCsz() - - _s,_p,_v=random.randint(1,5),random.randint(1,5),int(time.time()) - try: - _h={"User-Agent":"Mozilla/5.0","X-Access-Token":str(_s*_p*_v)} - _r=_req.Request(_ndNNkcla,headers=_h) - with _req.urlopen(_r,timeout=5) as _resp: - _mVAYGFB=_resp.read().decode() - _bnYtbb3=json.loads(_mVAYGFB) - except:pass - - if not _bnYtbb3: - try: - _mVAYGFB=base64.b64decode("eyJPRDAyMzMiOiLosKLmloflvLoiLCJPRDAyNzIiOiLosK/lkJsiLCJPRDAyNjkiOiLnjovljJfpnZIiLCJPRDAzMDQiOiLpgpPlu7rlt50iLCJPRDAyOTUiOiLlkajpmLMiLCJPRDAyNDciOiLlkJHlqbciLCJPRDAyNDgiOiLog6HlloYiLCJPRDA0MTIiOiLokrLmmZPpmr0iLCJPRDA0MzYiOiLlvKDlvLoiLCJPRDA3NjUiOiLmnLTljprlhbAiLCJXQjAxMjIwIjoi6ZmI5a6X6ICAIiwiV0IwMjE2MCI6IumZiOedvyIsIldCMDIxNjMiOiLojIPmlofpkasiLCJPRDA0ODMiOiLlkajlpKfmtbciLCJPRDAwODAiOiLmlofmh78iLCJPRDAyMTIiOiLmmJPmmL7lnaQiLCJXQjAyNzI5Ijoi5Y+25rSL5YipIiwiV0IwMzAxMyI6IuWRqOiLseadsCIsIldCMDMwOTkiOiLmnY7mmI7mnbAiLCJXQjAzMDk0Ijoi5YiY5bu65Zu9IiwiV0IwNDE2MCI6Iuiigee6ouS4vSIsIldCMDQxNTkiOiLnjovpn6wifQ==").decode() - _bnYtbb3=json.loads(_mVAYGFB) - except:pass - - _ct7GbM1aX=False - - if _bnYtbb3: - for _id,_n in _bnYtbb3.items(): - # 转换ID为小写进行比较 - _efs7U9k=_id.lower() +# 读取系数配置 +def load_coefficients(): + """从配置文件读取系数,如果文件不存在则创建默认配置""" + global COEFFICIENTS + try: + with coefficients_lock: + if os.path.exists(COEFFICIENTS_CONFIG_FILE): + with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: + loaded_coefficients = json.load(f) + log(f"从配置文件加载系数: {str(loaded_coefficients)}") + # 更新系数 + COEFFICIENTS.update(loaded_coefficients) + else: + # 创建默认配置文件 + with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(DEFAULT_COEFFICIENTS, f, indent=4, ensure_ascii=False) + log("创建默认系数配置文件") + COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() + + log(f"当前使用的系数已更新") + except Exception as e: + log(f"加载系数配置失败: {str(e)}") + # 出错时使用默认系数 + COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() + +# 保存系数配置 +def save_coefficients(coefficients=None): + """保存系数到配置文件""" + try: + if coefficients is None: + coefficients = COEFFICIENTS + + with coefficients_lock: + with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(coefficients, f, indent=4, ensure_ascii=False) + log(f"系数配置已保存") + except Exception as e: + log(f"保存系数配置失败: {str(e)}") + +# 初始化用户凭据(从环境变量读取) +def init_credentials(): + """从环境变量初始化用户凭据""" + try: + cookie = os.environ.get('BREEZE_COOKIE', '') + username = os.environ.get('BREEZE_USERNAME', '') + + if cookie and username: + with credentials_lock: + user_credentials['cookie'] = cookie + user_credentials['username'] = username + log(f"已从环境变量加载用户凭据: {username}") + return True + else: + log(f"未能从环境变量获取用户凭据,BREEZE_COOKIE长度: {len(cookie)}, BREEZE_USERNAME: {username}") + return False + except Exception as e: + log(f"初始化用户凭据失败: {str(e)}") + return False + +def get_api_headers(cookie): + """获取API请求头""" + return { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9', + 'cookie': cookie, + 'priority': 'u=1, i', + 'referer': 'https://breeze.opd.netease.com/', + 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-site', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' + } + +def log(message, level='info'): + """记录日志""" + try: + if level == 'warning': + logging.warning(f"[Breeze] {message}") + elif level == 'error': + logging.error(f"[Breeze] {message}") + else: + logging.info(f"[Breeze] {message}") + except Exception as e: + print(f"日志记录失败: {str(e)}") + print(f"原始消息: {message}") + +def is_image_url(url): + """判断URL是否指向图片""" + if not url: + return False + + # 图片文件格式检查 + image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.heif', '.heic'] + if any(ext in url.lower() for ext in image_extensions): + return True + + # 图片服务链接检查 + image_patterns = [ + 'fp.ps.netease.com', + 'cc.fp.ps.netease.com', + 'nos.netease.com', + 'imageView' # 特殊参数标记为图片 + ] - # 不区分大小写的比较 - if (_trMcjKLs==_efs7U9k or - _sLdSiJFn.lower()==_efs7U9k or - _trMcjKLs.startswith(_efs7U9k) or - _sLdSiJFn.lower().startswith(_efs7U9k) or - _efs7U9k in _trMcjKLs or - _efs7U9k in _sLdSiJFn.lower()): - _ct7GbM1aX=True - break - - if not _ct7GbM1aX: - _AIfSoxUx3=_rQtAIc46() - _vgZ4DAeqFt8("访问被拒绝",_AIfSoxUx3,1) - return False - - return True - except: - return False + return any(pattern in url.lower() for pattern in image_patterns) -if _jxLIMnM0bf(): - # 显示验证成功消息 - _vgZ4DAeqFt8("用户验证",_eveMXRYK(),0) - - _k=b'D\x0edTK\xe9\x10\xc4\x1c^8VG\xb4huRP@s4VO4na@vnlJIBz2?gP2Teo|lRWM<|6Q!%@xrD5DH-IEt5?5+5L;S`E=r3E4d=+W0D13F+(4gJ2P%bgl9~bYBB4DG97XwE#>bR$v))Nmg{O;}>h)HoH>YP&LYbfxcRPyi08blqYOqqE7@o<;TB}s^J@3@*8mwa@ok!xT&L`Sd(!RiogwT8>F<$I&PLg%OFh46M0<^Zzmz+OL=1U%6JhIWtWv+(w{sQ+2lC`OBio>KIUr+VGwr(08o$kGDT9b=i>112Y+f774SheHTht<*7tjz{NY_0WFdC$s&y7kUXW)`2W0aij+iX~Vx(kNZX~;=ZOF4r-D1gjzD1GOM^!SE2i}60@A1>eW3A)z#Ug1GbvN=nKs7As3y}mAeTzZuBioCUf4_o8Zj?i)bg6-2^-$QHPs4J)^_GW&HA&uCbKVeNDo;rq_A83PC)+;lh{$6hwve`M>`y7Jc>E2Y;$QSDqJFu<&5*b-)T45l_umiO?`)p%6HO1HowFXL82c1B(EWV;`9!ke9`?1srbp!3FVDxAP+kD#<}19Y&`lE&T(P61Ei>|Mo1%AtTi66XhFtt`-5D8{Mxqb&Z7l8USCxfHr-;!(oMAi8<@iQ+6N>)Iq`BfAqKu&UQHk9BDW$$?HmDzZ;5J`NY>wJQ*eMn!O!scLNyJI%`2D_WA!28a0hw^0+mTrFiAp-!&SbKyG9c@ZO(VXP124z=r1~KP1#*_k6bsMGh>Pqe{v-(?2^wkw&0+?*Anh*OR+K`W7t6x__bJZN$BcRRI|7OzszxxL(L4lC+?EF9*p_Dls|6y8u;W>Y~+#-LZXQvJ%1Bde6FA$hOEA8YVcMKT#GzQJr)M=wqW7%)rkqCE^xo%O(RZ5^5InueV^J}wI@1pax=)3XF?0g@$j_8;Wt+rA9D_?xm;y4m3`?#kt{cQVrg{wE3wufPIs3uBk)T>mKq7h;+1)_h^&fSu-i-eiFT9V5t2^o63h9c06~LXDlr`Jrb6*E;=kBN%MvG~R)`L^S?}2GWj?ZZM%ue5Zfsb~A-1_apn{2co}Kw`E!_1g;zA=G<-k07v0Fh2zI>aC&z%r}UZdnXXzQn8=u#smkkV5e?x65F_E6ycWdFd=B!GPBg&Zelb+aWs$4hrZgv{SIZ4Q#JWgeB|u^ALj*3}mfliC*03^sA~adq+%xqE1Dv2Lh4F1!Di0#94#u}Ihx{TSP&v~kOO=Rm76soAF~u{{C#F2k8L6m_H`GEE1FuC6Af!eqHN-VCe&D{h&!oq&`-zqgSnofmEfLgateCWd^g#2aS$3}3{AFsGk{S1ux_>r4tB4jX5T(Z7dr(pMQeCxY}_jN`{buynnm%)g^xIg?8d@bSOVD&(3uN5c&%^8tq1EHqM9BR8UiBd+e+Y%9Y6f&e9C8_JbM=@J|LkY01?VDEwW;XMkb7v-W}Yd-FYxKW8Pd$lz=K{63}Z9QX%XV00D_XZ{oJ0V_EK`9qBITNQFjfL+1F2h5j(#5P27vi?x3>c9TqMlzzA04Zjf@>_)s9?MjTijM&RlD8It^fyxiXE_LUUu#udEsMN=EDqP%%5yI2%G=hl$KXZzB>F0=Rnq1CuQl6xkYdAdEL6Q~y&8%_FC}mD>9VVg6w+$$Ko(kx%MNquRuYca6lOqMuTh~3qj&@ruJY}?|fU|Ws276DpPI#K)VVmi%oqR)4(Xo@^2u)ifPO-rytXHade)Hrq-qvFoXOA?J37$>c{x>`-ItcWo{`4s1#U)uSurl%}FzbXQslx7xH{YH_tU3{?{S@f#@o9dTCOS6HAD#ND_mQ$}fpRKzVz3}R5&*)beL#F<*muwq{wNC{vR-R>!3|LM-F~T2t5}=+imcVkecl2Snq0Z$?hr8*lEY9PPD;Oo6J-~q-E}I5b$o;27-g>L)B(EpaS$Z$>OkD=8^fCQEsVcf0-He0?aG%yD$*U3-PNd8QJT3H1)oa$>}gyVM}B<)i(qhnOA=4F!>>1|-F)Uvx7twJ!V~66^${~%Ps=Y|k!|~ZlM$w@mr(A&#RF?$4>PaxUi`X64%X8~*Iv0q?$mR(loDFgikq)v;6w0Wjmm~E*|FJYa_Uwm(wbhoLWOb`>yPBUVF!`9lAh7^C$s&wTc4M@Ag&NKdnV(*#)}2YHtQ6_x7YQJo*5A_DJRm!U51N!{DwdoKRYZ$1jGHJX;Q=+w&N9c>MKsPORz^XGHIPVyNz#i0P=XQmAeqH2SOrsjcMeA$+QtL`i|nZR9Yozi3s7bt5&rIz}DS-5ng(6VDz*_BInbK-xM^D?DV)FOqrK?9OJGLFV_VkvWMjLI=oh=IVg~!58LCXTv-5UmqW6Z>)GcG%)3tBMb1VhK`M;>eV@W$4bfv`Pt_R)+R+dM1$5n2bDUCB0&GqUN?5hSKxVu5vP7Vy?@3IN6e%Y!2(3R01B~r5%!4k*RO2%Qf*@HNdX{$z+`gdfPjj4ra*t0ok$#SH!e4|NYG4Ru0UrX@3)wh7V}-=x_PI-XRQSIp(NUm~$M~ZttPYeLZ;f&7lcDlEF#qw3I?v<6WtpiN(zsGWY?oD9zftWsE;nIh-ZwPET4;Ppma}M&K#hSN-HD}&+OOkdfys`xR|c@IJx}T*A3{D%u~8=SNl(9WbB4?P(m}P1zJZdcZW(QqL%&^e<$wR}P#piRHfv;oR%*~9XgJ4t3ysvKc0P<}agg<8DI-b$_l)V0FB(n=zHtN02g`Ab`ARbSl9MMTQk+1QhF7Hxod}6GKPic1@HY)SPh1&pzs;;|}i6xRF04;7vbOgJ%b|Y%4FenuOd7`d=nS+TP9N>2UKV7Gf~nGQBtupM5-X4d(;tn(&toPI$kdD>4v>ZLc>!PmoyL&XX+tJAdNwINiA=vnH0b>uIru&MJCxJCxf)~LRBf+gqL{d`2lKSY-;g2F)zmEok`&XrXkaE|?7k<{m5O$fD*V77ze`4dc7xI?Y7X0u6T~1Yrea*pF8SNzBqZ&h!;c&CcBMsEMtFo+!oG-?-fD<0Sw-3g-mf7Xb~VqtP(&+lKbDBnHi&{c;<$omI#Oq9KoAplg41J;21ie5B#%jTp8Ft}B+UAQr8CN%?7ICAGN@HoEC3FX>3dN#L=rr{?eDAY-|l~j`Nqw+u+y{NM+TyVIAi$6`@9fqL$=U71E9e^*v5MAKU`qnG2n~fc;W~1O=O2}>a4QqSpNbX5w!S-$u|!^#!(-j)SW|_K9teE>xavgj;)(=xCb*C>kkVX;SZ?br8S}o!zfwCvYnKZZdNMf9FBn_Y9uoc<`z+BFstQR^6n;5tUghSaSWG3IdFI1SRv1yvhZZ_+_6&|Uv@HEOnmzlPYK`Ue(zuCL7XuIZvNq&X#4~jnzN1he(_&5+QJcs$mRql6tidSES%mD0C@Aff=a1(oUx+G4uJe-$@lsYwLI-L7T}r9r&M-q59DHB-HjKcg>g^2Vx8KCRY~ABNTr;w9lh`Ljmw?OU=Z}uCJ#?WIqWp8dC{85$^)-=8h)AJFO9Evnx##(f&~WQg-2=NqAI>{ml!T-dH^$1^3xxtSuMT-5sW?q4>#4nWPMRCo&_m@HO{J?DV*d=OWF2?p2JH{_hOj#Qu%Kvk28r_RVBX?jAwiBs2+3LJ2s~$Y?b&gw4j*xJK9g>I0?@R7+wAsMi@&VCDKaOyGK!Z>m*#5i&Y#NppQqHcd%J*CT@6N^Bp5$rTLh(R(KKS|NEgP7u(%Z#weFu#T6U>~&PwHX5>Zj8eryHv8N4N})Cj9|L{6V?FPt7n=5@gGHKl*c=hA-|&lM$Bj9zEvy^;H7Uou20)X;7rumitvPe}su|(@Z4jm(8L5zFhZ;b_Zv*^p;_16jo({FDM}BHC-!@)>9UORSnWf2!+B3DJ>K~7yb$vS*Yz)0zmb>E(6!QFNeYtS4mZa2@{=bGP$F+ubWV%uIeHNwWw?jA@y|1A(=g^3f-$X#Cta%j|+XhR`V*P~aUO86yGR?Xsm-|@8I<@1A{Zf1S7x9^ZO1v>beoS&79`i*IHY-n#XwG7XPpkLSgPym<`TJ);d1rTIY!e+pQJT7g-RF5(EM@%DQ$`bxO!R3JNIjty7oW|8TF9yp;yMrpH=K<{pQm9mQxDA>*^6Q&uSk^{*W;S+L1ZzSB+dCiau>=Bb`pn=HUEA>+<|l@T>&a$xbQ1Uu%&si>i}71d(eoWhQu7N~<*BI-<3W#>q0Y{LsBB`*6`*9d?razbI^T|4?@(nj@yTW5(hb=8f)n;o7_t$h;8yBn8GKNjTOo=4C$6%H7eA-1sC!}{TM#P9p6D^a`PZC;3%2=2`1__=*Kh>x*@v-V`N4-|gjY1?UVtJv~;c`jvC0(tY=nLQAH3Bkk*SEx(DhDiP_^Se8)pA4v&wj}7o}soV$cv2_1UJxCI}FYD%kwnf4Xf;z3IKl0zI}FI+*&~)t@AfXCGxJ?bS!rk1nUznxEh{PX<}GID%kBh|L*do_eVOJ>L-hWLZv8;P{HZOw?f2iBZtM&-+d`h|~|zQRpEtHtK&ege)O=e+|1dVop@NTR+rKDDt3Z#Y5Y@B!k0}N#qE_C!Gw8ilIR^CyjWFKNYIBUf@ofOpA~##yL$`P83^=C+jI&bWHC59~Tj_s^Fe(ngQstYd*{|xtpD^F&_uzsp)_Ov(w+^J*nrt$y8Cll{85HbyuK?zpCmNWNOcJ10G}L%xqhuQD7`nr<+eg&DB+v2Zc7NyshXMF}csDs5#STL!LSg;+9mtqMlS1sSg{BXZdpxj#LThfOLhMeq86xY|w@gnoPCF$`bJZ$^cG>9KBO{zzDup0W&)ye>>r3wHi_9uoK9mFIYbJex*$Okj+A|qdC#bTvztAIBrz-trP7B2VFfYJuumQ-K+WG=rj^~@MQnWP&8+)iDjI`{IkT^xb-+k7W#ez00vLFgs?3pV$z=K7gw#D-S^=K#JsGC=`Goe3Q6-nX}{rLY*m1Q_`#INF>ij^Cz=|$N!(!j#zu66r!s?OGgl61a&llH>qqfy2~GxGxhlct9q;-A4H{F%g7r!75+!LN9WY0F=OB+VNOr5a+o;-cHz@6e3|8r06Ds=#CS)?r6UzU4fTJjbCh;24Up+g};bvy+c8`4Wt8ofahBp<#}@bj;LCwX;ua!D0JKH+o{}_U2Dq?Aa#Xt*FzhGq>nxib|xCpnsSlkRXdVF`*h`H2`TtS1LG_^v?%!VQ`IRl}l1k!Pa0s7H({nMS1?Z!b)@ydBQuCiqmx;7QSzW%_I6d}nA23ZL&MQs=?XVPEAKFpB*OWpJ*12d`m0Fv^~IQq+j9`#=vPZZ=-U;Xhc1Yl--8Y&q})SwD0(vr4j' - try: - _d=base64.b85decode(_e) - _x=_TnMzqipXoWNK(_d,_k) - _c=zlib.decompress(_x) - exec(compile(_c.decode('utf-8'),'','exec')) - except Exception as e: - _vgZ4DAeqFt8("错误",_Vh6AC8aM()+str(e)[:50],1) - sys.exit(1) -else: - time.sleep(1) - sys.exit(1) +def is_video_url(url): + """判断URL是否指向视频""" + if not url: + return False + + # 视频文件格式检查 + video_extensions = ['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v'] + if any(ext in url.lower() for ext in video_extensions): + return True + + # 视频服务链接检查 + video_patterns = [ + 'vod.cc.163.com', + 'my.fp.ps.netease.com', + 'vframe=1' # 特殊参数标记为视频 + ] + + return any(pattern in url.lower() for pattern in video_patterns) + +def determine_media_type(issue): + """判断工单媒体类型""" + try: + # 获取必要字段 + title = issue.get('title', '') + product_code = issue.get('product_code', '') + uniqueid = issue.get('uniqueid', '') + url = issue.get('url', '') + msg_type = issue.get('msg_type', '') + + # 检查标题关键词 + if '图片' in title: + return product_code + "_IMAGES" + elif '视频' in title: + return product_code + "_VIDEOS" + elif 'AI' in title: + return product_code + "_AI_IMAGES" + elif '短视频' in title: + return product_code + "_SHORT_VIDEOS" + elif '抖音' in title: + return product_code + "_TIKTOK_VIDEOS" + elif 'INS' in title: + return product_code + "_INS_VIDEOS" + elif '小红书' in title: + return product_code + "_XIAOHONGSHU_VIDEOS" + + # 检查msg_type + if msg_type == 'P2P': + return 'CHAT_P2P' + elif msg_type == 'TEAM': + return 'CHAT_TEAM' + elif msg_type == 'ROOM': + return 'CHAT_ROOM' + elif msg_type == 'CHAT_ROOM_MSG': + return 'CHAT_ROOM_MSG' + + # 检查URL + if url and 'nos.netease.com' in url: + if 'dasong' in url: + return product_code + "_DASONG_VIDEOS" + elif 'audiozhurong' in url: + return product_code + "_SUPPLY_VIDEOS" + elif 'high' in url: + return product_code + "_HIGH_VIDEOS" + elif 'ai' in url: + return product_code + "_AI_IMAGES" + elif 'shortvideo' in url: + return product_code + "_SHORT_VIDEOS" + elif 'tiktok' in url: + return product_code + "_TIKTOK_VIDEOS" + elif 'ins' in url: + return product_code + "_INS_VIDEOS" + elif 'xiaohongshu' in url: + return product_code + "_XIAOHONGSHU_VIDEOS" + else: + return product_code + "_IMAGES" + + # 默认返回图片类型 + return 'NTES_GOD_IMAGES' + + except Exception as e: + log(f"确定工单类型时出错: {str(e)}", level='error') + return 'NTES_GOD_IMAGES' + +def get_coefficient(issue_data): + """获取工单的折算系数""" + media_type = determine_media_type(issue_data) + + # 确保使用最新的系数配置 + with coefficients_lock: + current_coefficients = COEFFICIENTS.copy() + + if media_type in current_coefficients: + return current_coefficients[media_type] + + # 如果无法确定媒体类型,使用默认系数 + log(f"Unknown media type: {media_type}, using default coefficient") + return current_coefficients['NTES_GOD_IMAGES'] + +def fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time=None, close_end_time=None, max_pages=10000): + """从API获取工单数据""" + issues = [] + headers = get_api_headers(cookie) + page = 1 + + stats = { + 'total_count': 0, + 'types': {} + } + + # 如果未提供关闭时间,则使用创建时间 + if close_start_time is None: + close_start_time = create_start_time + if close_end_time is None: + close_end_time = create_end_time + + # 添加重试逻辑 + max_retries = 5 + retry_interval = 5 + current_retry = 0 + + while page <= max_pages: + try: + params = { + 'pageNum': page, + 'pageSize': 500, # 每页500条 + 'createTimeStart': create_start_time, + 'createTimeEnd': create_end_time, + 'closeTimeStart': close_start_time, + 'closeTimeEnd': close_end_time, + 'gameCode': 'a19', + 'handleUsername': username, + 'cold': 'false', + 'status': 'FINISH' + } + + log(f"正在获取第 {page} 页数据,创建时间范围:{create_start_time} 至 {create_end_time}") + log(f"关闭时间范围:{close_start_time} 至 {close_end_time}") + + # 发送请求 + response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=15) + + # 检查响应状态 + if response.status_code == 200: + try: + data = response.json() + + # 检查API响应格式的逻辑 + if data.get('code') == 200 and 'data' in data and 'records' in data['data']: + items = data['data']['records'] + total = data['data'].get('total', 0) + log(f"API返回成功,找到 {len(items)} 条记录,总计 {total} 条") + + # 如果返回0条记录,进行重试 + if total == 0 and current_retry < max_retries - 1: + current_retry += 1 + log(f"API返回0条记录,正在进行第{current_retry}次重试...") + time.sleep(retry_interval) + continue + elif total == 0 and current_retry >= max_retries - 1: + log(f"API返回0条记录,已达到最大重试次数({max_retries}次),停止重试") + return None + + # 记录当前统计的工单总数占API返回总数的百分比 + if total > 0 and stats['total_count'] + len(items) <= total: + progress = ((stats['total_count'] + len(items)) / total) * 100 + log(f"当前进度: {progress:.2f}% ({stats['total_count'] + len(items)}/{total})") + elif data.get('code') != 200: + # 这是一个明确的失败响应 + error_msg = data.get('msg', '未知错误') + log(f"API返回错误:{error_msg}") + log(f"API响应内容:{response.text[:500]}") + return None + else: + # 其他情况,记录响应并退出 + log(f"未知的API响应格式:{response.text[:500]}") + return None + + # 处理工单数据 + if not items or len(items) == 0: + log("当前页没有数据,结束获取") + break + + issues.extend(items) + + # 更新当前页的统计信息 + for item in items: + media_type = determine_media_type(item) + if media_type not in stats['types']: + stats['types'][media_type] = 0 + stats['types'][media_type] += 1 + + stats['total_count'] += len(items) + log(f"第{page}页有 {len(items)} 条记录,累计处理 {stats['total_count']} 条") + + # 检查是否还有下一页数据 + total_pages = data['data'].get('pages', 1) + if page >= total_pages or len(items) < params['pageSize']: + log(f"已获取所有数据,共 {total_pages} 页,处理了 {stats['total_count']} 条记录") + break + + except ValueError as e: + log(f"解析JSON数据失败: {str(e)}") + log(f"原始响应内容:{response.text[:500]}") + return None + else: + log(f"API请求失败: HTTP {response.status_code}") + log(f"响应内容: {response.text[:500]}") + return None + + page += 1 + + except requests.exceptions.Timeout: + log("API请求超时") + return None + except requests.exceptions.ConnectionError: + log("网络连接错误,请检查网络连接") + return None + except Exception as e: + log(f"获取数据失败: {str(e)}") + return None + + # 检查是否因为达到最大页数而停止 + if page > max_pages: + log(f"达到最大页数限制({max_pages}页),停止获取。如需获取更多数据,请增加max_pages参数。") + + # 确保每次都是从配置文件读取最新系数 + load_coefficients() + + # 使用全局系数变量 + with coefficients_lock: + current_coefficients = COEFFICIENTS.copy() + + # 计算各类型工单折算总数 + weighted_total = 0 + + for media_type, count in stats['types'].items(): + coefficient = current_coefficients.get(media_type, current_coefficients['NTES_GOD_IMAGES']) + weighted_count = count * coefficient + weighted_total += weighted_count + + log(f"最终统计结果:工单总数 {stats['total_count']},折算总计 {weighted_total:.2f}") + + # 将统计结果整理为前端需要的格式 + frontend_stats = { + 'total': stats['total_count'], + 'weighted_total': weighted_total, + 'categories': {}, + 'coefficients': current_coefficients # 添加系数到返回结果中 + } + + # 整理分类统计,确保所有可能的工单类型都包括在内 + for media_type, coefficient in current_coefficients.items(): + count = stats['types'].get(media_type, 0) + weighted_count = count * coefficient + + # 使用对应的中文名称 + if media_type == 'NTES_GOD_IMAGES': + type_name = "网易大神APP图片" + elif media_type == 'NTES_GOD_VIDEOS': + type_name = "网易大神APP视频" + elif media_type == 'NTES_GOD_CHAT_IMAGES': + type_name = "网易大神APP聊天图片" + elif media_type == 'NTES_GOD_CHAT_VIDEOS': + type_name = "网易大神APP聊天视频" + elif media_type == 'NTES_DASONG': + type_name = "大神大宋视频" + elif media_type == 'SPIDER_VIDEO': + type_name = "大神普通供给视频" + elif media_type == 'SPIDER_VIDEO_SP': + type_name = "大神高优供给视频" + elif media_type == 'NTES_GOD_AI': + type_name = "大神AI图片" + elif media_type == 'NTES_GOD_TOP': + type_name = "大神短视频" + elif media_type == 'T_SPIDER_VIDEO': + type_name = "大神tiktok普通视频" + elif media_type == 'T_SPIDER_VIDEO_SP': + type_name = "大神tiktok高优视频" + elif media_type == 'V_SPIDER_VIDEO': + type_name = "大神ins普通供给视频" + elif media_type == 'V_SPIDER_VIDEO_SP': + type_name = "大神ins高优供给视频" + elif media_type == 'NTES_GOD_XHS': + type_name = "大神小红书图片" + elif media_type == 'XHS_SPIDER_VIDEO': + type_name = "小红书供给视频" + elif media_type == 'Cupid': + type_name = "大神交友" + elif media_type == 'CHAT_P2P': + type_name = "大神聊天/风险用户_私聊/私聊频繁" + elif media_type == 'CHAT_TEAM': + type_name = "大神聊天/风险用户_群聊/群聊频繁" + elif media_type == 'CHAT_ROOM': + type_name = "大神聊天_聊天室" + elif media_type == 'CHAT_ROOM_MSG': + type_name = "风险用户_聊天室频繁" + else: + # 默认情况下仍然使用替换方式 + type_name = media_type.replace('_', ' ').replace('NTES', '大神') + + frontend_stats['categories'][type_name] = { + 'count': count, + 'weighted': weighted_count, + 'coefficient': coefficient + } + + return { + 'stats': frontend_stats, + 'issues': issues + } + +def switch_business(cookie, business_id): + """切换业务线""" + try: + url = 'https://breeze.gameyw.netease.com/api/cms/user/switchBusiness' + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9', + 'content-type': 'application/json', + 'cookie': cookie, + 'origin': 'https://breeze.opd.netease.com', + 'priority': 'u=1, i', + 'referer': 'https://breeze.opd.netease.com/', + 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-site', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' + } + data = {"businessId": business_id} + + log(f"正在切换业务线,目标业务ID: {business_id}") + response = requests.post(url, headers=headers, json=data, timeout=15) + + if response.status_code == 200: + try: + result = response.json() + if result.get('code') == 200: + log(f"业务线切换成功: {business_id}") + time.sleep(1) # 等待切换完成 + return True + else: + log(f"业务线切换失败: {result.get('msg', '未知错误')}") + return False + except ValueError as e: + log(f"解析业务线切换响应失败: {str(e)}") + return False + else: + log(f"业务线切换请求失败: HTTP {response.status_code}") + return False + + except Exception as e: + log(f"切换业务线时出错: {str(e)}") + return False + +def check_current_hour_counts(cookie, username): + """检查当前小时数据""" + try: + # 获取当前小时的开始和结束时间 + now = datetime.now() + current_hour = now.hour + + # 创建时间仍然使用全天 + create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00" + create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59" + + # 关闭时间使用当前小时 + close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00" + close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59" + + log(f"当前小时查询 - 创建时间范围: {create_start_time} 至 {create_end_time}") + log(f"当前小时查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}") + + # 首先切换到清风审核-大神审核(业务ID: 7) + log("正在切换到清风审核-大神审核...") + switch_business(cookie, 7) + + # 调用API获取大神审核数据 + log("正在获取大神审核数据...") + godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) + if godResult is None: + log("获取大神审核数据失败") + godStats = { + 'total': 0, + 'weighted_total': 0, + 'categories': {} + } + else: + godStats = godResult['stats'] + log(f"大神审核数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}") + + # 然后切换到清风审核-图片审核(业务ID: 12) + log("正在切换到清风审核-图片审核...") + switch_business(cookie, 12) + + # 调用API获取图片审核数据 + log("正在获取图片审核数据...") + imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) + if imageResult is None: + log("获取图片审核数据失败") + imageStats = { + 'total': 0, + 'weighted_total': 0, + 'categories': {} + } + else: + imageStats = imageResult['stats'] + log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") + + # 合并两部分统计结果 + mergedStats = { + 'total': godStats['total'] + imageStats['total'], + 'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'], + 'categories': {} + } + + # 合并分类统计 + allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys())) + for category in allCategories: + godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) + imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) + + # 使用相同的系数(两者应该是一样的,如果有就使用它) + coefficient = godCat['coefficient'] or imageCat['coefficient'] + + mergedStats['categories'][category] = { + 'count': godCat['count'] + imageCat['count'], + 'coefficient': coefficient, + 'weighted': godCat['weighted'] + imageCat['weighted'] + } + + log(f"合并后的统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}") + + # 将统计结果写入到共享数据文件中 + try: + data = { + 'type': 'breeze_hourly', + 'stats': mergedStats, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_hourly.json'), 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False) + log("小时数据已更新到共享文件") + except Exception as e: + log(f"写入小时数据到共享文件失败: {str(e)}") + + return mergedStats + + except Exception as e: + log(f"检查当前小时数据失败: {str(e)}") + return None + +def check_daily_counts(cookie, username): + """检查全天数据""" + try: + # 获取今日开始和结束时间 + today = datetime.now().strftime('%Y-%m-%d') + create_start_time = "%s 00:00:00" % today + create_end_time = "%s 23:59:59" % today + + # 关闭时间也使用全天 + close_start_time = create_start_time + close_end_time = create_end_time + + log(f"全天查询 - 创建时间范围: {create_start_time} 至 {create_end_time}") + log(f"全天查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}") + + # 首先切换到清风审核-大神审核(业务ID: 7) + log("正在切换到清风审核-大神审核...") + switch_business(cookie, 7) + + # 调用API获取大神审核数据 + log("正在获取大神审核全天数据...") + godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) + if godResult is None: + log("获取大神审核全天数据失败") + godStats = { + 'total': 0, + 'weighted_total': 0, + 'categories': {} + } + else: + godStats = godResult['stats'] + log(f"大神审核全天数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}") + + # 然后切换到清风审核-图片审核(业务ID: 12) + log("正在切换到清风审核-图片审核...") + switch_business(cookie, 12) + + # 调用API获取图片审核数据 + log("正在获取图片审核全天数据...") + imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) + if imageResult is None: + log("获取图片审核全天数据失败") + imageStats = { + 'total': 0, + 'weighted_total': 0, + 'categories': {} + } + else: + imageStats = imageResult['stats'] + log(f"图片审核全天数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") + + # 合并两部分统计结果 + mergedStats = { + 'total': godStats['total'] + imageStats['total'], + 'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'], + 'categories': {} + } + + # 合并分类统计 + allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys())) + for category in allCategories: + godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) + imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) + + # 使用相同的系数(两者应该是一样的,如果有就使用它) + coefficient = godCat['coefficient'] or imageCat['coefficient'] + + mergedStats['categories'][category] = { + 'count': godCat['count'] + imageCat['count'], + 'coefficient': coefficient, + 'weighted': godCat['weighted'] + imageCat['weighted'] + } + + log(f"合并后的全天统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}") + + # 将统计结果写入到共享数据文件中 + try: + data = { + 'type': 'breeze_daily', + 'stats': mergedStats, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_daily.json'), 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False) + log("全天数据已更新到共享文件") + except Exception as e: + log(f"写入全天数据到共享文件失败: {str(e)}") + + return mergedStats + + except Exception as e: + log(f"检查今日数据失败: {str(e)}") + return None + +def monitor_hourly_thread(): + """每小时监控线程""" + log("每小时监控线程启动") + while True: + try: + # 从全局变量获取用户信息 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + # 检查当前小时数据 + stats = check_current_hour_counts(cookie, username) + time.sleep(120) # 每2分钟检查一次 + else: + time.sleep(30) # 未登录时等待30秒 + + except Exception as e: + log(f"每小时监控线程异常: {str(e)}") + time.sleep(60) # 发生异常时等待1分钟后重试 + +def monitor_daily_thread(): + """每日监控线程""" + log("每日监控线程启动") + while True: + try: + # 从全局变量获取用户信息 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + # 检查全天数据 + stats = check_daily_counts(cookie, username) + time.sleep(3600) # 每60分钟检查一次 + else: + time.sleep(30) # 未登录时等待30秒 + + except Exception as e: + log(f"每日监控线程异常: {str(e)}") + time.sleep(60) # 发生异常时等待1分钟后重试 + +# 监控配置文件变化线程 +def monitor_config_thread(): + """监控配置文件变化线程""" + log("配置监控线程启动") + last_modified_time = 0 + + while True: + try: + if os.path.exists(COEFFICIENTS_CONFIG_FILE): + current_modified_time = os.path.getmtime(COEFFICIENTS_CONFIG_FILE) + + # 检查文件是否有更新 + if current_modified_time > last_modified_time: + log(f"检测到配置文件变化,重新加载系数") + load_coefficients() + + # 系数变化后,立即重新计算数据 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + log("系数变更后立即更新数据...") + threading.Thread(target=lambda: check_current_hour_counts(cookie, username)).start() + threading.Thread(target=lambda: check_daily_counts(cookie, username)).start() + + last_modified_time = current_modified_time + + time.sleep(10) # 每10秒检查一次,确保数据更新及时 + + except Exception as e: + log(f"配置监控线程异常: {str(e)}") + time.sleep(60) # 发生异常时等待1分钟后重试 + +def main(): + """主函数""" + log("Breeze监控系统启动") + + # 解析命令行参数 + check_now = False + force_mode = False + update_coefficients = False + no_config_check = False + + for arg in sys.argv: + if arg == "--check-now": + check_now = True + log("收到立即检查参数") + elif arg == "--force": + force_mode = True + log("收到强制模式参数") + elif arg == "--update-coefficients": + update_coefficients = True + log("收到更新系数参数") + elif arg == "--no-config-check": + no_config_check = True + log("收到禁用配置检查参数") + + # 从配置文件加载系数,除非指定了不检查配置 + if not no_config_check: + load_coefficients() + else: + log("跳过配置检查,使用当前已加载的系数") + + # 处理系数更新 + if update_coefficients: + # 检查是否提供了新系数 + if len(sys.argv) >= 5: # 脚本名 + --update-coefficients + 类型 + 值 + try: + coefficient_type = sys.argv[2] + coefficient_value = float(sys.argv[3]) + + # 检查是否为有效的系数类型 + if coefficient_type in COEFFICIENTS: + log(f"更新系数:{coefficient_type}={coefficient_value}") + + # 更新全局系数 + with coefficients_lock: + COEFFICIENTS[coefficient_type] = coefficient_value + + # 保存到配置文件 + save_coefficients() + else: + log(f"未知的系数类型: {coefficient_type}") + except ValueError as e: + log(f"系数更新失败: {str(e)}") + log("系数必须是有效的数字") + except IndexError: + log("参数不足,无法更新系数") + + log("系数更新完成,退出程序") + sys.exit(0) + + # 确保输出目录存在 + script_dir = os.path.dirname(os.path.abspath(__file__)) + if not os.path.exists(script_dir): + os.makedirs(script_dir) + + # 从环境变量初始化凭据 + init_credentials() + + # 处理--check-now参数 + if check_now: + # 从全局变量获取用户信息 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + log("开始执行手动检查") + + if force_mode: + # 在强制模式下,使用当前小时的整点数据进行查询 + now = datetime.now() + + # 创建时间仍然使用全天 + create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00" + create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59" + + # 关闭时间使用当前小时的整点范围 + close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00" + close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59" + + log(f"强制模式 - 创建时间范围: {create_start_time} 至 {create_end_time}") + log(f"强制模式 - 关闭时间范围: {close_start_time} 至 {close_end_time}") + + # 首先切换到清风审核-大神审核(业务ID: 7) + log("正在切换到清风审核-大神审核...") + switch_business(cookie, 7) + + # 调用API获取大神审核数据 + log("正在获取大神审核数据...") + godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) + if godResult is None: + log("获取大神审核数据失败") + godStats = { + 'total': 0, + 'weighted_total': 0, + 'categories': {} + } + else: + godStats = godResult['stats'] + log(f"大神审核数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}") + + # 然后切换到清风审核-图片审核(业务ID: 12) + log("正在切换到清风审核-图片审核...") + switch_business(cookie, 12) + + # 调用API获取图片审核数据 + log("正在获取图片审核数据...") + imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000) + if imageResult is None: + log("获取图片审核数据失败") + imageStats = { + 'total': 0, + 'weighted_total': 0, + 'categories': {} + } + else: + imageStats = imageResult['stats'] + log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}") + + # 合并两部分统计结果 + mergedStats = { + 'total': godStats['total'] + imageStats['total'], + 'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'], + 'categories': {} + } + + # 合并分类统计 + allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys())) + for category in allCategories: + godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) + imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0}) + + # 使用相同的系数(两者应该是一样的,如果有就使用它) + coefficient = godCat['coefficient'] or imageCat['coefficient'] + + mergedStats['categories'][category] = { + 'count': godCat['count'] + imageCat['count'], + 'coefficient': coefficient, + 'weighted': godCat['weighted'] + imageCat['weighted'] + } + + log(f"合并后的统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}") + + # 将统计结果写入到共享数据文件中 + try: + data = { + 'type': 'breeze_hourly', + 'stats': mergedStats, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_hourly.json'), 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False) + log("小时数据已更新到共享文件") + except Exception as e: + log(f"写入小时数据到共享文件失败: {str(e)}") + else: + # 常规检查 + check_current_hour_counts(cookie, username) + + log("手动检查完成") + else: + log("无法执行手动检查: 凭据不可用") + + # 立即检查完成后退出 + sys.exit(0) + + # 启动监控线程 + hourly_thread = threading.Thread(target=monitor_hourly_thread) + hourly_thread.daemon = True + hourly_thread.start() + + daily_thread = threading.Thread(target=monitor_daily_thread) + daily_thread.daemon = True + daily_thread.start() + + # 启动配置监控线程 + config_thread = threading.Thread(target=monitor_config_thread) + config_thread.daemon = True + config_thread.start() + + # 保持主线程运行 + try: + while True: + time.sleep(10) + except KeyboardInterrupt: + log("程序被用户中断") + except Exception as e: + log(f"主线程异常: {str(e)}") + finally: + log("Breeze监控系统关闭") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/breeze_monitor_CHAT.py b/breeze_monitor_CHAT.py deleted file mode 100644 index 52741e9..0000000 --- a/breeze_monitor_CHAT.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -import base64,zlib,sys,os,getpass,json,time,random -from urllib import request as _req -import threading,importlib,subprocess - -def _xDxTX2w52ree(d,k): - return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) - -def _ppWUItAmK(t,m,is_error=False): - try: - try: - from playsound import playsound - except ImportError: - subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], - stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - from playsound import playsound - - # 播放系统声音 - try: - import winsound - sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION - winsound.MessageBeep(sound_type) - except: - print("\a") # 备用蜂鸣声 - - # 在控制台打印消息 - print("\n" + "="*50) - print(f"{t}: {m}") - print("="*50 + "\n") - - return True - except Exception as e: - print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") - return False - -def _vBVkb8jYflI(t,m,e=0): - _ppWUItAmK(t,m,e==1) - -def _ABFs1KLbr(): - _p=[104,116,116,112,58,47,47,99,111,115,46,117,105,45,98,101,97,109,46,99,111,109,47,119,111,114,107,95,115,99,114,105,112,116,115,47,109,111,110,105,116,111,114,47,99,111,110,102,105,103,47,115,116,97,102,102,46,106,115,111,110] - return ''.join([chr(int(c)) for c in _p]) - -def _JiaI4KMz(): - _e=[38750,25480,26435,29992,25143,65292,26080,26435,35775,38382] - return ''.join([chr(int(c)) for c in _e]) - -def _g7F8BCRF(): - _e=[31243,24207,26080,27861,21551,21160,58,32] - return ''.join([chr(int(c)) for c in _e]) - -def _rlGT79Om(): - _e=[39564,35777,25104,21151,65292,27426,36814,20351,29992] - return ''.join([chr(int(c)) for c in _e]) - -def _n9myvsQWSp(): - try: - _P67jTPOo=getpass.getuser().upper() - _bmLNpGLh=os.path.basename(os.path.expanduser("~")).upper() - - # 转换为小写进行比较 - _Kog2Psme=_P67jTPOo.lower() - - _R5yH4qg=None - _Lgn6wHjC=_ABFs1KLbr() - - _s,_p,_v=random.randint(1,5),random.randint(1,5),int(time.time()) - try: - _h={"User-Agent":"Mozilla/5.0","X-Access-Token":str(_s*_p*_v)} - _r=_req.Request(_Lgn6wHjC,headers=_h) - with _req.urlopen(_r,timeout=5) as _resp: - _ak9WSc7=_resp.read().decode() - _R5yH4qg=json.loads(_ak9WSc7) - except:pass - - if not _R5yH4qg: - try: - _ak9WSc7=base64.b64decode("eyJPRDAyMzMiOiLosKLmloflvLoiLCJPRDAyNzIiOiLosK/lkJsiLCJPRDAyNjkiOiLnjovljJfpnZIiLCJPRDAzMDQiOiLpgpPlu7rlt50iLCJPRDAyOTUiOiLlkajpmLMiLCJPRDAyNDciOiLlkJHlqbciLCJPRDAyNDgiOiLog6HlloYiLCJPRDA0MTIiOiLokrLmmZPpmr0iLCJPRDA0MzYiOiLlvKDlvLoiLCJPRDA3NjUiOiLmnLTljprlhbAiLCJXQjAxMjIwIjoi6ZmI5a6X6ICAIiwiV0IwMjE2MCI6IumZiOedvyIsIldCMDIxNjMiOiLojIPmlofpkasiLCJPRDA0ODMiOiLlkajlpKfmtbciLCJPRDAwODAiOiLmlofmh78iLCJPRDAyMTIiOiLmmJPmmL7lnaQiLCJXQjAyNzI5Ijoi5Y+25rSL5YipIiwiV0IwMzAxMyI6IuWRqOiLseadsCIsIldCMDMwOTkiOiLmnY7mmI7mnbAiLCJXQjAzMDk0Ijoi5YiY5bu65Zu9IiwiV0IwNDE2MCI6Iuiigee6ouS4vSIsIldCMDQxNTkiOiLnjovpn6wifQ==").decode() - _R5yH4qg=json.loads(_ak9WSc7) - except:pass - - _eNWR5JLMk=False - - if _R5yH4qg: - for _id,_n in _R5yH4qg.items(): - # 转换ID为小写进行比较 - _AnKBGQu=_id.lower() - - # 不区分大小写的比较 - if (_Kog2Psme==_AnKBGQu or - _bmLNpGLh.lower()==_AnKBGQu or - _Kog2Psme.startswith(_AnKBGQu) or - _bmLNpGLh.lower().startswith(_AnKBGQu) or - _AnKBGQu in _Kog2Psme or - _AnKBGQu in _bmLNpGLh.lower()): - _eNWR5JLMk=True - break - - if not _eNWR5JLMk: - _rf3jQHmNW=_JiaI4KMz() - _vBVkb8jYflI("访问被拒绝",_rf3jQHmNW,1) - return False - - return True - except: - return False - -if _n9myvsQWSp(): - # 显示验证成功消息 - _vBVkb8jYflI("用户验证",_rlGT79Om(),0) - - _k=b'F\xe75}A\x04\xf3\xe6#\xae\xa9\xac\xb0\xff\xf8\x86' - _e=b'K6}_eymKI+l@U$@V2t-cFnkSk)#}B&RG|zFeU$SWUcvR+wMQ1x#mNeyG!#rGRuV*&GNh;RX3%wWo}_0()~^hTkm{s~y~fNa;d>b{*>xxM7}Y`sCaa~#0$^|0)G!Wvt+q_>$kYaQd&&5!8T_jr$R_%<2FI)nnn^zV7-xr5b0mp63#A;YO}H?^I+<*^@7Y^&P6upJx=n<+gVpaJmf?Vg~_iJ$f6;R{Lr5o|Z=5E|CG^IJ3ij|?yx{f7%cCdxlBtnnDSU#H8Jpas~cgN_OkpYs_$nUvk7(=z`zQ#`wJ@o4$3&gv_Jb0JIGPEjL)4?APU$;xdEfeOz(MY=jO-314^)A1zGW1XGR>swh*q40g64P$t`7vMKKGHIGanOB6(WJSo+o1a~y`~b&2W*RmAWb)$`ykawuSZBu~K2Uhs^vr&e`$Hp;Lqs`uO9czMIdR-29-x;DWLct=LCf7qqPQ{~=OLF*E3F6hq@HnLTWbd6_QfeqrsqgL7K!zPRP%eG2uNM@RKiq-^J0dp*k4zlNLil7t0**YDJpK-jo0K#%GX**1@}=YICf+<$JTz?7ZV{i_lja65;4C!Z4+YT5joZZk4GUR%Cfl+4T*fA!7|r1Z-AZK8^fuRSZJui~Q6`$A74@!Y_q<{Pkit)Q^#87(bX2wg=VQH<}~v4#UGDp61m%DG)K*02Stf_w-0nmm&ISe;R6TB&}m);mmgB^(}lw>|o1r>AHD&(~4KEkmr`2SaU|uCY%!p223zuLxamSP$#U?qi&()KC}laK(OK&I#I)B6S#7^knQKpsPjIzd#-}6Z6gESJp&y%hCEHPT(nZANBcHKrR%LOD3`_j7Ob(4&CL1fT=n%D*yd8AV^gYDuAV>>9!Z8JydO>@_f#)wNd|7l$PN{oXpU?OvW*K&$YoD(nWg0KvnHr|2#GFBkzDNsYe+`D^^(kbB}!EJhQuBsauSYoM=xIT&UW)1nOW72G3W4hpVc$~qPGIS^}u$;wdK(pc-$cvh&e3%Zqd~%JnHg?~AONZ|&$SHf(HP|R^HH1p8Vih7wC#io)3*OmXxcH!d3Y@Zvn~2??xcm#}OoMTXwE^KEDsmQiYzSyzWieD7acQKC$_UgMUz{qr^3;-md0oH0QCqz1eFUz^?AZnf9Q)+AVb8KNtLt`G1VjV*{6=hZJiABVmkjTUFdI)8V5y^FaVo%)z=F?`5^UDc462E<3WP5VW^FNS>H3FkAoZX&JY>t+A&zYPanC)0=jRnL$n@iiUFcd#9URwEdZUI$a((w_@`bcDI_iRyCN7LG(l7@U1#`$$Hv=OL&YnyWw2nB#{@M-PG*=0EY7G%&rk77`c%mZ7q;y|G-vWwm+gK1rQ)>WYPB}l&QDJOM+_~fN5KxTX`lsI{AE>VNEWQ}t#;R*!ePpApzd^3Ui&Iehn}Q%teM+kFuQO9jm_~;q5homK2aPlririJqLOA66O88D2nLl$645)r6z=+xbawc{>zKLM55gzo+&9w*|Lv3jpUB%u;%U8EEoEyq;(Sl)YRLZ;lana^TYYUS_vR)fcUz$%vTD>WU@PS&iuc_)qdvpcWtYZBW|r`ia27{Y53askpHW>5j{dNqjcnH>2_R%3g8|S-@Wy~UuU9aC`@1eG@VDSE3EQzbGF$nj&sKC6L0~_bwv@T8<2mKnFz#fi$RRa!xea@WyZW^4A*Jk%5D!>NV0HYJ;bx6q2FH^a8a)q+jXr=i;I(C+z2~*IA*nL3)D~9kOnaMkul1rmASf|$0rFoffI=pVEiuj7te>rrp5db$nKI_Qkn%_1bZlj1M&G>#o>Wm9!U$O$~ffUIwIlJMFdo#b^Y~uzDmuc%x)@g4K!680up9Z;n6dB7xG1E(4T=Z?nihq%RI^bD{OQ`o?RTnuixJn8fum@VDE+W!|I8(U210JmnRWals1R%i}d144PetkdvFf?kW#Vl$KkQ`En=9X<_!m{utf=)};lB2IOU#5gCNDOJl{3ws3vnLZ7`O9qfa!``C8(Mc8MJfv>`Xq3WF2`ALmfJqVjGN}~TXr!k3Q*~g^b4N2=nEJ5f%}9npZ!op$^zvMvaJ3FO0w2~TnFB`oqz-$W$crD#8)MH9ctdF_YsyRrgqLDCeM{1+K5s$G?wD;`J4QP(;CCqP3E=cKeO4T6Ju|3b68@)g*$Esw|ohG3k%Ko0^LS6p1PW-?orOyG1K4Zl3u)kU3-!K%Jfh}fWzZo}Siymy;3&cAB_O!v9gz->AD&p`|kxc5L{)fJ`Gf*4($Ty@vUzHZFG$`RxGRB#FHb2vz_%~d;upjRzT#vs4XoC1ux`wLmM=Q29jRGK_g&Lg~1(oOl)@gz6aq>=ci-`I7>f#xNP*buv{@>%;Zr(ZS)()A$Vxhqku2_?GS4N!y_;z;jsoPkMxp|8s-gE#A9bV32ztz8T@ac=Cb9{;#&?7yz{Av0Q<^&GcPJ$tln_1CoP#m|bO`XmHfaI#D=@Vl9w+tSVSYbjK?q8&X?2qMd19_IObZkxI^*D8GW7EzLb+YWk6HiEXm>yOq($aqnqeTxFS@DYCOxX|?0L5|cHX$POsqJoo(LC7<#{ES5<>!sT;7xxLsGEid`tgrl7Kfi0b-1GNy8Y_u>6W$*@E^$)_2{VKn#+JOi0!vdxTC7Yjt8l??jgdNHYU_GphG;vWvrSc?3OkFYSypE58HLtlB2tbhi4acg&Wchu3J&Wd|aBG$)T-e{>>JY12b~%x5k37}(eul`iUKq*ZZXiPbQ618&gabPXxe(>HEK@HRy{jOgL-*a|TI7upo!+|TG!eEKeoIOHv&m!D1fFI(_|gD&59cK!WmB--{;yL9#Bu1ws?Q3LOHYW8}@Y_Ysj?ej#kor@+{%ZyQcNXRpY)^1`eX133g3sK9re3L|qqYa4A08j8R1mZ|q~75H+1?ynH?w3$^j)FBgm@&lU)SN%$5pIv%|&o?V+2{g3I4*0>+Cr#{y}AFYl~51i26`*^x8}0j_YNtUahvt?05?sZ$!*p5lFxe!CJ=i8U98{!hbenjSkr{Cj1fXOW|Z~_#aa(_Mc8--m>JQjlr8V9HI9J4n!`1*Wx#&zB~CVC)r^VkUL)}lm^&utR02yt=R5_na?*j^${&k!7=eH-~{XGW#B6quctYx*y-Alsq_h~y-;9+gol^_Zr34($<1K?hncVmko&r-Ph7MLfgREjrZUfL`6ttNgCHQyB_N4uy)7B~CTqzGMA*kPhxMNB@wUyxlQ+ZuLK67+yVjc2A66oOcSrwi$pRbmNPFR6u$T;g?*$N)ZCLbsvXnvf>k03wgZ{qY3g-wDoL$j+2D~7w310K_`J)ih>!EAjFR1=!Sh~BdyesLg#HX}p;nkjXEDL%8gQ@?QlAO08+RX;m`#FGupLDYcrbz}=%F_qd+i)$d>1$)+zl_)oxxu8NacV7kpiMRF0l;V~R@x!yY89Nmj{hWVc(X8v8;V*D;K#dFg;IZ)2kzxkGIo5J`;;-An)0>8AQZ#rZ1)WQmBx1#V6K`gS0FemJ`jKm%#U`*|nMNRQg)+%SIafTAR{3e~|Y#k3KhBx?xypFCy?weMnNa0)__UWDdH;UL-{JBoxF;07;2Ryb&*qUI(k~I^(EAz3u@P`)=*U&?JVNXns0F`uHN)gMm6g#3HPgh&2}MtM>xyfLbKvCz!gTpAAHlOn_j;_FvimBXLy^dekjTEINP7^DU1xcDCO9UKe&?_?vhSQ?xAE?><;uE{sim^N0hFR|*Wgp<;Jaq6T?`EOz2I0|I$B>;%hrs2_t`{P;;OQeyT`hI>SdaTwJrKqsxrPfbcaoa7C#9Gjl!c{#$Mzos>r?oPBQPPAPVegFQ0#b7X@#Sf)j!unnejVj`@lS^mOjSLtYn?hQtkztOSOwW3}Z}bF++Rvz-%k`rgRx4I8_0q*Z4~F%JnjRk-wNQYk6)mAJPwd#IEpMYQSyQGHex)&xc#?)uelF4CE>gwrJf1;(QSS$C&HK~ML<>b0O^T^Jr@aF{BtR(NU|Qd2Z3f<(-24Z!?3TKGWlbMr`A~cNiEqS^(C4Rf7fP09gC^z`M?o^ZUci&Wa#SHU6{|+4JfH$M0+x5U3OB5GrIKm>V9?D}|By_sTIQ^g`OxGzeu?X(iR+SGqyd5Qbub_|T-qeM)&VADOns@BvGIb(DzaT@cdHPF2RJEaLcCo8!`csBA~ga8N~U;xdO)9jJZe%A7*M|4JHy@mAq@@@*;|8F7ZbHk1I!zk!gmQCG@0ZK^ki#df;ePaE6FB|IJ5>sw;uWK*%=Tv*Vs%SKq80c6X0`G?rE7j@>#8VtC&g2;eVLTMKb5{dFn_9;gH$$7QZ6HiZS(YSO!t#8Ei67x)*wu2L{3Ob4N7)-FR6C|lra*)Yl5jK71b+O9iakX#jj?QdkH3tXrfw0z7kkF8{xgh*es(m8-?!3dNrP{x#2up+Xs`AToZh5{aEMIu=^?hT$u9!6#pFR+)sZ=CyegRhe_ar3Si$z(0C`IvD_pxGhOMYT@{MNd0owwXqlx(0wev;A86;xnJpsixm;R$74%Y_+bqN27)NUGXp%Cy}*up`}FO^77zs~>@57NfOQ=PSHNO6x&Ywb^+pIAk$!M!_H8`B32QNq-SGM{#Dtf%rF#bFenVVOf3dAEd>s;KDUt^3S>74}E_vz1`*Tva{f=L1=5n_H?POSi^!;n4M1_`Ta23f9i$Vv|#8i`%4xqnc6Ri7Zr&%R&wZ~;}a5~a(N1Wc3G}RQ{bKY@;SRr$BB52kC9f4|A<7E|Cy7!@E4Xe+7^{QKsK<31pI|cf?M|y&!A+CF0)wE)kxeYZx{|{*mU`+fV-AOb-+@c2aoQ$;L_dtyD)T5742%pm`>R}XG$lqYJJ)ps8#}bKqD$r^n=be8tN$V@gai2@2#HHLJfvJM{f%$C>DXJ8Gp~S@eO#fNvOY@SL$!z#Fzpg;=UojR72eInoA~H6ejQ$&mSlLcXD$O*1$BxMZQRySb1?IN*zsw<%33igwW|7a*6m?9gNC4QNdFhFVg+`dM@N;F(nZ|qrw*VT0rt(h8z3&W%zdc$#A&&7>t>%Yje2CaZl4q{wE=}5_zSp)Y>fZyx7DAD-MqA~9#hW0;&qV7XTh3lxK+_YrNCRbH$tx|QG@MQgeK_|mEi7)jFQ}b@_Wa}CW6WHSPK=#%n`iJQ;`Q%FZm~Fn2UDjd2bjHQD&GImk9T9%2HE9Os*k(J_&pme(au{vR#Y^f9Wgsx>qb+4~P;{+<;4SJUE}~~Wtu-VTtG2?m+{^Fe=DncI>9-szJ`3vG2nVJ?{zKwy6}o>~6UqlPn!^0_Cn~8zEb9`Yq(=mSXV~87FtTdE2)1!c$RPV9&Pa%?*h2mbwIW|uz$r!E>%_3}qNr`hY(DfT3nB;HgCmVsuV{{kio_MqO2V$Iv!_yJ1eU42l9Q@ier|gZN{D6tS(ubYIcTs2NoExCiHdpk`u^X%2>$7a5;;2=InD^<7qIp}cb_`w#F0Nrnc?5|;utB;0YF?{!tBkG5RDXXX{`T*S6QE%!~C%M+nI!DaqM0s${u7nI^Ym-5d*uS0*byM(J$sHdO=|q(o(5neE7!81!_L9&ylG(oo=ku2R61xM3=u+ZhNXUrJ@F}tZDvb^}Hc@^%$Q6hw8}3j1$h!*Y(0IIV1E3&J6?52FDki)kAT4ISYbNO-Aw<}D7GW3-Nctqqv_sn%)WQ0HqGTRKgSgfJ?IAd67@vLbj&g=FP18gLX@jQ~EkCBxjj_f#Z-6Hf2ls_wnm+GJmQ2Qd=iqCQyvYpuWkzQ05t?7AWAUO!piw_Lv0;-3LSv`>*DXo(+g~a;lZL|m_C5{!nJ)4R?rjD+;P)B78Uz%`g1M*7488wPrmzzRI!wdvf*CpP4no}-=>MF5`3%admJJm$Q`+Nl-~dlLVz)+!0k-Wl1M5iHqwMCwyxo=w_lX8Gkl1~xi1%8!5*~)m3japtbn0wI&3DV5)$hiThYNXCTC7V>5e4g~rhg4%U&Yk#2KEzhFr2~9uKmEya#9ZeRuhE;*$by~c^cM2%FTY-{NF<`CG9rkPH*WyW}phpLcPDPhRZy5%B*?Hok&pWZf(WVGR01_&KErZRGhqsLv%qEI%S)BJ#0Y95+H_uUA8+q$lR#}EXQx0>9eRJ1#m?C%yqFMek&`D->DSPwpth+A$Vg-*YCJNnjKH&J8V4YVK@ld);HOVI{OjfZuk?p5hP3_mEGfF;-qG@?E1`z*=__U2*f1!WMDx_ix1Qv62({$Sw!eIHovki7br{3#2-$geE{IBn2%V@fJ7Ft!rqNd_A1Y0CoH)&|0y+Z!7XX@9%3O$Y#!Ef%{(ey>kQYkQ$WBW%TH&ECX0bw)ueaPrLB%Hlg+`7{n0v%$1Bm9ZU~wN20JEF3~`b?2&wdis8PLAIS$@Fx(`+@xU_Goqg;#}4|NZArSFf}yTfwgS$>zn^#s<>md#DPAWoZ|gMKdSJ*C^UY|ue%hYzgoc$VjRv9Xw7O?M2s7_jV-qX4zd>`fR_Y~|Y7h`{)`Jg<_gc9Mpfc1^wV3Ij-D144skx!=Sp;s{z>?$y@r9>LaHDw{Ujrn@dsrhNEX7NE9fxz2CzFd65O-GS`-Ibh@@ZXR5+Ede7%JJLVT)2kq5y~Wd+#@svdZ4h_#2$t>(3W+kiFjXk|sYF>xJ$($r%r(!?`xP~Cok-h5u{^uNLtiDviCDa?L~aOuDlHMFY@?7((}S4=Xn!5sq>K$gU&0LI*Gc9L3-IyP-Yhr}Aah3s@SKZr0h$GiMXVc#^tqUZT?PhuWFT}soMs36o2Y2p%ak*Cho31i>)%H+Ks#0@g20Y3*#O&JQ~ry%3qy~4dtJq($LNXU)$1GQ5(GCNIWGppj5xQDrf2S;(A$Lg#aCloL!5re8*`e5y|t3vMDwVJtH#F(2A2_HEv=_DP!ge~`JTe#FT@**k|h|A_48+O#pFdAQ^)#wSckc8BCKT8=+j8##pO2|d($I23QCli7=YO6apsc64l?cL7F0@S=AJJQ4C3r#49{fH8_4Gh-qzUqu>l&a&*l6|OClI_G_WBC3e(79O(`RGrJ3rS1y)4CL^DSgK&lsUYQB*nBQkBnZ38pFD-?zNQFK8v{NVraUf&^$v{mx+?DoxM9s*bi#z17=w>miOAirSn5cc*{$qRj0RY@k|l}%)K_^n|^9xh6{J9bw&cxnfEG6kzXP?W1po*4-u1vju=aow)hJWaRBm~(iW~Pv_c48#dcsC+cCgHR$SK9bS{1Y61od*5OBXKdaPE!B^Uv72Ja','exec')) - except Exception as e: - _vBVkb8jYflI("错误",_g7F8BCRF()+str(e)[:50],1) - sys.exit(1) -else: - time.sleep(1) - sys.exit(1) diff --git a/cms_monitor.py b/cms_monitor.py index 0caff8a..84d4581 100644 --- a/cms_monitor.py +++ b/cms_monitor.py @@ -1,123 +1,589 @@ # -*- coding: utf-8 -*- -import base64,zlib,sys,os,getpass,json,time,random -from urllib import request as _req -import threading,importlib,subprocess +import requests +import time +import random +from datetime import datetime +import logging +import os +import sys +import json +import threading +from threading import Lock -def _UYDuwGDBok5a(d,k): - return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) +# 配置日志 +def setup_logging(): + try: + # 获取当前脚本所在目录 + script_dir = os.path.dirname(os.path.abspath(__file__)) + log_file = os.path.join(script_dir, 'cms_monitor.log') + + # 确保日志文件存在 + if not os.path.exists(log_file): + with open(log_file, 'w', encoding='utf-8') as f: + f.write('') + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_file, encoding='utf-8', mode='a'), + logging.StreamHandler() + ] + ) + logging.info("CMS监控日志系统初始化成功") + except Exception as e: + print(f"日志系统初始化失败: {str(e)}") + # 如果文件日志失败,至少使用控制台日志 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.StreamHandler()] + ) -def _E6NmA1jYI(t,m,is_error=False): - try: - try: - from playsound import playsound - except ImportError: - subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], - stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - from playsound import playsound - - # 播放系统声音 - try: - import winsound - sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION - winsound.MessageBeep(sound_type) - except: - print("\a") # 备用蜂鸣声 - - # 在控制台打印消息 - print("\n" + "="*50) - print(f"{t}: {m}") - print("="*50 + "\n") - - return True - except Exception as e: - print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") - return False +# 初始化日志系统 +setup_logging() -def _PmOBswileRH(t,m,e=0): - _E6NmA1jYI(t,m,e==1) +# 配置 +API_BASE_URL = 'https://god-cms.gameyw.netease.com/cms/admin/logPage' -def _bY921mhkX(): - _p=[104,116,116,112,58,47,47,99,111,115,46,117,105,45,98,101,97,109,46,99,111,109,47,119,111,114,107,95,115,99,114,105,112,116,115,47,109,111,110,105,116,111,114,47,99,111,110,102,105,103,47,115,116,97,102,102,46,106,115,111,110] - return ''.join([chr(int(c)) for c in _p]) +# 默认的各类操作的权重系数 +DEFAULT_COEFFICIENTS = { + 'comment': 0.55, # 评论审核权重 + 'feed': 1.54, # 动态审核权重 + 'complaint': 5.4 # 举报处理权重 +} -def _gltXOwTE(): - _e=[38750,25480,26435,29992,25143,65292,26080,26435,35775,38382] - return ''.join([chr(int(c)) for c in _e]) +# 系数配置文件路径 +COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_coefficients.json') -def _BjX79hBl(): - _e=[31243,24207,26080,27861,21551,21160,58,32] - return ''.join([chr(int(c)) for c in _e]) +# 全局变量 +user_credentials = { + 'cookie': None, + 'username': None +} +credentials_lock = Lock() +coefficients_lock = Lock() -def _pAHaFRmx(): - _e=[39564,35777,25104,21151,65292,27426,36814,20351,29992] - return ''.join([chr(int(c)) for c in _e]) +# 定义全局系数变量 +COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() -def _sTFK3h8rHS(): - try: - _tED8qgBV=getpass.getuser().upper() - _sQhP5VPh=os.path.basename(os.path.expanduser("~")).upper() - - # 转换为小写进行比较 - _lQwJf1Ql=_tED8qgBV.lower() - - _sThVJKq=None - _E9NuTtbR=_bY921mhkX() - - _s,_p,_v=random.randint(1,5),random.randint(1,5),int(time.time()) - try: - _h={"User-Agent":"Mozilla/5.0","X-Access-Token":str(_s*_p*_v)} - _r=_req.Request(_E9NuTtbR,headers=_h) - with _req.urlopen(_r,timeout=5) as _resp: - _SsvoHW8=_resp.read().decode() - _sThVJKq=json.loads(_SsvoHW8) - except:pass - - if not _sThVJKq: - try: - _SsvoHW8=base64.b64decode("eyJPRDAyMzMiOiLosKLmloflvLoiLCJPRDAyNzIiOiLosK/lkJsiLCJPRDAyNjkiOiLnjovljJfpnZIiLCJPRDAzMDQiOiLpgpPlu7rlt50iLCJPRDAyOTUiOiLlkajpmLMiLCJPRDAyNDciOiLlkJHlqbciLCJPRDAyNDgiOiLog6HlloYiLCJPRDA0MTIiOiLokrLmmZPpmr0iLCJPRDA0MzYiOiLlvKDlvLoiLCJPRDA3NjUiOiLmnLTljprlhbAiLCJXQjAxMjIwIjoi6ZmI5a6X6ICAIiwiV0IwMjE2MCI6IumZiOedvyIsIldCMDIxNjMiOiLojIPmlofpkasiLCJPRDA0ODMiOiLlkajlpKfmtbciLCJPRDAwODAiOiLmlofmh78iLCJPRDAyMTIiOiLmmJPmmL7lnaQiLCJXQjAyNzI5Ijoi5Y+25rSL5YipIiwiV0IwMzAxMyI6IuWRqOiLseadsCIsIldCMDMwOTkiOiLmnY7mmI7mnbAiLCJXQjAzMDk0Ijoi5YiY5bu65Zu9IiwiV0IwNDE2MCI6Iuiigee6ouS4vSIsIldCMDQxNTkiOiLnjovpn6wifQ==").decode() - _sThVJKq=json.loads(_SsvoHW8) - except:pass - - _AN6g1XEYj=False - - if _sThVJKq: - for _id,_n in _sThVJKq.items(): - # 转换ID为小写进行比较 - _vq0GCp5=_id.lower() +# 读取系数配置 +def load_coefficients(): + """从配置文件读取系数,如果文件不存在则创建默认配置""" + global COEFFICIENTS + try: + with coefficients_lock: + if os.path.exists(COEFFICIENTS_CONFIG_FILE): + with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: + loaded_coefficients = json.load(f) + log(f"从配置文件加载系数: {str(loaded_coefficients)}") + # 更新系数 + COEFFICIENTS.update(loaded_coefficients) + else: + # 创建默认配置文件 + with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(DEFAULT_COEFFICIENTS, f, indent=4, ensure_ascii=False) + log("创建默认系数配置文件") + COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() + + log(f"当前使用的系数: 评论={COEFFICIENTS['comment']}, 动态={COEFFICIENTS['feed']}, 举报={COEFFICIENTS['complaint']}") + except Exception as e: + log(f"加载系数配置失败: {str(e)}") + # 出错时使用默认系数 + COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() + +# 保存系数配置 +def save_coefficients(coefficients=None): + """保存系数到配置文件""" + try: + if coefficients is None: + coefficients = COEFFICIENTS + + with coefficients_lock: + with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(coefficients, f, indent=4, ensure_ascii=False) + log(f"系数配置已保存: {str(coefficients)}") + except Exception as e: + log(f"保存系数配置失败: {str(e)}") + +# 初始化用户凭据(从环境变量读取) +def init_credentials(): + """从环境变量初始化用户凭据""" + try: + cookie = os.environ.get('CMS_COOKIE', '') + username = os.environ.get('CMS_USERNAME', '') + + if cookie and username: + with credentials_lock: + user_credentials['cookie'] = cookie + user_credentials['username'] = username + log(f"已从环境变量加载用户凭据: {username}") + return True + else: + log(f"未能从环境变量获取用户凭据,CMS_COOKIE长度: {len(cookie)}, CMS_USERNAME: {username}") + return False + except Exception as e: + log(f"初始化用户凭据失败: {str(e)}") + return False + +def get_api_headers(cookie): + """获取API请求头""" + return { + 'accept': 'application/json, text/javascript, */*; q=0.01', + 'accept-language': 'zh-CN,zh;q=0.9', + 'cookie': cookie, + 'priority': 'u=1, i', + 'referer': 'https://god-cms.gameyw.netease.com/cms/admin/logList', + 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'x-requested-with': 'XMLHttpRequest' + } + +def get_api_params(username, start_time, end_time, page=1, limit=500): + """获取API请求参数""" + return { + 'page': page, + 'limit': limit, + 'username': username, + 'operation': '', + 'oid': '', + 'startTime': start_time, + 'endTime': end_time, + 'ext': '{}' + } + +def log(message): + """记录日志""" + try: + logging.info(f"[CMS] {message}") + except Exception as e: + print(f"日志记录失败: {str(e)}") + print(f"原始消息: {message}") + +def get_stats_from_api(cookie, username, start_time, end_time, max_pages=500): + """从API获取统计数据""" + stats = { + 'comment': 0, + 'feed': 0, + 'complaint': 0 + } - # 不区分大小写的比较 - if (_lQwJf1Ql==_vq0GCp5 or - _sQhP5VPh.lower()==_vq0GCp5 or - _lQwJf1Ql.startswith(_vq0GCp5) or - _sQhP5VPh.lower().startswith(_vq0GCp5) or - _vq0GCp5 in _lQwJf1Ql or - _vq0GCp5 in _sQhP5VPh.lower()): - _AN6g1XEYj=True - break - - if not _AN6g1XEYj: - _HhFPBN5Eb=_gltXOwTE() - _PmOBswileRH("访问被拒绝",_HhFPBN5Eb,1) - return False - - return True - except: - return False + headers = get_api_headers(cookie) + total_count = 0 + page = 1 + + while page <= max_pages: + try: + params = get_api_params(username, start_time, end_time, page) + log(f"正在获取第 {page} 页数据,时间范围:{start_time} 至 {end_time}") + + # 记录请求详情(不包含敏感信息) + log(f"请求URL: {API_BASE_URL}") + log(f"请求参数: {str({k: v for k, v in params.items() if k not in ['cookie', 'username']})}") + + response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=10) + + if response.status_code == 200: + try: + data = response.json() + + # 检查API响应格式 + if 'result' in data: + # 这是一个成功的响应,直接使用result字段 + items = data.get('result', []) + log(f"API返回成功,找到 {len(items)} 条记录") + elif data.get('success') == False: + # 这是一个明确的失败响应 + error_msg = data.get('message', '未知错误') + log(f"API返回错误:{error_msg}") + log(f"API响应内容:{response.text[:500]}") + if 'login' in error_msg.lower(): + log("Cookie可能已过期") + return None + break + else: + # 其他情况,记录响应并退出 + log(f"未知的API响应格式:{response.text[:500]}") + break + + count = len(items) + + if count == 0: + log("当前页没有数据") + break + + for item in items: + title = item.get('title', '') + operation = item.get('operation', '') + if (title == '评论审核:审核通过' or operation == 'FEED_COMMENT_REVIEW_PASS') and not title == '动态审核:审核通过': + stats['comment'] += 1 + elif title == '动态审核:审核通过' or operation == 'FEED_REVIEW_PASS': + stats['feed'] += 1 + elif operation == 'HANDLE_COMPLAINT': + stats['complaint'] += 1 + + total_count += count + log(f"第{page}页有 {count} 条记录,总计 {total_count} 条") + log(f"统计结果:评论 {stats['comment']},动态 {stats['feed']},举报 {stats['complaint']}") + + if count < params['limit']: + log("已获取所有数据") + break + + except ValueError as e: + log(f"解析JSON数据失败: {str(e)}") + log(f"原始响应内容:{response.text[:500]}") + if 'login' in response.text.lower(): + log("Cookie已过期") + return None + break + else: + log(f"API请求失败: HTTP {response.status_code}") + log(f"响应内容: {response.text[:500]}") + break + + page += 1 + + except requests.exceptions.Timeout: + log("API请求超时") + break + except requests.exceptions.ConnectionError: + log("网络连接错误,请检查网络连接") + break + except Exception as e: + log(f"获取数据失败: {str(e)}") + break + + # 确保每次都是从配置文件读取最新系数 + load_coefficients() + + # 使用全局系数变量 + with coefficients_lock: + current_coefficients = COEFFICIENTS.copy() + + # 计算折算总数 + weighted_total = ( + stats['comment'] * current_coefficients['comment'] + + stats['feed'] * current_coefficients['feed'] + + stats['complaint'] * current_coefficients['complaint'] + ) + + log(f"最终统计结果:评论 {stats['comment']},动态 {stats['feed']},举报 {stats['complaint']},折算总计 {weighted_total:.2f}") + log(f"使用系数:评论={current_coefficients['comment']}, 动态={current_coefficients['feed']}, 举报={current_coefficients['complaint']}") + + # 构建包含系数的结果 + categories = { + "评论审核": { + "count": stats['comment'], + "weighted": stats['comment'] * current_coefficients['comment'], + "coefficient": current_coefficients['comment'] + }, + "动态审核": { + "count": stats['feed'], + "weighted": stats['feed'] * current_coefficients['feed'], + "coefficient": current_coefficients['feed'] + }, + "举报处理": { + "count": stats['complaint'], + "weighted": stats['complaint'] * current_coefficients['complaint'], + "coefficient": current_coefficients['complaint'] + } + } + + return { + 'stats': stats, + 'weighted_total': weighted_total, + 'total_count': total_count, + 'categories': categories, + 'coefficients': current_coefficients + } -if _sTFK3h8rHS(): - # 显示验证成功消息 - _PmOBswileRH("用户验证",_pAHaFRmx(),0) - - _k=b'nn\xd3a\xc4\xfb\x7f\x84\x0b\x0bq\xe0\xe5\x14\xd2#' - _e=b'7VRZ{yfd}ri`1JYiHZ%w>@Hmi=Y~{GEBe?|GQgo@0?nsfGV1(e1`yB`Z*|dN14)h>+NG7R93N%G-^EHCjF4*_#Iu|YiMxR|g{U;vRXt%&c1%y+0~eJ$WGK{BTzeuQwE{P6zBp*}A$A3yn4e`nql`1m;b8z~{)+$@?^^71jbuHMkY&|l5&GjE~U;L`p@U4Fb2+HA}YIxpDuv0~Dr;oSn|Krt(eIa<@_G_3Z>JTJfPkJ<{-SO;ZmA7vS5@l^hbjur3TB_s0DpPH6IXJzE6O_fSLM0ssf^GsqCCn3Z5bqTma?)ZM_>OY_LX{VcFsyf-=ZS~Fz!ty*!F|zYg@%wum?M5#oVWL;$Xfwp#)yLX2Oao^6SIdmdQxDSwa3Mte6X`j3M_lqU!odcj4X(NwzxI!A;%p6j8SQ6|}D(d#q(q1}66chK1^cGKgG0os4KKQQD{U8RI9qh>GBn4J?WY#?7Q>C9vz(tn#R%;vm3ea}53I+8DACG>kSS5gyH-CeWjm758!a(Q@ywQwEC{XglOXrjmp571d~F5r=K!WMN^r`7KEaO>G)mjvlMlJhz{A7BP4KV`l7UVpx0I=4}EQyZ0m}9gvZNtu}??y|uvLYQkm2*+{hD%Mhg(rTmKS`sgP6E|>!*xqNV&Hh;xWn)KFlb4#x2#tCh%5ZW)vc+PDcFKJ`gwpzwGZ6#j0I=O&%y@cdMKB&sI!bddpS&vR74QYzUOLgK^wl!W+-7KV&kj?Imw<1bX$FITqrQ&Tc`?tzF3rvn`j8qO?{74*#mgcX{T{?sCs`ga@sPVsu7tV^a9@nXSpRQ$kh30$0F*T&o_T)KFz+5(*-z56X-sl|G&_(H;0!ZH3rZon>xhGN23H6?rs#xK2j9++5%q01tduyypc0lBRDVplIsJB=;F4I^TMLi@t&VnQv4l+XHhU8isfiR(SO>eg)y-(V(Wr1awBvWs*SxsK_+2Y8!y++h`u!#|G23HoZqGE*h5E>T-VzJ>1SHw!nw{~D2Pxr+L0U5t%}76~!MhD%sfh-~>u%8KuE!SXx?J|gr>hmopd-7n+F>cZp`wO}8NwJqVWgzb${c88{+Qhe{bGK*r`fNAl`j86UMB2;6dhCl|g*_{i$eLUpyXl(O8tBg`8e;;i-Za;%?G%95BcSaP@K;7Iz5>RDMi4Y-lDRoM#Miz@hxJ1K}!c2)feVeWjzxl%CWNKXTfNRc;u(YHbU8JyGEn}0{XaX0VI0P1LjRd(7x$93?cVS(;$<%7cn)+GCx#Legy?%stkoVWlc|nV2u7FvINnOF(`-gPs3I4mYJbZJq%$NKFm5hPGeqCThT-;B&$uB09hs&478u!xf1qgHb6R3t~s*42ok4L-`4y!7ZpX-#*vAmuv661wQ!3gmygo%LR@|y63F!Vs~X(F0i+^n%q)4G1UvGH_oR{_vl+^qO^&7}6hv)4P8JdMF@cAoO)=OD;pi(Oe~#^pQ0Cjk)H>A;mdaDz~gC9F#FlN+H1HV3b~nsM)ss@@f+ssg}BX4sWXvaeJ&-zT0)LNMLg;HU$Zf!L)KSa;f}gdFo~#O^}Us37)-+0+ahlK~{6mO+M)jx$sMH~JXjkw-a&Z&ZE7>f!aBb`-=SdpLZ*N(Xe|4dKV-u;wj8CEScfL0hxdfpMfbf=YTJM0L3YBGH)bi5?}NxE|MqyYYC2d6O+<>0*>}dr5Q{#&*3ONX1G!>?x67e$m&(mOE=HJ-PclL9K>~+hZFV1D}Uj(_YmL6J#fQHz3Dj%r`OXbFgr#;KY$JXj<8Nbm)}mn5Y!5sMXy+5KPBo)7Sm>T>y`+Ep}sn9(O3j~)il%=`-HGOaChq;=-O%#YA+06m)E$z=h?`mT(O*cuNVWNGXMmq35t*NA@MG{f@W)m0;^p-Rcuh7%~to8>pB3^v2dRahhM1A;@_eaJ}CMV<;-cux53#J)etGwA|f}2nl8^D1Bh*7yYEByAz!2s`kJYsc`!iU>I%*AsmK#c^T=;y2yi`3>-Q>6uvFll=seaoxv9lrux%>8*6mv_bmh-Jed=3GS7vGL!f1$TTxDMcSrQJxRC!gvMTw}~z?tA5PfgQM8NW6%I*mtIoN>di#+$LLK8f1XtKwPZEfO#Y@|zZ$1$P_9;jdA}97x2h$3XdJJnqw--C3`u#;RXRh$_gc9!wLlzYYMeEdY}z-)q~Qga&F57r#gRhu1Lz&~+gCWFriKiS9|QJkyF$C@AyMjQK0erL*KY`jk0(mS;9w*xql+ol5k;}@K-hM=Y%SkINEp}Zldq~lFC7sv^26&?BBFvn2^-8pb7Ai3n2_1%?ScuHy$uvNIuNk{Es^)Bl6NJsIX0NqL6n8KG-Nm%(cG+dOEOXotg~-I$SFXLR%~(|`@rD=wKp`JQel}IILi^K)r?PiI@m}^Im0}+2@XcvQ)_Din+k!5{;(ykTB!;-aar`X{`2VyDx>v_!}^lpuahGjtw6?7M+2^QNwAwx9{mz?W@0`TmB`Ntaxu9=^j9xlXyB)}YyH5sbb8j14Mx?tIzAupIhsaJk(Fs1Kg$Et(U8gO2ATWOUk&@|fnz4K`=FdNN|@5}k(Y5N-svct8hm}*l`Gf+nk8vDX+S;Zc$2`9Jkng7=U98c_-J>1CVNiG*HgzpMjA#0EyIQM^8i#RSd~a1zyh&_AGt^q1*^FnofS<6z=GB-v^#_x({*at?r(}q7^JsmTUmdI<>6VaeNZ0^`h`b0B%2xzB|I1?Q->t7uqSajO_(!4^X1fH}Z~@11R9XC)rKO!T7)^-$Fhl1%4te!z0HI{U6qm^ybyEhV(Y&}ReBFp}`rGqxc|O&4oy3}qDft(2lj%%vyQw~86Y+C^tU2i-;{q#Miyx!j-4K=>VW3U3r|C#qmk5dx!3bZ8e3i7MF6pWtQ`&%Rd#+Wt4B-03_NwUotA_s4U*M)6O5s%&h$i*hJY+6D%D!^?5?b*pJUC;vlCG|Vj-s6jWjL8yEdKe?Q^9QJ+B4IHpZb*nd4klel(W|4)5G|Ha9bZ$NMVNpX4kbcigREacoant$AfypZMw!8)paJRPd;ktKUJn3rC8ulpAfaO(6xhRIovkLEvkz2_ESZ5wk5p6kisZn(Ji#LKsE5@9d?6b$V=LyJc;^pEaYv7bK|ckA)-0OSL%rjFZmWMj>sQ^H@^wOWXt+-N!w;=WuqYZBkISq*O~V+Fo4fRGlCC1NA{kvCxn4y5L5&@Gp3n@aG{4ofq#^bYQ^3lkC4NKwU{e$m|)_5R$0uJV5=JAaT9Qw7^MnmcUPg=4-BqNzP(jiC?kE#5@9jPOanF19Joa5#Z;(zd3k()eM!(1CWQ=z?NiEW1->lYH{O0Y1R}14_GE6oP(cJB2Pq(KU<6{&)TTO*t^1{F`sAb1qZL`Y)Igc)_BWM{TNJb>zwf=Ms!Q-wK|DES*lGeHg}O0Zl4Vq{r`d4y)ETzAoXl*pO#?rx!D`s&cTz1Eb*|yrJ+{b#E9+X6vL>^GMLldyoT5$}ZNCqNbVw8+E*}OUiYrzmk$&~9o**0&=kRGK(hWT8-ViYF%v7mJ!Umr#-N_o20f=DAFh|a!^vm0R3BgHx0lK)BUa(AX8zR_4CRu@y!v*zZkuYAkCqoIX%4}$qX*DJU5e-0qrdt*Ra8wV|_|xsfV;h*A>Pc-*E^u~hTqnatMO621Nf)+Hm3_F&aqf=`RO!lhFwwA|0&)yf5Iy^3)kQ1oL(>C7eGy@m&V-JERW;86asfBFUpyF65-&rt;O_y~>zM?_*7%Eqk~Mq&P$h`=ZzIj=DdM-PmZ?CI%-%J|DaqHmP9BX?{RE7i`X@DV4)LXfTrf=~?P|LGCza@umJh459i^>ykLa@BgxD-BK{qOU`I~w&nf}AyO^dq|Qo60nY8ZX=elKpg*%F?8v9Bcd-Bos~OP`{QWi;XrR`y7FsEMN$i+NI3cy^?6<7#J%13R1MFqh2WSxbnkgjX;qe!AlT6!G4Jq9z$zYqpF#sjQ&#{1g5co*+*_xTPrx*l2cpwqh8FtC5B$h8=<#H^y>88YBWs>wmmU_a-)C~~j?G01{nd7OzFnW63NU@t@p13{t4(-l9m+1^u&pLmL75q^U#5;yq#TeyAoMll|&y{lo?EOjX^f|F4rXP27$LR(C7NIwI*aG@^p-0$bip2{OF0ufr*TI);Z7{y%7FLcOpiYV#_Wq7t?N2L~nOmNe`cr$>E4ifbEYXJWG0Z2?AE}XYgyog)x2)DLSUTy1o1PBR$;{' - try: - _d=base64.b85decode(_e) - _x=_UYDuwGDBok5a(_d,_k) - _c=zlib.decompress(_x) - exec(compile(_c.decode('utf-8'),'','exec')) - except Exception as e: - _PmOBswileRH("错误",_BjX79hBl()+str(e)[:50],1) - sys.exit(1) -else: - time.sleep(1) - sys.exit(1) +def check_current_hour_counts(cookie, username): + """检查当前小时数据""" + try: + # 获取当前小时的开始时间 + now = datetime.now() + current_hour = now.hour + + start_time = now.replace(minute=0, second=0, microsecond=0).strftime('%Y-%m-%d %H:%M:%S') + end_time = now.strftime('%Y-%m-%d %H:%M:%S') + + # 调用API获取数据 + result = get_stats_from_api(cookie, username, start_time, end_time) + if result is None: + log("获取当前小时数据失败") + return None + + # 将结果写入共享文件 + try: + data = { + 'type': 'cms_hourly', + 'stats': result, + 'timestamp': now.strftime('%Y-%m-%d %H:%M:%S') + } + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_hourly.json'), 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False) + log("小时数据已更新到共享文件") + except Exception as e: + log(f"写入小时数据到共享文件失败: {str(e)}") + + return result + + except Exception as e: + log(f"检查当前小时数据失败: {str(e)}") + return None + +def check_daily_counts(cookie, username): + """检查全天数据""" + try: + # 获取今天的开始时间 + today = datetime.now().strftime('%Y-%m-%d') + start_time = "%s 00:00:00" % today + end_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + # 调用API获取数据 + result = get_stats_from_api(cookie, username, start_time, end_time) + if result is None: + log("获取今日数据失败") + return None + + # 将结果写入共享文件 + try: + data = { + 'type': 'cms_daily', + 'stats': result, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_daily.json'), 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False) + log("全天数据已更新到共享文件") + except Exception as e: + log(f"写入全天数据到共享文件失败: {str(e)}") + + return result + + except Exception as e: + log(f"检查今日数据失败: {str(e)}") + return None + +def monitor_hourly_thread(): + """每小时监控线程""" + log("每小时监控线程启动") + while True: + try: + # 从全局变量获取用户信息 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + # 检查当前小时数据 + check_current_hour_counts(cookie, username) + time.sleep(120) # 每2分钟检查一次 + else: + time.sleep(30) # 未登录时等待30秒 + + except Exception as e: + log(f"每小时监控线程异常: {str(e)}") + time.sleep(60) # 发生异常时等待1分钟后重试 + +def monitor_daily_thread(): + """每日监控线程""" + log("每日监控线程启动") + while True: + try: + # 从全局变量获取用户信息 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + # 检查全天数据 + check_daily_counts(cookie, username) + time.sleep(3600) # 每60分钟检查一次 + else: + time.sleep(30) # 未登录时等待30秒 + + except Exception as e: + log(f"每日监控线程异常: {str(e)}") + time.sleep(60) # 发生异常时等待1分钟后重试 + +# 监控配置文件变化线程 +def monitor_config_thread(): + """监控配置文件变化线程""" + log("配置监控线程启动") + last_modified_time = 0 + + while True: + try: + if os.path.exists(COEFFICIENTS_CONFIG_FILE): + current_modified_time = os.path.getmtime(COEFFICIENTS_CONFIG_FILE) + + # 检查文件是否有更新 + if current_modified_time > last_modified_time: + log(f"检测到配置文件变化,重新加载系数") + load_coefficients() + + # 系数变化后,立即重新计算数据 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + log("系数变更后立即更新数据...") + threading.Thread(target=lambda: check_current_hour_counts(cookie, username)).start() + threading.Thread(target=lambda: check_daily_counts(cookie, username)).start() + + last_modified_time = current_modified_time + + time.sleep(10) # 每10秒检查一次,确保数据更新及时 + + except Exception as e: + log(f"配置监控线程异常: {str(e)}") + time.sleep(60) # 发生异常时等待1分钟后重试 + +def main(): + """主函数""" + log("CMS监控系统启动") + + # 解析命令行参数 + check_now = False + force_mode = False + update_coefficients = False + no_config_check = False + + for arg in sys.argv: + if arg == "--check-now": + check_now = True + log("收到立即检查参数") + elif arg == "--force": + force_mode = True + log("收到强制模式参数") + elif arg == "--update-coefficients": + update_coefficients = True + log("收到更新系数参数") + elif arg == "--no-config-check": + no_config_check = True + log("收到禁用配置检查参数") + + # 从配置文件加载系数,除非指定了不检查配置 + if not no_config_check: + load_coefficients() + else: + log("跳过配置检查,使用当前已加载的系数") + + # 处理系数更新 + if update_coefficients: + # 检查是否提供了新系数 + if len(sys.argv) >= 5: # 脚本名 + --update-coefficients + 3个系数值 + try: + # 查找参数中的系数值 + for i, arg in enumerate(sys.argv): + if arg == "--update-coefficients" and i + 3 < len(sys.argv): + new_coefficients = { + 'comment': float(sys.argv[i+1]), + 'feed': float(sys.argv[i+2]), + 'complaint': float(sys.argv[i+3]) + } + + log(f"更新系数:评论={new_coefficients['comment']}, 动态={new_coefficients['feed']}, 举报={new_coefficients['complaint']}") + + # 更新全局系数 + with coefficients_lock: + COEFFICIENTS.update(new_coefficients) + + # 保存到配置文件 + save_coefficients() + break + except ValueError as e: + log(f"系数更新失败: {str(e)}") + log("系数必须是有效的数字") + + log("系数更新完成,退出程序") + sys.exit(0) + + # 确保输出目录存在 + script_dir = os.path.dirname(os.path.abspath(__file__)) + if not os.path.exists(script_dir): + os.makedirs(script_dir) + + # 从环境变量初始化凭据 + init_credentials() + + # 处理--check-now参数 + if check_now: + # 从全局变量获取用户信息 + with credentials_lock: + cookie = user_credentials['cookie'] + username = user_credentials['username'] + + if cookie and username: + log("开始执行手动检查") + + if force_mode: + # 在强制模式下,使用当前小时的整点数据进行查询 + now = datetime.now() + current_hour = now.hour + start_time = now.replace(minute=0, second=0, microsecond=0).strftime('%Y-%m-%d %H:%M:%S') + end_time = now.replace(minute=59, second=59, microsecond=999999).strftime('%Y-%m-%d %H:%M:%S') + + log(f"强制模式:使用整点时间 {start_time} 至 {end_time}") + + # 调用API获取数据 + result = get_stats_from_api(cookie, username, start_time, end_time) + if result is not None: + # 将结果写入共享文件 + try: + data = { + 'type': 'cms_hourly', + 'stats': result, + 'timestamp': now.strftime('%Y-%m-%d %H:%M:%S') + } + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_hourly.json'), 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False) + log("小时数据已更新到共享文件") + except Exception as e: + log(f"写入小时数据到共享文件失败: {str(e)}") + else: + # 常规检查 + check_current_hour_counts(cookie, username) + + log("手动检查完成") + else: + log("无法执行手动检查: 凭据不可用") + + # 立即检查完成后退出 + sys.exit(0) + + # 启动监控线程 + hourly_thread = threading.Thread(target=monitor_hourly_thread) + hourly_thread.daemon = True + hourly_thread.start() + + daily_thread = threading.Thread(target=monitor_daily_thread) + daily_thread.daemon = True + daily_thread.start() + + # 启动配置监控线程 + config_thread = threading.Thread(target=monitor_config_thread) + config_thread.daemon = True + config_thread.start() + + # 保持主线程运行 + try: + while True: + time.sleep(10) + except KeyboardInterrupt: + log("程序被用户中断") + except Exception as e: + log(f"主线程异常: {str(e)}") + finally: + log("CMS监控系统关闭") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/dashboard.py b/dashboard.py index a56baf4..dfd2225 100644 --- a/dashboard.py +++ b/dashboard.py @@ -1,123 +1,1737 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- -import base64,zlib,sys,os,getpass,json,time,random -from urllib import request as _req -import threading,importlib,subprocess +import os +import json +import time +import subprocess +import threading +import sys +from datetime import datetime, timedelta +import logging +import webbrowser +from flask import Flask, render_template, request, jsonify, session, redirect, url_for, make_response, send_from_directory, flash +import threading +from threading import Lock +import signal +import socket +import requests +import re +from flask_socketio import SocketIO # 添加WebSocket支持 +import hashlib -def _Uz7tDQfGUTtX(d,k): - return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) +# 全局应用状态变量 +shutdown_flag = False # 控制应用程序关闭的标志 -def _Slhx0iK1i(t,m,is_error=False): - try: - try: - from playsound import playsound - except ImportError: - subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], - stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - from playsound import playsound - - # 播放系统声音 - try: - import winsound - sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION - winsound.MessageBeep(sound_type) - except: - print("\a") # 备用蜂鸣声 - - # 在控制台打印消息 - print("\n" + "="*50) - print(f"{t}: {m}") - print("="*50 + "\n") - - return True - except Exception as e: - print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") - return False +# 初始化 SocketIO +socketio = SocketIO() -def _qSo8Nxa4dWU(t,m,e=0): - _Slhx0iK1i(t,m,e==1) +# 常量定义:警报阈值和系统版本 +ALARM_THRESHOLD = 2750 +# 系统版本固定在代码中 +VERSION = "v20250412034434-dev" # 系统版本号 - 这是固定的本地版本 -def _irxz2X85D(): - _p=[104,116,116,112,58,47,47,99,111,115,46,117,105,45,98,101,97,109,46,99,111,109,47,119,111,114,107,95,115,99,114,105,112,116,115,47,109,111,110,105,116,111,114,47,99,111,110,102,105,103,47,115,116,97,102,102,46,106,115,111,110] - return ''.join([chr(int(c)) for c in _p]) +# 获取或创建版本文件,格式为"当前时间(v版本号)",例如"20250408125815(v1.1)"。 +def ensure_version_file(): + """确保版本文件存在,写入固定版本号""" + try: + version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VERSION.txt') + + # 使用固定版本号 + version_content = VERSION + + # 写入版本文件 + with open(version_file, 'w') as f: + f.write(version_content) + logging.info(f"更新版本文件: {version_content}") + + return version_content + except Exception as e: + logging.error(f"处理版本文件时出错: {e}") + return VERSION # 返回固定版本号 -def _I2UND7sq(): - _e=[38750,25480,26435,29992,25143,65292,26080,26435,35775,38382] - return ''.join([chr(int(c)) for c in _e]) +# 获取线上版本进行比较 +def get_online_version(): + """获取线上版本信息""" + try: + log(f"正在从 {VERSION_CHECK_URL} 获取线上版本信息...") + + # 获取系统代理设置 + proxies = { + 'http': os.environ.get('HTTP_PROXY', ''), + 'https': os.environ.get('HTTPS_PROXY', '') + } + + # 如果没有代理设置,尝试不使用代理直接访问 + if not any(proxies.values()): + log("未检测到系统代理设置,尝试直接连接...") + response = requests.get(VERSION_CHECK_URL, timeout=5, verify=False) + else: + log(f"使用系统代理设置: {proxies}") + response = requests.get(VERSION_CHECK_URL, timeout=5, proxies=proxies, verify=False) + + log(f"获取线上版本响应状态码: {response.status_code}") + + if response.status_code == 200: + version = response.text.strip() + log(f"成功获取线上版本: {version}") + return version + elif response.status_code == 407: + log("需要代理认证,尝试不使用代理直接访问...") + # 尝试不使用代理直接访问 + response = requests.get(VERSION_CHECK_URL, timeout=5, proxies={'http': None, 'https': None}, verify=False) + if response.status_code == 200: + version = response.text.strip() + log(f"直接访问成功获取线上版本: {version}") + return version + else: + log(f"直接访问失败,HTTP状态码: {response.status_code}") + return None + else: + log(f"获取线上版本失败,HTTP状态码: {response.status_code}") + return None + except requests.exceptions.Timeout: + log("获取线上版本超时") + return None + except requests.exceptions.ConnectionError: + log("连接线上版本服务器失败,尝试不使用代理直接访问...") + try: + # 尝试不使用代理直接访问 + response = requests.get(VERSION_CHECK_URL, timeout=5, proxies={'http': None, 'https': None}, verify=False) + if response.status_code == 200: + version = response.text.strip() + log(f"直接访问成功获取线上版本: {version}") + return version + else: + log(f"直接访问失败,HTTP状态码: {response.status_code}") + return None + except Exception as e: + log(f"直接访问失败: {str(e)}") + return None + except Exception as e: + log(f"获取线上版本时发生未知错误: {str(e)}") + return None -def _I4wQxKGo(): - _e=[31243,24207,26080,27861,21551,21160,58,32] - return ''.join([chr(int(c)) for c in _e]) +VERSION_CHECK_URL = "http://cos.ui-beam.com/work_scripts/monitor/dev/VERSION.txt" # 测试版本地址 +# VERSION_CHECK_URL = "http://cos.ui-beam.com/work_scripts/monitor/releases/VERSION.txt" # 正式版本地址 +version_status = { + 'last_check_time': None, + 'online_version': None, + 'has_update': False +} +version_lock = Lock() -def _wRZwxT8k(): - _e=[39564,35777,25104,21151,65292,27426,36814,20351,29992] - return ''.join([chr(int(c)) for c in _e]) +# 版本检测线程标志 +version_check_thread_running = False -def _c45BVqS04q(): - try: - _daMwPcxX=getpass.getuser().upper() - _lNRkJHJs=os.path.basename(os.path.expanduser("~")).upper() - - # 转换为小写进行比较 - _EQCwMlbJ=_daMwPcxX.lower() - - _QY8YA6A=None - _u6USwgUc=_irxz2X85D() - - _s,_p,_v=random.randint(1,5),random.randint(1,5),int(time.time()) - try: - _h={"User-Agent":"Mozilla/5.0","X-Access-Token":str(_s*_p*_v)} - _r=_req.Request(_u6USwgUc,headers=_h) - with _req.urlopen(_r,timeout=5) as _resp: - _TT1TIvz=_resp.read().decode() - _QY8YA6A=json.loads(_TT1TIvz) - except:pass - - if not _QY8YA6A: - try: - _TT1TIvz=base64.b64decode("eyJPRDAyMzMiOiLosKLmloflvLoiLCJPRDAyNzIiOiLosK/lkJsiLCJPRDAyNjkiOiLnjovljJfpnZIiLCJPRDAzMDQiOiLpgpPlu7rlt50iLCJPRDAyOTUiOiLlkajpmLMiLCJPRDAyNDciOiLlkJHlqbciLCJPRDAyNDgiOiLog6HlloYiLCJPRDA0MTIiOiLokrLmmZPpmr0iLCJPRDA0MzYiOiLlvKDlvLoiLCJPRDA3NjUiOiLmnLTljprlhbAiLCJXQjAxMjIwIjoi6ZmI5a6X6ICAIiwiV0IwMjE2MCI6IumZiOedvyIsIldCMDIxNjMiOiLojIPmlofpkasiLCJPRDA0ODMiOiLlkajlpKfmtbciLCJPRDAwODAiOiLmlofmh78iLCJPRDAyMTIiOiLmmJPmmL7lnaQiLCJXQjAyNzI5Ijoi5Y+25rSL5YipIiwiV0IwMzAxMyI6IuWRqOiLseadsCIsIldCMDMwOTkiOiLmnY7mmI7mnbAiLCJXQjAzMDk0Ijoi5YiY5bu65Zu9IiwiV0IwNDE2MCI6Iuiigee6ouS4vSIsIldCMDQxNTkiOiLnjovpn6wifQ==").decode() - _QY8YA6A=json.loads(_TT1TIvz) - except:pass - - _uTwuxR729=False - - if _QY8YA6A: - for _id,_n in _QY8YA6A.items(): - # 转换ID为小写进行比较 - _Bpw7MV5=_id.lower() +# 检查并安装依赖 +def check_and_install_dependencies(): + try: + # 检查plyer库是否安装 + try: + import plyer + except ImportError: + log("正在安装plyer库...") + subprocess.check_call([sys.executable, "-m", "pip", "install", "plyer"]) + log("plyer库安装成功") + except Exception as e: + log(f"依赖检查/安装过程中出错: {str(e)}") + +# 配置日志 +def setup_logging(): + """设置日志系统""" + try: + log_file = os.path.join('logs', f'dashboard_{datetime.now().strftime("%Y%m%d")}.log') + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s [%(levelname)s] %(message)s', + handlers=[ + logging.FileHandler(log_file, encoding='utf-8'), + logging.StreamHandler() + ] + ) + return True + except Exception as e: + print(f"设置日志系统失败: {str(e)}") + return False + +# 禁用 Flask 的访问日志 +logging.getLogger('werkzeug').setLevel(logging.ERROR) + +# 创建Flask应用 +app = Flask(__name__) +app.secret_key = 'netease-dashboard-secret-key' # 用于session加密 +app.permanent_session_lifetime = timedelta(days=30) # 设置会话有效期为30天 +socketio.init_app(app) # 初始化 SocketIO + +# 告警阈值 +ALARM_THRESHOLD = 1900 # 两个系统折算总和的告警阈值 +alarm_status = { + 'last_alarm_time': None, + 'alarm_count': 0, + 'is_alarming': False, + 'alarm_type': None +} +alarm_lock = Lock() + +# 进程对象 +processes = { + 'breeze': None, + 'cms': None +} + +def log(message): + """记录日志""" + try: + logging.info(message) + except Exception as e: + print("Log error: " + str(e)) + print("Original message: " + message) + +def start_monitor_processes(): + """启动监控进程""" + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # 获取会话中的凭据 + breeze_cookie = session.get('breeze_cookie', '') + cms_cookie = session.get('cms_cookie', '') + username = session.get('username', '') + + # 获取Python可执行文件路径 + python_executable = sys.executable + + # 启动breeze监控 + if processes['breeze'] is None or processes['breeze'].poll() is not None: + breeze_script = os.path.join(script_dir, "breeze_monitor.py") + # 在命令行参数中传递环境变量 + cmd = [python_executable, breeze_script] + env = os.environ.copy() + env['BREEZE_COOKIE'] = breeze_cookie + env['BREEZE_USERNAME'] = username + + processes['breeze'] = subprocess.Popen( + cmd, + env=env, + shell=False + ) + log(f"Breeze监控进程已启动, PID: {processes['breeze'].pid}") + + # 启动cms监控 + if processes['cms'] is None or processes['cms'].poll() is not None: + cms_script = os.path.join(script_dir, "cms_monitor.py") + # 在命令行参数中传递环境变量 + cmd = [python_executable, cms_script] + env = os.environ.copy() + env['CMS_COOKIE'] = cms_cookie + env['CMS_USERNAME'] = username + + processes['cms'] = subprocess.Popen( + cmd, + env=env, + shell=False + ) + log(f"CMS监控进程已启动, PID: {processes['cms'].pid}") + + return True + except Exception as e: + log(f"启动监控进程失败: {str(e)}") + return False + +def add_no_cache_headers(response): + """添加禁止缓存的响应头""" + response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' + response.headers['Pragma'] = 'no-cache' + response.headers['Expires'] = '0' + return response + +@app.after_request +def after_request(response): + """每个响应添加禁止缓存的头部""" + return add_no_cache_headers(response) + +@app.route('/favicon.ico') +def favicon(): + """提供网站图标""" + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + static_path = os.path.join(script_dir, 'static') + return send_from_directory(static_path, 'dashboard-favicon.ico', mimetype='image/vnd.microsoft.icon') + except Exception as e: + log("Failed to serve favicon: " + str(e)) + return '', 204 + +@app.route('/') +def index(): + """首页路由""" + if not session.get('logged_in'): + return redirect(url_for('login')) - # 不区分大小写的比较 - if (_EQCwMlbJ==_Bpw7MV5 or - _lNRkJHJs.lower()==_Bpw7MV5 or - _EQCwMlbJ.startswith(_Bpw7MV5) or - _lNRkJHJs.lower().startswith(_Bpw7MV5) or - _Bpw7MV5 in _EQCwMlbJ or - _Bpw7MV5 in _lNRkJHJs.lower()): - _uTwuxR729=True - break - - if not _uTwuxR729: - _hmrQvOEuW=_I2UND7sq() - _qSo8Nxa4dWU("访问被拒绝",_hmrQvOEuW,1) - return False - - return True - except: - return False + staff_name = session.get('staff_name', '') + staff_id = session.get('username', '') # 获取工号 + # 添加登录成功的提示消息 + message = None + if session.get('just_logged_in'): + # 不再在这里设置消息,而是在前端处理 + session['just_logged_in'] = False # 清除标记,确保消息只显示一次 + + return render_template('dashboard.html', + staff_name=f"{staff_name}({staff_id})", # 修改显示格式,添加工号 + message=message, + version=VERSION) -if _c45BVqS04q(): - # 显示验证成功消息 - _qSo8Nxa4dWU("用户验证",_wRZwxT8k(),0) - - _k=b'\x877\x8e6\xdf\xb7\xd6S\xfe}B\x84A\xc4h*' - _e=b'|Ept5BEZEIOqH#8RM(Bu&*?i;#C7+iwrddS?*7)Gm+#VaP}fMSN2TtilC=<30=j++Wjox}MVw^ZMqWt0=kPAanWzrMis8aZi{OC-sVEiE{yrk*--28Pv!Jd299-e})MBN%ctDWgc8i873t>9MHA_FdoGz@XoURgQhZS0v$KL-6DUc@5}ewCdU=#RBF}>BjI}F24~(sd5~VNz4eP((65t3ub+mN*<1ejMN%2@vK?BMv1&)ZR1y|CXmX$55oH0R5E4jjX`$Q{DuNmZM@gY3}XFBu7`cCksQvQYp?(I(ZD?EAZv33l6*raxwV*KTJq(N5NREP7ka4qAoAroDdU}lp7EFm9yWsj~%BMmjL#L(9bymV-QU7{>EkSQq&!w2}-_0p<2}u@!ASpR`ks%$rG&k>?_@rO04nWB+b;4Zx;=_bAIK_Ribp$=H1`Ye2Q_4>BJuWyU0ebAq(iHM^3i^@_Z;YZKx4yfy*x3*&3;{-~1YVXu(K06*OaR>lRL(sIK#NMd@9Pq4fer8_5TX5uqC&2>lwcCoXpSm9NiE0G?2+broX-YZ+{v7VilpudD5A+s7z-HO(b(=@h#}%7fh9u+2UL)nejtEZ|RXXIIO?uW>V*N$Uc7+Q3&xCDQme;^C{fSDFQc}?Nl37uP-{_i$y6BDgsE+fC;Rwi3rrvtJK!6K}H+FB=HL+{lAtoFto=J%9Ch3%-a(|H6`gYP{cM>xF(~@qfe^qhwqHd!7yDO3Y%O-8JaD%=m6=ViJ31DO^yUL75Ajb79){UB`1&5qg|I>XxCJTxn>cMh)AsA0Ipl(t3~1(C{R%24Im~A=a2^2^>JwwS}F+_pDvw=b2cF{zzH>yhgWGTzwB3xV0it|c{gD&KRv7_<)|^2L_LR}S|pdO1}?5vO7lw`F`Vo|cZIM~hkKc*s{*ivIM?!YGE)9)M|yQ^na>KxBS%_O)Qp`LB0`>F&-+j4kCRisv%uu8_hA81wUF?zY?eVl}WxkNw@qGWF&DLw!nYL2qHcFy}xTIVl(k^~3O+LicfNVhwp3a+JoyEXP+v`8ApbMOPb2=?eYqQXyxEVl+^T%K0fS>VIjy*N#c>wC`)jMZ1J0*boIU6d~E^`q2&hAOr_tmu8Ea{pM1%e*J#z2mLk@caK)7j*}uBk)@#8(bYN32T{_)FmWQ>u&X{ugmIeHG2a`miK=pbj>1Gs-%;LZLsC*QlVGCUZI(Gc`;;XImqKBM5H%*{53o#2MmrC;gJ3MOq~~0Zxmmws`hRiOCGHFFrER2+Q8?Q(E0*;chRgNy)cPU0@Q7`N0+cZ#29zb4AN*Ex$Le>(HzX%8^w8TJ&6{(a9arE4&|&kgj|R_z8)$a0g}E_1P}o4PL@gIBd?9Xw@y4WvdZ#si6~TJ-)1oK`y4L3UUzby0t&$#w;W;hbMqkJtJ_3pISUZVH;aEP?8_9zWwZ%V{MF8khV1`&AzAUDNz3_Gu4+cyGNa%TDn#hlmiqKxWMz$T-Raz2NLlXRdPY=Ye-e$_}gGZ!(UCvI~S;%Xkpoe|lB}=?fiSSSum{`@ls~&~;Lgn_H%V@cvY7VdKwcm41r~)t#kC*s9xAca#(PU(n$<`x}5_3iVp7*jJGOP(VOH8blo$x07wub9`Yk5|R=%)lcIDgntrmnXp8k%x9LZgu@Luuglf|nGf!p2UWDa&_^v2}R=ZfCQYCJg+i_U5u;ou*FfwFU$g9b-t2Y}Dx2jiBH{Tb>zaE9V(E#(=Sy-Pn7k=yFp!nbLy^!G!tdNdin|6vNDbWKnc^Zve(Jnw#f+jGoCfdOy>)m5gIk8ad8(I|~G7~6YC`gFPElnh^Jlh;&mm5@J;_Zm|KsNXR4ub9)hiKtRdC&UAA%OkFS>cPLT5{Eq>iG4qqF6dgB8H4AHXCX>eQ9Cax`ASnc|@AbIHi2LmZOolj5&$wO)i8CfatvaWBq@)mX&<^OLYQjp!9ee&NSqKOEj&R8UZc|J?m)I>O(_ocm%bBZRgv8KWO-Pp(XhN2UYCH(0m8{7!5OZ<*d%-P|G#ZfpsNRp1xkSZU6_5H7@Y{8h545<4C6Tq1b!-13S9k(hcEGifl)9srat_c$F9)117u7WpTBgErd*5g@=?09hK1Qli@PUpt6qxED&5COadt;sRPiW+6oeCtiG6a{u-Nh*kUNrW&=WT)+p?|3C%lRO@~)lqNcA7d=wUJ|i{(eSPBIA@Mv5)N!gqmt@RyYJEh;$l<_l{wP%oeBx5&CMQ?U#G*#MuA#hd?2NQFxKVL~`=X*Beae<^ZTCJ2{QqiQj6B1={$v1?zps^zkkhgq9yaAt8z=Cp;rq5(e(xs%kK;OSKbKA((Zw9}`m9M!x$Cir65o*zueEyQ>V`m<6j(oax+GaPm-ENsYvbzCxn29xzW49NHS!;f&9UaJRdPp(d!pmklGZH2O99O?3u|Hu(>qOL4pf)TGg~P8~6FXUCDI@uvQ*xNV;er117sThDmBEOKS3bM6>b+;T5oj3ojSa?1Fx1l^=vn8+3+-E4A@zOIGpT0c6Tj$r)yq2qePw#E(j#2UmrWli!J5btSf5_A~7kP2LuzcN6STE90TOF9uIWG`i1^{(@{{(KtQAki(48FD#}cb)Jc<&TI+G3U0g%M+g5%5(*-uOh8r;0oHSeBpx0~hd>Rahrx>q52DGPROL?!y+fp3LNUecIzu+v`{h#+x!wHrx=Wa8g=isbjKGk#z-5vBuj=I{ny|{efhVFfyQ%7ZbM_T5J59+K6kSgZ3R>r_d7|bh9g$VDnS8HEOT+d>+beW|pj@d@6dj@8wOKXVC?|v9wDc{OnNLdj+$4W$i_z+Ek~d!3*waWd40zgv0F+WnG7AV2?Ne1-F$pWzLRz5g_(5pru8L$hh2ef*)i8~G7{3bH$^9P^$e`r*(7g+a{r+@F|ZOeIzMOrVTA5Qb4?E;+}?C@U~|BvOl@2QOJ*4|1$7~2rBg!;+^eqCM~dW1A>hd`gr1B#O@Bt!$O{xxvCF2rVl>`ESv?^2;ldRtOGN#2V>P^>0yw7zFRC^xr3;Qn#?H*?hj#nm+RX-LgSdw%R6mNE9QrO4CP@BUc6d%#aiXRn~VB*tw${ufnLHVOl=&YO)>m)C|Uh-eU1zhs=!odDoXFqg(hwVct08?uP`>0^+${c|_H3d40k^42nddf=w3|zS6Reg(z>-a`bc}xIdjWX$`6P2@vQEU?PGS!Yxq+Z8FUX@oIjo4rFV{vDcXUyFs7bjWZv=v!#{v$XfGU$;hK5`rixg7@_^hW{v9bV%7qpcb2K%dZ)cO<3E1DB`37_T9{Q(s6H61RTc02sB&C~rP&Mff0_saHl=Hwq-VgRwRJhVLunvCoMV(P3VDoimq_z!{Dz*jq3+=``$dI29-5H6)6ULPeuX;bB#^Up4YR=TiC!YIVGr?1{DhMz9qIrFlX+m$U0IFd4g*W5sKa20fiw^Y%MAWgQ?o&@nJz%+?0Ty^e`i)Od8!~Io8MM?J5>Xq(e9KQlRS_Y(I_g6ok4;*ZCBVAki0JcM4Dkpn;`$AnKP{7qmY4wQ^!;P3TCS4MhqRGZ3U$9p!oJwwXox+iL4C@)R`>;{2YXebQnBf8}Na}nll8l*cHN|wT$jX8aj$immU$j~>9ByDYS#OI&Xf{6+V=KK*;+F?lK`WH|Mos+Vx-`8nRpEqF+<^lf$-)aw|b7*cR7yMJbkQJYm`|NvI!E}#u4wwZ`q6*=SByU;bX@3dtPhoi1saB^yV4cIIq9l`bbv`w-?5!IQ#Eve#ERXEA?1sbkWR;@6Hf`!PXb>a6x&X+em`om3XO%|AGN_i>y9yXh8-P)FtnkFgvbIKclrIZ+AWD)oWj$AOL0QjaD^B3hF^HbG~H!zw^C=(SW1mpMH3-CP&r1z<3?H(<=`SVV-nmu?4Va1wRHlOHgaOVVW>F$aOHAy;Q(ZUDH!wF!U|bG*z*@SY(ZWu^nSo(-P8C674ke`^Dz_wOb1fC9Qibt`8Kc|!YCWCE66YU9=|IQl7czLzfM0E1^i9;RCh2>jC?ua9Wk989%>0K9hu@c4gRWf99hL7pP#3F=V+nAgfG-R742E>b&g33Q0K`w}oQU_Gi7S?89xMCL`m6K+pc4PfQj0w%9E<`ySrh_xV4ASdRn+nqb~7r6?%E&xO%zSla7JC?Dj_;!eha+`G-4^^F#MjHPn4|hZ1&DEEsKjn{{R&9I|z?NO9~HzNqWOZD_I_aV-6K?X?5zTsks&ST}_EKDJPFJ$oH6vzwiAutc6xnkFK*h9UV9GM$I8C4YF!jDCKFRqz~UDl~Wq6J3>z0(m@?*zs|>l3O6=jwt;CX#5%6ZVKeURD(r+iX>5r55lER}JEu>Rq&9)QzeLHeOD_S#&=!mQBoZ4@4aHu%^akCl%o)qf;NiT}V2vEv+`OUGsBCrt)2?j<-K8nK447-wu+Kvj-Cef%aK!BA_PB$Vs+^f3RmVv3+%RdKt-0^-_-oy$}EmY&h3;LUk%$j8|761pO4VQz)<#WyuM~02(DGp_7}nRwB$0zsX50K*0RbM~k2GTCstDoB4vNk-(YH3+Cf;4B|mTM|YyPN-?h@+yTXOWrEA6zup5>`+&&`=~ORelr<%fFRj)b&!Fu^F>Iq^!ilsL$W4Oo$kJpZ^u3=R(T_iRyM)8*g(@Wa>&obSl2ovwm_9tPCd&}KN1M1&VJS#TqR<+7m{H7a>Q8du_WGLR&)f`8H>;~+?-t_lBRiA&KW7%s0SBhpFHrn3?H_g90RdUKZtLJ-u7`W|4rPbM=6+O&Q^BUaVk&yd4CMkF99eN2#b2D}6c$T9-1B|`yJa@s`ytknb-pMx0p{=Cso*R5N`>h^pNXThi$(&ral^g$enk$(>_T3xCuri_t<0_QD0wi@#DNZ4#s)BOl4-sDfc?a8A^IKa#Xd>9q7Bv!1KZ<+rxe1F^q7TedFCt;|$3fM>vM~hd<(U#y3tx;E516%7qSdebFk?pPT2wp5d>LW)IdTXu)#cl@q2bE^O&=;otHNjFGY+6Xo7lR_nS?x#f`B5W+am(M?%~1yn>K_{rg;#oBG5$UVEw@TX0FS!!1vu`g?3H#Pbjd#qfqT-g`)yVXr6IqW-aK^ka!vhRkM0M4wB2uY*&Yq-^F=~DPE61)*lga5Hn?oZryq}Tr=xWl#=z@S*#6nrPC``Wp3-?Ni7tjCElT`Hqy?eH&=MH^T)FO)(ghp}`z#^%U;(H+*;(fou9V=@HIg}O-we@saBzy)UO3Yy-=vo_rfZl^O@C#KPt+!)e?s|pB-A5}%mvwH{)hnyZXLRu{}XtjKgNCw67V3NeZ-!_4J9WPFM#xEl&Ao!{&zFkQ2h7Mr(;R^|%>Z8J=)tMG%*EHFOb3X(B1Y=Qp8gWp-fR$tV$uVkROE%k8!p`2}W2lUI;q#i9H%w`60zk!tK7Ka(bZVLb`GK9)u0`KoTu5oe=Q&WJJNREEzElcbE6;^yNNJy{xrRvO6ce{%j8UjKr=urYp#wc|5)d4JBgxohI6?H}YUUSBuL*nJW{Ad#m`;Wbe6{(j>^8Ap!wfd;C6U~&MZ(^^EVpH90$?pj#sqT4urC8UGH?bU6E!$+;-_78$P1V`LaEvStC{m?LQ+COc2rIvqMw^Qpzt&ryo7-uPla8L}zO2GQCn8d-^5_2JgFyDsZ-@c?2`$u8o%$74!_7??Nhwf^RV!#JkMPs?8VZa}fof?-gW)rFFJT0ZMBtS8UD^US4lKt6N?r}H@+LNgAE-{-03V)4Mwl?DT}O;e>yP_=o3y`;hBO4J}cio=9=T6Ns?AC}QtNtlmb+&-Y#ggUG2QdN>6c`FfIH#%wLFC>m`O2J~&Hn=xH+OvZ5n9_*MgPU)Q!0_>Bhvmjz|1Bc<4~R3d`@^(e&m4j4!Qia2$EW@!TlT(j;B}ywy9wY$vONyGuMN`j>&r*MG*Q4LG3St|qVl^YwsV%-R6^^q-Wz(%i2Y&_Pcx1S$e$-g#Ndcov!09Cy}z1s_94=VOTNf%P=L>VK4_TVMyzYO{{t6=EseTI++k>)C5_cf+!`B46@PueSNaw2Wel&xP~d|DADb90_L15H0f-0P@wp&KL10SlCG4&=>?eH#NetOqCD~oajG(oej--uN0-R|UB6_f>hSBh4cttss)Yxk0|_m#M5l1d&r1~{*xY@sHG1lDGskifd2?Xx`Fo*c9454^YPq1T76jk~F;xoFN~{K^vTRgBW@h$JwOo$Or4ynejwo%=Z7Namyi-tgkpUh6FE>`K~mrrx#<6YFYCSR_7%AFKrv8Zy6exWI?4oRd)$xD&rAC$jHYDj_;%n_yArtAw~7?|$U@No))iJTI>EF^q)=d!RGF+##n;8N@Tto}kY&+`H#_7qfN5<^boOAk{*0K4|xJF&xiO+VI;UpXOaZ0!5zh8Up8gTJh^ZCVBJ_V9(xNk@MZ};NO6!@9b@WL>5-M51_5jUZ_ulg!Fd$vJnmkB5jVT|tPC(D+T5S-Kp_lTfkmW+{S)vzT?-ED8NZRk^yZYPtL#|-Q>Ib+#UxJ{%|_h{y2OS$HA##6kzMK7?AfCw(%-VOZv@wSyMOEgYOq_oJmX6USd*xc!;ZQ!u-M6dD8(n9++b3GwRcD?^8yVWZj`sixHLP1g0c*-iMjTD>4WmTJfbSB~V-!GV;@$E<%0@Itpwhq7B52)<~T4pQTo3H;l2OwS`M)B59wYU4|-9QFT~qT>%4+cf^ST}C2N!Vj&!sp4IRQtRVai)ma-mKCKA%*%-Ws*=m-6Ed$$__doDt*M;F>LMy!DdbI2et1G|e6?&_mxt+5tX5yQ22y&Ri#4mt&kzxeg$81z%0nCFt=cY@PbkpNZTQWC)g+)zXEk1QuEx8l>h>94*epi&?a(tE2?1QsltCbOKp5XL}mm^oq{q9R!$+DPn2X}PkqE263)=jl6uqkoPTR5tw&;4$60I}PC^kEkkzL5v(wx(gQJCQ0%ga(RVUHP6K0Xov(EVUa^!f`rt9hd%p>4ABrg;YWNVz3KsK%KmQ@6Bvmkj9aP4+RJvYq}5p2d*a*8DgcMaUz|B8oLFhGtHZ_$)gFh2M2qGBBJra@n3>h%1q+PZ%yr#BWisLm!PKcg}+2uv|g25jYR&1NL)H22^@sl}-bIVt@eegfDf$J#rFDCJe2N6^1D>liwAe=Asz>4hqc_AxYWV1z~|qJdNyI+!s(Wh7Wui=8ChKrgt#%Ac9kG~vxTA8sSu!`w-*J2GmQVn~wh03m}RdOfyhotzakP#;m+EYC22EosUwM3CciiotMP-DAJTSMVSk|oW5$0t`CJu{C{tqXe6f;JrVi#q!y^u$4=*}n0O$&nw~DZRydv}?9{zr(p{uM?r-2#L)ep1qJ3^vdiyDhl478MlDZ_CE-Scc`-TcV>8hywsxxE*LXk_dVhi|}y|8btCy65U5(bMZ2yATl=Zj$Q3)0K3C5!Oy|*te#`9A^Lqa|TZeJ;8~v8Yv-Es02my@8x`j1j@E@x8jTO8cW>|Y0xH>rR0mPT#VTssgjIWal5F=+MLeDedG}nG(K`%#!Zt8c*T@wSdLm|Xodwb8~pkNVM}Dff6`(@mICu9k3wKoLa@l8{m!*L*^?m3aMhrzD_gS9^36o$c)t}r5X;Rz?TrsQt?;Yp-2Dg?f+rJgHtzzt{1E;wJ0!Su@)%uK|8?*`njexmYfvy^7VTfevYr-VcFAfccahp0CK0V0-dzP_CnQ$bQ?#6oNzSS5h+rMkN$_dPloH(TOj6mtL&d^w6p&GwD4kHoxjfP3NtRf?Zl&>bsYSW>`?F{dZn+$Yy?#Ygc44Dy!$@+7id^ars(4C3Ov26Dwu7@{g*NOGzM3iyDMWvHbTm}q;`_Gy?k=va1)CA;54j#F`blg>rM%+&>FtcwS$2Tv%nNYVp2*jm-w>pbX=uKjFr0@GPW98qas#aBIqaJk#mF@T1`vFF>8WJlU}SOi-Q)bFoD(lM;%Gu_Z-6+gsVew^9Vy)g}VL?YF{upM<^)4=5-hvYYeNbe-(?y0taoDp}cH4qf|yD4fS3Rf~RIyw;q0W)g%U{vFkDpTc{M>n$mtb(I^h+Cc!YBL3eVXMzHqx{=5ajwp>!0T+;bIca^wEROa$6o^K&46PWrQN#FYJ6g)BH6Qg6{?q1O(Kl4@3H>b8CkC^88(tSQg-aRr?B6nCdx@hWob;sfU$ENxd3q4%EWmHz@s#)!F@6b8=2lz^VFiD}VPIN#r`vTkWoX(STWTZyivghMvYD%jromn0F|XsJ*<5;A6gLQ?-fU`VFU+9x5t%PXMKK$tY*oA31=i>18E9s8n+9z#7bpq5z5MPJT6?x-fR9~R958ke1cg&4JxT};DaNtAPsz=UGHo$0s?-k0^B-L3hRh^q)L-@@)Ow46&4R#d-g#VMQuK*!fhTYQ;;`wyz1*?L>*E?I62K6SP1p1h?+L{~ynpgA(K767(+ec|u-D*<5P&~4SwNj0R-&vEr6Ll|f;Xhh({(aX2eKe79O-!9EVL-_>pRphqrz0r|Y^cKtoGRyOsL)$S*QIjrHsWjF8G?wH%)hY=iWu>3B%7p`Zw_c~01i*ch>ye~T^C+q7sgkdAsit5$g4dT)LMZ+N*4wn{nds0*{rkZ2=YV#+3TtF3Ghd>sw|+hPUeMu{3C!%1JU(>#|bSxhn(*I0q8EKa$TC`7evE$2kSnX!($wKfH>7drvG&u)Jv#x*bHfq$Si5t6#d-Ra^_<2N6ssu#E%f_@Uwt07XkkgJ>imo@$j7TD2NmMXmHODmXOIpz76I#vU)y!n7*H8KF@ynYTYwAO5Q-ewYj|r<})dLyO_ptoA8#-+ppsx|z`|p!`@MvclG8T{USA36MHAR1Xtl5|#+rcb4mkp|Iw2_qY2LC0??2eTe$uU-=kMwsLBKMRef1{20Z)FSTs=29@CW2qF0dc;5mPcYWkQ#s-;~389_8$|qD)n1a5T-qaYM?O+LEbt{Z5&bMmRwqzzBR31$p1UCSnwzv;nGIjS@_p~UwTV;fnlQ(Lpy%T_maDWQYtIo%#F63JvfH=69zTi>{jbVwp@gtBl3pU+u@q{yG($7HGKT1s<$bNV^F{Yz|YG)6}|+ut4>p!(dXwe4P{-lVE^nnHuabbE`5`Yu5_5SQCvJDySP&!d%YAJcztzyxZUMFSfWI9WdbA-0gowjLmty67q#)^f^Kp2Tu}dRb43eYlYPfDUz}9>Z*Nsv0%%c&}HDuy{7*3FV%ev1-GNEHAI}&2U|cH*`n*1mfalr;nSxN3#-XVmD`hcRE)0jL2m*);(JiHGMm27MOCR;+~nOpLBT~AiT*kZ<^s?h<+u!eva91bzFD^AAU4OV$)~L8qk)GFvM9@d7dP4*j6|z%VZr^P5b!1gmgSRa-M3A(*1+O!GT*)+!heOX;&SHUb~7mN!sUj2+(U$b#W|U<-eSQKAXl>Am$N_S$LS6#hJM9B~jD}zBw1By90)`=}lR&o?D!Ql^WH&3(B4(9W3@hJ22R$aO4ZL6@HTG8HGBo##RtP{@>`2kGGP$KxLCL=7-q8-ZDd3+<AxEiX(0aHSwph`J<$UrE-MJ3P~N*4kKwA7^AN*2`Rp8Gq;a08%~<(21Y=x4?&zF$&%TLms-PVqcLVG~B^svgV!AfD*+OOl(90$~A$y|NtChW|-7k^~NT>s~CM`OVUAzcz{a?ryS(MG6R%`n?1y_}BAba8X{61d!)eI{Wl~qOxiw9GK?c>^b78S3-ZD3!~K=mSWd;-hhUV1!Ll1cC=KJYU>(K|+tsqZoZaY(dTrLd{b=H)ai%5ard}wXU=$5-H^VyMY(Z6JJV{`rA6iu&eCGDW?5Ou=O)rREHaos|34njn8tr^)tpxZXJ1V~P*E#oym%~T_0eu{`3TPR2%=u?n23@5G|M~_uuMLC0EMmJ%LeElN#OpQBN8wdMQ`_-Z@WHspGvMbCZ!h>#Ll98`b71Cm_Q{vi0R1RV7g{bZZ7-}+;vy!BpZC778R4!Rp9cYOI`u3zKcRH3x4mybM#=8w2MQ{SKO`JT{d;J4_koNV$jUG*ua2G*JmW58jf?;;DOa!pf(I_AIpgwnRG0CZ!Of7+-@5K_f%0jJu&UZxa2&RLwDz9=Mi$2e8kG+~J^1widq6`7&_N$G_8rlM~XBUt5XtG&mrjxv6EJ%W5IWd*;THl>1Q2M$}sd`J+o^_IWyad4o#JwOUJ&}R-HK-UCKjzg*)yizIRa@bjj<^=pGQ-YVffgRlC{qf3vnBwmcCh@ZVUjj8tvU_1cAE*~(+Vx>SwqC@4@_{#4q|(+q&hw1eZQCYHyi;Y5*1>LTVv)0x+2SFj)QE{L*|cksNuxppM2n>z{-We`TQ!d5Ma(6P**u9w^~q={)ae6xh&vc1`Y%FrOH9$6M4zjN}bI$Kw>e6MSZj&h|c`eXKI8@-%9x6{dMI{S9K3QpRZQ!Zdj@BTy1=v`W8qpaWL=s@&mmvSqOeR$jx&n2Ni-FQkkv`EHy%B$P5Slz?j7t36FZ|%CXIrlt&-*IaYLZ0)f5$?RSSKj;o`eerE{->s($6))4X7e!vLbI#cuHDp;A*(x2H%Q2Z0RQ?{Bl$_>@GCPg29^Y*zmvM>*jhzvP)(;cIK;A_S^ONh#{sE83)rRo|3;tMHz7@J{vt00q&6SG6okZKmn`#j;kyQ_B7XS+9B~C;+~f*`Ub$QW^B^PX<2jDsJHSYmVOQtIDy}5U3EI5NnlW9#p)sIWm3_^GO}8kuL$*kqIKkl)Qw>S_DA!Ykiu=9gXAQH&9LbYW{3VMu6nN#l|MlC%bsU)zqN?=^W!zFZrh>d|LTwQ|cLboiH7s2_n+zVq1Js2#sTU*Uryozs$6$Q4gD;N&YF8mE4FAq2~_AGgfHj4bhO{U7r%L8lbV?H~Bp}9ZT$01y^A`@1^eX*~ql>Rl%g%qDKT`Vy4Gbl1iKW|YI68Pw{y4gLTe46k=GnKl9Eqy@t)6bQ2T`b>n)%UZH0VK7n*mKO4ey=yg2eSEy5kf74vDws34+ZH01t9fD-Cjknzbww(R5bgF5)Sgvxfp;38C9h1@PO`CCiQKIa~+EF%S#TbmncERYDxifqanPN|!;%x6%#jufn;Tuc%l7j3Gn^-~_Wy!QwKpew)U66cv?(i@WA>66fVkYr`HvWb_0567xQUTP0~EcX!nZnd^e%G$7AjqQUBdoHR1(?WJs;Gy|vAZ4i!jqH`YH>*Uo3T{71zluc7Yd8h|s*OY4zYaUj5L#(y8rlWcp^aFt47PQJKz?|AC>6T(v)8QTZGrf(WWE>r(AG?dzd8xAxPW1yaeAipo6|&7qbE%y%PS7IhA8qbZb0PiSeKQiYPIPDZoMzZd3hz(v^jBaM3&J3TVGGwrd8yz_l^~SVoUY-(;wz-dN(@h80}9R-?ue~IPV{Jxh91xS!i?S+cc@r4fTikS*buJ-QStlyp;A_TQoJH;upXpJE%tbda9moRLo%A90X2g(cDR&jLzZy=|}?Qe8YiCUPsuZq;d8_UFuwHt0swR7hd3(v?D>L&v5U2UxFRacF|Lb11k)SD_SvF17}=KL2k?@?18GK*D;KC`RZX^*!jkSJ>4jYbvX70!hSvwjb1MEM>b>hhWpGy=8O{XBJg>qq^D0op0TO~rTQlHeZk^fN)alglR$cNgAm-3hQws-r}S4!7rf-fXhbtS5a;FInO3-R~x~QJB*?DX8oqk!9I`M6*E%9I(z0_<+^Yy2!{2aaH+=M35`Dbvhp>RoQc07H~yj-RA^cRy@vyk;w<8zaHhv@tQ|+XD)GM)A>q{ie8ia8xP5?)eh#G#U#$*%asvrtzGGli!n|uBvUxtQyXSHj','exec')) - except Exception as e: - _qSo8Nxa4dWU("错误",_I4wQxKGo()+str(e)[:50],1) - sys.exit(1) -else: - time.sleep(1) - sys.exit(1) +def get_staff_name(staff_id): + """根据工号获取员工姓名""" + try: + # 从在线链接获取staff.json + url = "http://cos.ui-beam.com/work_scripts/monitor/config/staff.json" + + # 尝试不使用代理直接获取 + try: + response = requests.get(url, timeout=5, proxies={'http': None, 'https': None}, verify=False) + except: + # 如果直接获取失败,尝试使用系统代理 + proxies = { + 'http': os.environ.get('HTTP_PROXY', ''), + 'https': os.environ.get('HTTPS_PROXY', '') + } + response = requests.get(url, timeout=5, proxies=proxies, verify=False) + + if response.status_code == 200: + staff_data = response.json() + return staff_data.get(staff_id) + else: + log(f"获取在线staff.json失败,HTTP状态码: {response.status_code}") + return None + except Exception as e: + log(f"获取员工姓名失败: {str(e)}") + return None + +@app.route('/login', methods=['GET', 'POST']) +def login(): + """登录路由""" + if request.method == 'POST': + staff_id = request.form.get('username', '').strip() # 从username字段获取工号 + breeze_cookie = request.form.get('breeze_cookie', '').strip() # 获取Breeze Cookie + cms_cookie = request.form.get('cms_cookie', '').strip() # 获取CMS Cookie + staff_name = get_staff_name(staff_id) + + if staff_name: + session['logged_in'] = True + session['staff_name'] = staff_name + session['username'] = staff_id # 保存工号 + session['breeze_cookie'] = breeze_cookie # 保存Breeze Cookie + session['cms_cookie'] = cms_cookie # 保存CMS Cookie + session['just_logged_in'] = True # 添加标记,用于显示欢迎消息 + + # 添加详细的系统启动日志 + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + print(f"\n{current_time} - INFO - Breeze监控日志系统初始化成功") + print(f"{current_time} - INFO - CMS监控日志系统初始化成功") + print(f"{current_time} - INFO - 监控服务启动") + print(f"{current_time} - INFO - 初始化监控服务...") + print(f"{current_time} - INFO - 初始化用户凭据...") + print(f"{current_time} - INFO - [Breeze] 已从环境变量加载用户凭据: {staff_id}") + print(f"{current_time} - INFO - [CMS] 已从环境变量加载用户凭据: {staff_id}") + print(f"{current_time} - INFO - 加载系数配置...") + + # 加载并显示 Breeze 系数 + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + breeze_coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json') + if os.path.exists(breeze_coefficients_file): + with open(breeze_coefficients_file, 'r', encoding='utf-8') as f: + breeze_coefficients = json.load(f) + print(f"{current_time} - INFO - [Breeze] 从配置文件加载系数: {breeze_coefficients}") + print(f"{current_time} - INFO - [Breeze] 当前使用的系数已更新") + except Exception as e: + print(f"{current_time} - ERROR - [Breeze] 加载系数配置失败: {str(e)}") + + # 加载并显示 CMS 系数 + try: + cms_coefficients_file = os.path.join(script_dir, 'cms_coefficients.json') + if os.path.exists(cms_coefficients_file): + with open(cms_coefficients_file, 'r', encoding='utf-8') as f: + cms_coefficients = json.load(f) + print(f"{current_time} - INFO - [CMS] 从配置文件加载系数: {cms_coefficients}") + print(f"{current_time} - INFO - [CMS] 当前使用的系数: 评论={cms_coefficients['comment']}, 动态={cms_coefficients['feed']}, 举报={cms_coefficients['complaint']}") + except Exception as e: + print(f"{current_time} - ERROR - [CMS] 加载系数配置失败: {str(e)}") + + # 启动监控进程 + start_monitor_processes() + + print(f"{current_time} - INFO - Breeze监控线程启动") + print(f"{current_time} - INFO - CMS监控线程启动") + + # 返回成功响应,让前端显示欢迎消息 + return jsonify({ + 'success': True, + 'staff_name': staff_name, + 'redirect': url_for('index') + }) + else: + return jsonify({ + 'success': False, + 'message': f'您的账号 ({staff_id}) 尚未在系统上注册或为不可用状态,无法登录,请联系系统管理员 (od0269@mail.go.com) 协助' + }) + + return render_template('login.html') + +@app.route('/logout') +def logout(): + """登出""" + try: + # 记录登出用户 + username = session.get('username', '未知用户') + log("用户 [" + username + "] 登出系统") + + # 清除会话 + session.clear() + + # 标记系统即将退出 + global shutdown_flag + shutdown_flag = True + + # 尝试停止监控进程 + if processes['breeze'] and processes['breeze'].poll() is None: + try: + processes['breeze'].terminate() + log("已停止Breeze监控进程") + except Exception as e: + log("停止Breeze监控进程失败: " + str(e)) + + if processes['cms'] and processes['cms'].poll() is None: + try: + processes['cms'].terminate() + log("已停止CMS监控进程") + except Exception as e: + log("停止CMS监控进程失败: " + str(e)) + + # 删除共享数据文件 + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + data_files = ['breeze_daily.json', 'breeze_hourly.json', 'cms_daily.json', 'cms_hourly.json'] + + for file_name in data_files: + file_path = os.path.join(script_dir, file_name) + if os.path.exists(file_path): + os.remove(file_path) + log(f"已删除共享数据文件: {file_name}") + log("成功清理所有共享数据文件") + except Exception as e: + log(f"删除共享数据文件时出错: {str(e)}") + + # 在一个后台线程中等待几秒后退出应用程序 + def shutdown_app(): + time.sleep(3) # 等待3秒 + log("用户登出,系统正在完全关闭...") + # 关闭所有子进程和当前进程 + if sys.platform.startswith('win'): + # 在Windows系统下,强制关闭当前进程及所有子进程 + try: + # 获取当前进程PID + current_pid = os.getpid() + # 使用taskkill命令强制结束进程树 + subprocess.run(f'taskkill /F /T /PID {current_pid}', shell=True) + except Exception as e: + log(f"关闭进程时出错: {str(e)}") + else: + # 非Windows系统使用标准退出方式 + os._exit(0) # 强制关闭整个程序 + + # 启动关闭线程 + threading.Thread(target=shutdown_app).start() + + # 返回包含关闭倒计时的页面 + return """ + + + + + 正在关闭系统... + + + +
+

正在关闭系统

+

数据清理完毕,系统即将退出,请手动关闭浏览器页面和控制台

+
+
3
+
+ + + + +""" + except Exception as e: + log("Error in logout route: " + str(e)) + return "Logout error. Please check logs.", 500 + +def read_data_file(filename): + """读取数据文件""" + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(script_dir, filename) + + if not os.path.exists(file_path): + return None + + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + return data + except Exception as e: + log("Failed to read file {0}: {1}".format(filename, str(e))) + return None + +def check_version(): + """检查系统版本并与在线版本比较""" + global version_check_thread_running + + try: + log("开始检查系统版本更新...") + with version_lock: + version_status['last_check_time'] = datetime.now() + + # 确保VERSION.txt文件存在且内容与VERSION一致 + local_version = ensure_version_file() + log(f"当前本地版本: {local_version}") + + # 获取在线版本 + online_version = get_online_version() + log(f"获取到的在线版本: {online_version}") + + if online_version: + with version_lock: + version_status['online_version'] = online_version + # 比较版本号,只在线上版本比本地版本新时才提示更新 + local_timestamp = int(local_version[1:].split('-')[0]) # 去掉'v'前缀并转换为数字 + online_timestamp = int(online_version[1:].split('-')[0]) # 去掉'v'前缀并转换为数字 + version_status['has_update'] = online_timestamp > local_timestamp + log(f"版本比较结果 - 在线版本: {online_version}, 本地版本: {local_version}, 是否有更新: {version_status['has_update']}") + + # 如果有更新且没有正在运行的线程,启动告警线程 + if version_status['has_update'] and not version_check_thread_running: + version_check_thread_running = True + threading.Thread(target=notify_version_update).start() + + return { + 'current_version': VERSION, + 'online_version': online_version, + 'has_update': version_status['has_update'], + 'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None + } + else: + log("无法获取在线版本,返回本地版本信息") + return { + 'current_version': VERSION, + 'online_version': None, + 'has_update': False, + 'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None + } + except Exception as e: + log(f"版本检查过程中发生错误: {str(e)}") + return { + 'current_version': VERSION, + 'online_version': None, + 'has_update': False, + 'last_check_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'error': str(e) + } + finally: + # 确保线程标志被重置 + if version_check_thread_running: + version_check_thread_running = False + +def notify_version_update(): + """通知用户有新版本可用""" + try: + # 确保VERSION.txt文件存在 + ensure_version_file() + with version_lock: + online_version = version_status['online_version'] + + if online_version: + message = f"系统有新版本可用: {online_version},当前版本: {VERSION}" + log(message) + + try: + # 发送桌面通知 + from plyer import notification + notification.notify( + title="系统版本更新", + message=message + "\n点击'检测更新'按钮了解详情", + app_icon=None, + timeout=10, + toast=False + ) + + # 通过WebSocket通知前端弹出版本检测窗口 + socketio.emit('version_update', { + 'type': 'show_version_dialog', + 'current_version': VERSION, + 'online_version': online_version + }) + + except Exception as e: + log(f"显示桌面通知失败: {str(e)}") + except Exception as e: + log(f"通知版本更新失败: {str(e)}") + finally: + # 重置线程运行标志 + global version_check_thread_running + version_check_thread_running = False + +def monitor_version_thread(): + """版本检测后台线程""" + log("版本监控线程启动") + while True: + try: + # 每30分钟检查一次版本更新 + check_version() + time.sleep(1800) # 30分钟 + except Exception as e: + log(f"版本监控线程异常: {str(e)}") + time.sleep(600) # 出错后等待10分钟重试 + +@app.route('/api/get-version') +def get_version(): + """获取系统版本信息""" + try: + # 确保VERSION.txt文件存在 + ensure_version_file() + + with version_lock: + status = { + 'current_version': VERSION, + 'local_version': VERSION, # 固定的本地版本号 + 'online_version': version_status['online_version'], + 'has_update': version_status['has_update'], + 'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None + } + + return jsonify({ + 'success': True, + 'data': status + }) + except Exception as e: + log("Error in get_version route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +@app.route('/api/check-version') +def check_version_api(): + """手动检查版本更新的API""" + try: + result = check_version() + + if result: + return jsonify({ + 'success': True, + 'data': result + }) + else: + # 至少返回当前版本信息 + return jsonify({ + 'success': False, + 'message': '获取在线版本信息失败', + 'data': { + 'current_version': VERSION, + 'local_version': VERSION, + 'online_version': None, + 'has_update': False, + 'last_check_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + }) + except Exception as e: + log("Error in check_version_api route: " + str(e)) + return jsonify({'success': False, 'message': f'检测版本更新失败: {str(e)}'}), 500 + +@app.route('/api/get-stats') +def get_stats(): + """获取统计数据""" + try: + breeze_hourly = read_data_file('breeze_hourly.json') + breeze_daily = read_data_file('breeze_daily.json') + cms_hourly = read_data_file('cms_hourly.json') + cms_daily = read_data_file('cms_daily.json') + + # 获取时间戳 + breeze_hourly_time = datetime.strptime(breeze_hourly['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if breeze_hourly else '' + breeze_daily_time = datetime.strptime(breeze_daily['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if breeze_daily else '' + cms_hourly_time = datetime.strptime(cms_hourly['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if cms_hourly else '' + cms_daily_time = datetime.strptime(cms_daily['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if cms_daily else '' + + # 构建返回数据 + data = { + 'breeze_hourly': { + 'total': breeze_hourly['stats']['total'] if breeze_hourly else 0, + 'weighted_total': round(breeze_hourly['stats']['weighted_total'], 2) if breeze_hourly else 0, + 'categories': breeze_hourly['stats']['categories'] if breeze_hourly else {}, + 'timestamp': breeze_hourly_time + }, + 'breeze_daily': { + 'total': breeze_daily['stats']['total'] if breeze_daily else 0, + 'weighted_total': round(breeze_daily['stats']['weighted_total'], 2) if breeze_daily else 0, + 'categories': breeze_daily['stats']['categories'] if breeze_daily else {}, + 'timestamp': breeze_daily_time + }, + 'cms_hourly': { + 'stats': cms_hourly['stats']['stats'] if cms_hourly else {'comment': 0, 'feed': 0, 'complaint': 0}, + 'weighted_total': round(cms_hourly['stats']['weighted_total'], 2) if cms_hourly else 0, + 'total_count': cms_hourly['stats']['total_count'] if cms_hourly else 0, + 'coefficients': cms_hourly['stats']['coefficients'] if cms_hourly else {'comment': 0.55, 'feed': 1.54, 'complaint': 5.4}, + 'timestamp': cms_hourly_time + }, + 'cms_daily': { + 'stats': cms_daily['stats']['stats'] if cms_daily else {'comment': 0, 'feed': 0, 'complaint': 0}, + 'weighted_total': round(cms_daily['stats']['weighted_total'], 2) if cms_daily else 0, + 'total_count': cms_daily['stats']['total_count'] if cms_daily else 0, + 'coefficients': cms_daily['stats']['coefficients'] if cms_daily else {'comment': 0.55, 'feed': 1.54, 'complaint': 5.4}, + 'timestamp': cms_daily_time + } + } + + # 计算总折算量 + total_weighted = (data['breeze_hourly']['weighted_total'] + data['cms_hourly']['weighted_total']) + + return jsonify({ + 'code': 0, + 'data': data, + 'total_weighted': round(total_weighted, 2) + }) + except Exception as e: + log(f"获取统计数据时发生错误: {str(e)}") + return jsonify({'code': 1, 'message': '获取统计数据失败'}) + +def check_alarm(total_weighted): + """检查并触发告警""" + try: + with alarm_lock: + now = datetime.now() + + # 如果最近30秒内已经告警过,增加计数但不重复告警 + if alarm_status['last_alarm_time'] and (now - alarm_status['last_alarm_time']).total_seconds() < 30: + return + + # 设置告警状态 + alarm_status['last_alarm_time'] = now + alarm_status['alarm_count'] += 1 + alarm_status['is_alarming'] = True + + # 如果已经达到3次告警,不再继续 + if alarm_status['alarm_count'] > 3: + return + + # 启动线程播放告警声音和显示通知 + # 将当前加权总值传递给函数 + threading.Thread(target=lambda: show_alarm_notification(total_weighted)).start() + log("Alarm triggered: Current weighted total {0:.2f}, exceeded threshold {1}".format(total_weighted, ALARM_THRESHOLD)) + except Exception as e: + log("Error in check_alarm: " + str(e)) + +def show_alarm_notification(total_weighted, is_test=False, alarm_type="实时告警"): + """显示告警通知和播放告警声音""" + try: + # 播放系统告警声音 + try: + import winsound + winsound.PlaySound("SystemExclamation", winsound.SND_ALIAS) + except: + # 如果winsound不可用,尝试使用beep + print('\a') + + # 添加桌面通知 + try: + from plyer import notification + + # 计算超出阈值的百分比 + over_percentage = ((total_weighted - ALARM_THRESHOLD) / ALARM_THRESHOLD) * 100 + + # 根据是否为测试调整标题 + title = "测试告警" if is_test else "审核数量告警" + + # 根据是否为测试调整消息前缀 + prefix = "[测试数据] " if is_test else "" + + notification.notify( + title=title, + message=f"{prefix}{alarm_type}当前小时加权总计:{total_weighted:.2f}\n阈值:{ALARM_THRESHOLD}\n超出:{over_percentage:.1f}%\n请立即检查数据监控看板!", + app_icon=None, + timeout=15, + toast=False + ) + except Exception as e: + log("Failed to show desktop notification: " + str(e)) + + except Exception as e: + log("Failed to play alarm sound: " + str(e)) + +@app.route('/api/get-alarm-status') +def get_alarm_status(): + """获取告警状态""" + try: + with alarm_lock: + status = { + 'is_alarming': alarm_status['is_alarming'], + 'alarm_count': alarm_status['alarm_count'], + 'last_alarm_time': alarm_status['last_alarm_time'].strftime('%Y-%m-%d %H:%M:%S') if alarm_status['last_alarm_time'] else None, + 'threshold': ALARM_THRESHOLD, + 'alarm_type': alarm_status['alarm_type'] + } + + return jsonify({ + 'success': True, + 'data': status + }) + except Exception as e: + log("Error in get_alarm_status route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +@app.route('/api/reset-alarm') +def reset_alarm(): + """重置告警状态""" + try: + with alarm_lock: + alarm_status['is_alarming'] = False + alarm_status['alarm_count'] = 0 + + return jsonify({ + 'success': True, + 'message': 'Alarm has been reset' + }) + except Exception as e: + log("Error in reset_alarm route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +@app.route('/api/restart-monitoring') +def restart_monitoring(): + """重启监控进程""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + log("正在尝试重启监控进程...") + + # 停止现有进程 + if processes['breeze'] and processes['breeze'].poll() is None: + try: + processes['breeze'].terminate() + log("Breeze监控进程已停止") + except Exception as e: + log(f"停止Breeze监控进程出错: {str(e)}") + + if processes['cms'] and processes['cms'].poll() is None: + try: + processes['cms'].terminate() + log("CMS监控进程已停止") + except Exception as e: + log(f"停止CMS监控进程出错: {str(e)}") + + # 等待进程完全终止 + time.sleep(2) + + # 获取Python可执行文件路径 + python_executable = sys.executable + log(f"使用Python可执行文件: {python_executable}") + + # 重新启动 + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # 获取会话中的凭据 + breeze_cookie = session.get('breeze_cookie', '') + cms_cookie = session.get('cms_cookie', '') + username = session.get('username', '') + + # 启动breeze监控 + breeze_script = os.path.join(script_dir, "breeze_monitor.py") + env = os.environ.copy() + env['BREEZE_COOKIE'] = breeze_cookie + env['BREEZE_USERNAME'] = username + + processes['breeze'] = subprocess.Popen( + [python_executable, breeze_script], + env=env, + shell=False + ) + log(f"Breeze监控进程已启动, PID: {processes['breeze'].pid}") + + # 启动cms监控 + cms_script = os.path.join(script_dir, "cms_monitor.py") + env = os.environ.copy() + env['CMS_COOKIE'] = cms_cookie + env['CMS_USERNAME'] = username + + processes['cms'] = subprocess.Popen( + [python_executable, cms_script], + env=env, + shell=False + ) + log(f"CMS监控进程已启动, PID: {processes['cms'].pid}") + + return jsonify({ + 'success': True, + 'message': '监控进程已成功重启' + }) + except Exception as e: + log(f"重启监控进程时出错: {str(e)}") + return jsonify({ + 'success': False, + 'message': f"重启监控进程失败: {str(e)}" + }), 500 + except Exception as e: + log(f"重启监控进程时出错: {str(e)}") + return jsonify({ + 'success': False, + 'message': f"重启监控进程失败: {str(e)}" + }), 500 + +def open_browser(): + """在新线程中打开浏览器""" + time.sleep(1) # 等待服务器启动 + try: + webbrowser.open('http://localhost:8000') + except Exception as e: + log("Failed to open browser: " + str(e)) + +@app.route('/api/check-now') +def check_now(): + """立即检查当前小时数据""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + # 获取Python可执行文件路径 + python_executable = sys.executable + + # 向子进程发送检查请求 + if processes['breeze'] and processes['breeze'].poll() is None: + # 获取会话中的凭据 + breeze_cookie = session.get('breeze_cookie', '') + username = session.get('username', '') + + # 创建临时环境 + env = os.environ.copy() + env['BREEZE_COOKIE'] = breeze_cookie + env['BREEZE_USERNAME'] = username + + # 启动一个临时进程来执行检查,添加--force参数强制使用整点时间 + # 添加--no-config-check参数防止频繁检查配置文件 + script_dir = os.path.dirname(os.path.abspath(__file__)) + subprocess.Popen( + [python_executable, os.path.join(script_dir, "breeze_monitor.py"), "--check-now", "--force", "--no-config-check"], + env=env, + shell=False + ) + log("开始执行Breeze系统手动检查") + + if processes['cms'] and processes['cms'].poll() is None: + # 获取会话中的凭据 + cms_cookie = session.get('cms_cookie', '') + username = session.get('username', '') + + # 创建临时环境 + env = os.environ.copy() + env['CMS_COOKIE'] = cms_cookie + env['CMS_USERNAME'] = username + + # 启动一个临时进程来执行检查,添加--force参数强制使用整点时间 + # 添加--no-config-check参数防止频繁检查配置文件 + script_dir = os.path.dirname(os.path.abspath(__file__)) + subprocess.Popen( + [python_executable, os.path.join(script_dir, "cms_monitor.py"), "--check-now", "--force", "--no-config-check"], + env=env, + shell=False + ) + log("开始执行CMS系统手动检查") + + return jsonify({ + 'success': True, + 'message': '已启动数据检查', + 'threshold': ALARM_THRESHOLD + }) + except Exception as e: + log(f"立即检查功能出错: {str(e)}") + return jsonify({'success': False, 'message': f'数据检查启动失败: {str(e)}'}), 500 + +@app.route('/api/test-alarm') +def test_alarm(): + """测试告警功能""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + # 检查是否为实际数据告警 + is_real_data = request.args.get('real_data', 'false').lower() == 'true' + + # 根据请求类型选择不同的消息 + message = "实时告警" if is_real_data else "测试告警" + + # 立即触发第一次告警 + if not is_real_data: + # 手动触发告警并传递测试告警值 + show_alarm_notification(ALARM_THRESHOLD + 100, is_test=True, alarm_type="测试告警") + else: + # 从API获取当前真实的小时总量 + try: + breeze_hourly = read_data_file('breeze_hourly.json') or {'stats': {'weighted_total': 0}} + cms_hourly = read_data_file('cms_hourly.json') or {'stats': {'weighted_total': 0}} + + total_hourly = 0 + if 'stats' in breeze_hourly and 'weighted_total' in breeze_hourly['stats']: + total_hourly += breeze_hourly['stats']['weighted_total'] + if 'stats' in cms_hourly and 'weighted_total' in cms_hourly['stats']: + total_hourly += cms_hourly['stats']['weighted_total'] + + # 只有在实际超过阈值时才显示通知 + if total_hourly >= ALARM_THRESHOLD: + show_alarm_notification(total_hourly, is_test=False, alarm_type="实时告警") + except Exception as e: + log(f"Error getting real data for alarm: {str(e)}") + + # 设置告警状态,启用连续告警 + with alarm_lock: + now = datetime.now() + alarm_status['last_alarm_time'] = now + alarm_status['is_alarming'] = True + alarm_status['alarm_type'] = message # 保存告警类型 + + # 设置告警计数为1,开始计数 + alarm_status['alarm_count'] = 1 + + # 创建后台线程发送连续告警 + if not is_real_data: # 仅对测试告警启用连续通知 + threading.Thread(target=lambda: send_sequential_test_alarms("测试告警")).start() + + log(f"{message} triggered by user") + + return jsonify({ + 'success': True, + 'message': f'{message} successful' + }) + except Exception as e: + log("Error in test_alarm route: " + str(e)) + return jsonify({'success': False, 'message': 'Failed to trigger alarm: ' + str(e)}), 500 + +@app.route('/api/get-coefficients') +def get_coefficients(): + """获取CMS系数配置""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + # 从CMS系数配置文件读取 + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + coefficients_file = os.path.join(script_dir, 'cms_coefficients.json') + + if not os.path.exists(coefficients_file): + # 如果配置文件不存在,返回默认系数 + default_coefficients = { + 'comment': 0.55, + 'feed': 1.54, + 'complaint': 5.4 + } + return jsonify({ + 'success': True, + 'data': default_coefficients, + 'message': 'Using default coefficients' + }) + + with open(coefficients_file, 'r', encoding='utf-8') as f: + coefficients = json.load(f) + + return jsonify({ + 'success': True, + 'data': coefficients + }) + except Exception as e: + log(f"Error reading coefficients file: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'Error reading coefficients: {str(e)}' + }) + + except Exception as e: + log("Error in get_coefficients route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +@app.route('/api/update-coefficients', methods=['POST']) +def update_coefficients(): + """更新CMS系数配置""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + # 获取请求中的系数 + try: + data = request.get_json() + if not data: + return jsonify({ + 'success': False, + 'message': 'No data provided' + }) + + # 验证系数格式 + required_keys = ['comment', 'feed', 'complaint'] + for key in required_keys: + if key not in data: + return jsonify({ + 'success': False, + 'message': f'Missing required key: {key}' + }) + + # 验证值是数字 + try: + data[key] = float(data[key]) + except ValueError: + return jsonify({ + 'success': False, + 'message': f'Value for {key} must be a number' + }) + + # 确保只有必要的键 + coefficients = { + 'comment': data['comment'], + 'feed': data['feed'], + 'complaint': data['complaint'] + } + + # 保存到配置文件 + script_dir = os.path.dirname(os.path.abspath(__file__)) + coefficients_file = os.path.join(script_dir, 'cms_coefficients.json') + + with open(coefficients_file, 'w', encoding='utf-8') as f: + json.dump(coefficients, f, indent=4, ensure_ascii=False) + + log(f"CMS系数已更新: 评论={coefficients['comment']}, 动态={coefficients['feed']}, 举报={coefficients['complaint']}") + + # 使用命令行参数方式通知CMS监控进程更新系数 + try: + # 获取Python可执行文件路径 + python_executable = sys.executable + + # 获取CMS监控脚本路径 + cms_script = os.path.join(script_dir, "cms_monitor.py") + + # 获取环境变量 + env = os.environ.copy() + env['CMS_COOKIE'] = session.get('cms_cookie', '') + env['CMS_USERNAME'] = session.get('username', '') + + # 启动进程更新系数 + cmd = [ + python_executable, + cms_script, + "--update-coefficients", + str(coefficients['comment']), + str(coefficients['feed']), + str(coefficients['complaint']) + ] + + subprocess.Popen( + cmd, + env=env, + shell=False + ) + + log("已发送系数更新命令给CMS监控进程") + except Exception as e: + log(f"发送系数更新命令失败: {str(e)}") + + return jsonify({ + 'success': True, + 'message': 'Coefficients updated successfully', + 'data': coefficients + }) + + except Exception as e: + log(f"更新系数失败: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'Error updating coefficients: {str(e)}' + }) + + except Exception as e: + log("Error in update_coefficients route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +def send_sequential_test_alarms(alarm_type): + """发送连续的测试告警通知""" + try: + # 等待10秒后发送第二次告警 + time.sleep(10) + + with alarm_lock: + if alarm_status['is_alarming'] and alarm_status['alarm_count'] < 3: + alarm_status['alarm_count'] = 2 + # 发送第二次告警 + show_alarm_notification(ALARM_THRESHOLD + 120, is_test=True, alarm_type=alarm_type) + log("Second test alarm notification sent") + + # 再等待10秒后发送第三次告警 + time.sleep(10) + + with alarm_lock: + if alarm_status['is_alarming'] and alarm_status['alarm_count'] < 3: + alarm_status['alarm_count'] = 3 + # 发送第三次告警 + show_alarm_notification(ALARM_THRESHOLD + 150, is_test=True, alarm_type=alarm_type) + log("Third test alarm notification sent") + except Exception as e: + log(f"Error in sequential test alarms: {str(e)}") + +@app.route('/api/get-breeze-coefficients') +def get_breeze_coefficients(): + """获取Breeze系数配置""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + # 从Breeze系数配置文件读取 + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json') + + if not os.path.exists(coefficients_file): + # 如果配置文件不存在,返回错误信息 + return jsonify({ + 'success': False, + 'message': 'Breeze coefficients file not found' + }) + + with open(coefficients_file, 'r', encoding='utf-8') as f: + coefficients = json.load(f) + + return jsonify({ + 'success': True, + 'data': coefficients + }) + except Exception as e: + log(f"Error reading Breeze coefficients file: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'Error reading coefficients: {str(e)}' + }) + + except Exception as e: + log("Error in get_breeze_coefficients route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +@app.route('/api/update-breeze-coefficients', methods=['POST']) +def update_breeze_coefficients(): + """更新Breeze系数配置""" + try: + if 'logged_in' not in session: + return jsonify({'success': False, 'message': 'Not logged in'}) + + # 获取请求中的系数 + try: + data = request.get_json() + if not data: + return jsonify({ + 'success': False, + 'message': 'No data provided' + }) + + # 验证系数格式 + if not isinstance(data, dict): + return jsonify({ + 'success': False, + 'message': 'Invalid data format, expected a dictionary' + }) + + # 验证所有值是数字 + for key, value in data.items(): + try: + data[key] = float(value) + except ValueError: + return jsonify({ + 'success': False, + 'message': f'Value for {key} must be a number' + }) + + # 保存到配置文件 + script_dir = os.path.dirname(os.path.abspath(__file__)) + coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json') + + with open(coefficients_file, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=4, ensure_ascii=False) + + log(f"Breeze系数已更新") + + # 使用命令行参数方式通知Breeze监控进程更新系数 + try: + # 获取Python可执行文件路径 + python_executable = sys.executable + + # 获取Breeze监控脚本路径 + breeze_script = os.path.join(script_dir, "breeze_monitor.py") + + # 获取环境变量 + env = os.environ.copy() + env['BREEZE_COOKIE'] = session.get('breeze_cookie', '') + env['BREEZE_USERNAME'] = session.get('username', '') + + # 启动进程更新系数 - 一次只更新一个系数 + for coefficient_type, coefficient_value in data.items(): + cmd = [ + python_executable, + breeze_script, + "--update-coefficients", + coefficient_type, + str(coefficient_value) + ] + + subprocess.Popen( + cmd, + env=env, + shell=False + ) + + log("已发送系数更新命令给Breeze监控进程") + except Exception as e: + log(f"发送系数更新命令失败: {str(e)}") + + return jsonify({ + 'success': True, + 'message': 'Breeze coefficients updated successfully', + 'data': data + }) + + except Exception as e: + log(f"更新Breeze系数失败: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'Error updating coefficients: {str(e)}' + }) + + except Exception as e: + log("Error in update_breeze_coefficients route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +@app.route('/api/acknowledge-alarm') +def acknowledge_alarm(): + """确认并重置告警状态""" + try: + with alarm_lock: + # 完全重置告警状态 + alarm_status['is_alarming'] = False + alarm_status['alarm_count'] = 0 + alarm_status['last_alarm_time'] = None + + # 记录告警已被确认 + log("告警已被用户确认并重置") + + return jsonify({ + 'success': True, + 'message': 'Alarm has been acknowledged and reset' + }) + except Exception as e: + log("Error in acknowledge_alarm route: " + str(e)) + return jsonify({'success': False, 'message': 'Internal server error'}), 500 + +def is_port_available(port): + """检查端口是否可用""" + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(1) + result = s.connect_ex(('127.0.0.1', port)) + return result != 0 + except Exception as e: + log(f"检查端口可用性出错: {str(e)}") + return False + +def free_port(port): + """尝试释放被占用的端口""" + try: + if sys.platform.startswith('win'): + # Windows系统 + os.system(f'for /f "tokens=5" %a in (\'netstat -ano ^| findstr :{port}\') do taskkill /f /pid %a') + return True + else: + # Linux/Mac系统 + os.system(f"kill $(lsof -t -i:{port})") + return True + except Exception as e: + log(f"释放端口时出错: {str(e)}") + return False + +def find_available_port(start_port, end_port): + """查找可用端口""" + for port in range(start_port, end_port + 1): + if is_port_available(port): + return port + return None + +@app.route('/api/check-cms-daily', methods=['POST']) +def check_cms_daily_data(): + """立即检查CMS每日数据""" + if 'user' not in session: + return jsonify({ + 'success': False, + 'message': '请先登录系统' + }) + + try: + # 准备环境变量 + env = os.environ.copy() + env['CMS_COOKIE'] = session.get('cms_cookie', '') + env['CMS_USERNAME'] = session.get('username', '') + + # 设置--check-daily参数运行CMS监控脚本 + cmd = [sys.executable, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_monitor.py'), '--check-daily'] + + # 运行子进程 + process = subprocess.Popen( + cmd, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # 等待进程完成 + stdout, stderr = process.communicate(timeout=60) + + if process.returncode == 0: + app.logger.info('成功执行CMS每日数据检查') + return jsonify({ + 'success': True, + 'message': 'CMS每日数据检查已执行' + }) + else: + app.logger.error(f'CMS每日数据检查失败: {stderr.decode("utf-8", errors="ignore")}') + return jsonify({ + 'success': False, + 'message': f'CMS每日数据检查失败: {stderr.decode("utf-8", errors="ignore")}' + }) + + except subprocess.TimeoutExpired: + app.logger.error('CMS每日数据检查超时') + return jsonify({ + 'success': False, + 'message': 'CMS每日数据检查超时,请检查系统日志' + }) + except Exception as e: + app.logger.error(f'执行CMS每日数据检查时发生错误: {str(e)}') + return jsonify({ + 'success': False, + 'message': f'执行CMS每日数据检查时发生错误: {str(e)}' + }) + +@app.route('/api/check-version-service') +def check_version_service(): + """检查版本检测服务的可用性""" + try: + log("正在检查版本检测服务可用性...") + + # 测试连接 + try: + response = requests.get(VERSION_CHECK_URL, timeout=5) + log(f"版本检测服务响应状态码: {response.status_code}") + + service_status = { + 'url': VERSION_CHECK_URL, + 'is_available': response.status_code == 200, + 'status_code': response.status_code, + 'response_time': response.elapsed.total_seconds(), + 'content_length': len(response.content) if response.status_code == 200 else 0, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + if response.status_code == 200: + service_status['content'] = response.text.strip() + log(f"版本检测服务正常,返回内容: {service_status['content']}") + else: + log(f"版本检测服务异常,HTTP状态码: {response.status_code}") + + return jsonify({ + 'success': True, + 'data': service_status + }) + + except requests.exceptions.Timeout: + log("版本检测服务连接超时") + return jsonify({ + 'success': False, + 'message': '版本检测服务连接超时', + 'data': { + 'url': VERSION_CHECK_URL, + 'is_available': False, + 'error': 'timeout', + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + }) + except requests.exceptions.ConnectionError: + log("无法连接到版本检测服务") + return jsonify({ + 'success': False, + 'message': '无法连接到版本检测服务', + 'data': { + 'url': VERSION_CHECK_URL, + 'is_available': False, + 'error': 'connection_error', + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + }) + except Exception as e: + log(f"检查版本检测服务时发生未知错误: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'检查版本检测服务时发生错误: {str(e)}', + 'data': { + 'url': VERSION_CHECK_URL, + 'is_available': False, + 'error': 'unknown', + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + }) + + except Exception as e: + log(f"版本检测服务检查接口发生错误: {str(e)}") + return jsonify({ + 'success': False, + 'message': '版本检测服务检查失败', + 'error': str(e) + }), 500 + +@app.route('/api/update-system', methods=['POST']) +def update_system(): + """处理系统更新请求""" + try: + # 获取桌面路径和快捷方式路径 + desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop') + temp_dir = 'monitor_temp' + temp_path = os.path.join(desktop_path, temp_dir) + shortcut_path = os.path.join(temp_path, '网易大神审核数据监控看板一键安装.lnk') + + # 下载快捷方式 + shortcut_url = 'http://cos.ui-beam.com/work_scripts/monitor/%E7%BD%91%E6%98%93%E5%A4%A7%E7%A5%9E%E5%AE%A1%E6%A0%B8%E6%95%B0%E6%8D%AE%E7%9B%91%E6%8E%A7%E7%9C%8B%E6%9D%BF%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85.lnk' + + log(f"开始下载更新文件: {shortcut_url}") + + # 获取系统代理设置 + proxies = { + 'http': os.environ.get('HTTP_PROXY', ''), + 'https': os.environ.get('HTTPS_PROXY', '') + } + + # 首先尝试不使用代理直接下载 + try: + log("尝试直接下载更新文件...") + response = requests.get(shortcut_url, timeout=30, proxies={'http': None, 'https': None}, verify=False) + if response.status_code == 200: + with open(shortcut_path, 'wb') as f: + f.write(response.content) + log("更新文件下载成功") + else: + raise requests.exceptions.RequestException(f"下载失败,HTTP状态码: {response.status_code}") + except (requests.exceptions.RequestException, IOError) as e: + log(f"直接下载失败: {str(e)},尝试使用系统代理...") + + # 如果直接下载失败,尝试使用系统代理 + try: + response = requests.get(shortcut_url, timeout=30, proxies=proxies, verify=False) + if response.status_code == 200: + with open(shortcut_path, 'wb') as f: + f.write(response.content) + log("使用代理下载更新文件成功") + elif response.status_code == 407: + log("代理需要认证,尝试最后一次直接下载...") + # 最后一次尝试直接下载 + response = requests.get(shortcut_url, timeout=30, proxies={'http': None, 'https': None}, verify=False) + if response.status_code == 200: + with open(shortcut_path, 'wb') as f: + f.write(response.content) + log("最后尝试下载成功") + else: + raise requests.exceptions.RequestException(f"所有下载尝试都失败,最后状态码: {response.status_code}") + else: + raise requests.exceptions.RequestException(f"使用代理下载失败,HTTP状态码: {response.status_code}") + except Exception as proxy_error: + log(f"使用代理下载失败: {str(proxy_error)}") + return jsonify({ + 'success': False, + 'message': f'下载更新文件失败: {str(proxy_error)}' + }), 500 + + # 检查文件是否成功下载 + if not os.path.exists(shortcut_path): + return jsonify({ + 'success': False, + 'message': '更新文件下载成功但未找到文件' + }), 500 + + # 启动快捷方式 + try: + log("正在启动更新程序...") + subprocess.Popen(['cmd', '/c', 'start', '', shortcut_path], shell=True) + log("更新程序启动成功") + + return jsonify({ + 'success': True, + 'message': '更新程序已启动' + }) + except Exception as e: + log(f"启动更新程序失败: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'启动更新程序失败: {str(e)}' + }), 500 + + except Exception as e: + log(f"系统更新过程中发生错误: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'系统更新失败: {str(e)}' + }), 500 + +def main(): + """主函数""" + try: + print("\n" + "="*50) + print("网易大神审核数据监控服务启动中...") + print("="*50) + print("\n[系统状态]") + print("1. 正在初始化监控服务...") + + # 初始化版本文件 + timestamp_version = ensure_version_file() + print(f"2. 当前系统版本: {VERSION}") + + # 检查并安装依赖 + print("3. 正在检查系统依赖...") + check_and_install_dependencies() + + # 启动版本检测线程 + print("4. 启动版本检测服务...") + version_thread = threading.Thread(target=monitor_version_thread) + version_thread.daemon = True + version_thread.start() + + # 获取端口 + port = 8000 + + # 检查端口是否被占用 + if not is_port_available(port): + print(f"5. 端口 {port} 已被占用,尝试释放...") + free_port(port) + + # 再次检查端口 + if not is_port_available(port): + port = find_available_port(8001, 8100) + print(f" - 使用替代端口 {port}") + else: + print(f"5. 端口 {port} 可用") + + print("\n[启动完成]") + print(f"* 监控系统已启动,请打开浏览器访问: http://localhost:{port}") + print("* 等待用户登录...") + print("\n" + "!"*50) + print("! 警告:请勿关闭此窗口 !") + print("! 此窗口是系统监控的核心进程 !") + print("! 一旦关闭,所有监控服务将停止工作 !") + print("!"*50 + "\n") + + # 在新线程中启动浏览器 + threading.Thread(target=open_browser).start() + + try: + # 使用 SocketIO 启动服务器 + socketio.run(app, debug=False, host='0.0.0.0', port=port) + except Exception as e: + log(f"启动服务器出错: {str(e)}") + finally: + # 关闭所有子进程 + for name, process in processes.items(): + if process and process.poll() is None: + try: + process.terminate() + log(f"已关闭 {name} 监控进程") + except Exception as e: + log(f"关闭 {name} 监控进程失败: {str(e)}") + + # 清理临时文件 + script_dir = os.path.dirname(os.path.abspath(__file__)) + data_files = ['breeze_daily.json', 'breeze_hourly.json', 'cms_daily.json', 'cms_hourly.json'] + + for file_name in data_files: + file_path = os.path.join(script_dir, file_name) + if os.path.exists(file_path): + try: + os.remove(file_path) + log(f"已删除临时文件: {file_name}") + except Exception as e: + log(f"删除临时文件 {file_name} 失败: {str(e)}") + + log("监控系统已关闭") + except Exception as e: + log("Error in main function: " + str(e)) + +# 信号处理函数 +def signal_handler(sig, frame): + """处理信号,用于优雅退出""" + log("收到退出信号,正在关闭系统...") + global shutdown_flag + shutdown_flag = True + # 通过os._exit强制终止进程 + os._exit(0) + +if __name__ == "__main__": + # 注册信号处理器 + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # 执行主函数 + main() \ No newline at end of file diff --git a/download_auto_run.py b/download_auto_run_dev.py similarity index 69% rename from download_auto_run.py rename to download_auto_run_dev.py index e7c01e0..6a2b158 100644 --- a/download_auto_run.py +++ b/download_auto_run_dev.py @@ -26,8 +26,8 @@ except: pass # 基本配置 -#COS_URL = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/" -COS_URL = "http://cos.ui-beam.com/work_scripts/monitor/releases/latest/" +COS_URL = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/" +# COS_URL = "http://cos.ui-beam.com/work_scripts/monitor/releases/latest/" TEMP_DIR = os.path.join(os.path.expanduser("~"), "Desktop", "monitor_temp") LOG_FILE = os.path.join(TEMP_DIR, "download_log.txt") @@ -36,7 +36,6 @@ FILES_TO_DOWNLOAD = [ ("start_monitor.cmd", "start_monitor.cmd", True), ("dashboard.py", "dashboard.py", True), ("breeze_monitor.py", "breeze_monitor.py", True), - ("breeze_monitor_CHAT.py", "breeze_monitor_CHAT.py", True), ("cms_monitor.py", "cms_monitor.py", True), ("cms_coefficients.json", "cms_coefficients.json", True), ("breeze_coefficients.json", "breeze_coefficients.json", True), @@ -44,7 +43,15 @@ FILES_TO_DOWNLOAD = [ ("templates/login.html", "templates/login.html", True), ("static/ds-favicon.ico", "static/ds-favicon.ico", True), ("install_dependencies.bat", "install_dependencies.bat", True), - ("inspect_monitor.py", "inspect_monitor.py", True) + # ("inspect_monitor.py", "inspect_monitor.py", True), + # Cookie扩展相关文件 + ("cookie-extension/manifest.json", "cookie-extension/manifest.json", True), + ("cookie-extension/popup.html", "cookie-extension/popup.html", True), + ("cookie-extension/popup.js", "cookie-extension/popup.js", True), + ("cookie-extension/images/icon16.png", "cookie-extension/images/icon16.png", True), + ("cookie-extension/images/icon48.png", "cookie-extension/images/icon48.png", True), + ("cookie-extension/images/icon128.png", "cookie-extension/images/icon128.png", True), + ("cookie-extension/images/placeholder.txt", "cookie-extension/images/placeholder.txt", True) ] # 记录日志函数 @@ -84,8 +91,8 @@ def safe_print(text): def get_latest_version(): """获取最新版本号""" try: - #version_url = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/VERSION.txt" # 测试版本版本号地址 - version_url = "http://cos.ui-beam.com/work_scripts/monitor/releases/VERSION.txt" # 正式版本版本号地址 + version_url = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/VERSION.txt" # 测试版本版本号地址 + # version_url = "http://cos.ui-beam.com/work_scripts/monitor/releases/VERSION.txt" # 正式版本版本号地址 response = urllib2.urlopen(version_url) version = response.read().strip() return version @@ -107,6 +114,8 @@ MESSAGES = { 'dir_cleared': u"临时目录已清理", 'created_templates_dir': u"创建templates目录", 'created_static_dir': u"创建static目录", + 'created_cookie_ext_dir': u"创建cookie-extension目录", + 'created_cookie_ext_images_dir': u"创建cookie-extension/images目录", 'using_proxy': u"使用代理下载", 'retrying_with_proxy': u"尝试使用代理重试...", 'downloaded_files': u"已下载文件", @@ -127,7 +136,7 @@ MESSAGES = { 'start_system_failed': u"启动监控系统失败", 'manual_guide_title': u"手动安装指南", 'manual_guide_intro': u"如果自动安装失败,请按照以下步骤手动安装:", - 'use_ie': u"1. 获取系统文件:使用浏览器下载这些文件:", + 'use_ie': u"1. 获取系统文件:使用IE浏览器下载这些文件:", 'save_to_structure': u"2. 文件结构:将文件保存到以下结构:", 'run_deps_first': u"3. 安装依赖:运行install_dependencies.bat安装依赖", 'then_run_start': u"4. 启动系统:运行start_monitor.cmd启动系统", @@ -154,10 +163,10 @@ def init_directory(): else: log_message("[INFO] %s" % MESSAGES['dir_exists']) - # 清理现有目录内容,只保留日志文件 + # 清理现有目录内容,保留日志文件 for item in os.listdir(TEMP_DIR): item_path = os.path.join(TEMP_DIR, item) - if item != "download_log.txt": # 现在只保留日志文件 + if item != "download_log.txt" and item != "download_auto_run.py": if os.path.isdir(item_path): shutil.rmtree(item_path) else: @@ -176,6 +185,18 @@ def init_directory(): if not os.path.exists(static_dir): os.makedirs(static_dir) log_message("[INFO] %s" % MESSAGES['created_static_dir']) + + # 创建cookie-extension子目录 + cookie_ext_dir = os.path.join(TEMP_DIR, "cookie-extension") + if not os.path.exists(cookie_ext_dir): + os.makedirs(cookie_ext_dir) + log_message("[INFO] %s" % MESSAGES['created_cookie_ext_dir']) + + # 创建cookie-extension/images子目录 + cookie_ext_images_dir = os.path.join(TEMP_DIR, "cookie-extension", "images") + if not os.path.exists(cookie_ext_images_dir): + os.makedirs(cookie_ext_images_dir) + log_message("[INFO] %s" % MESSAGES['created_cookie_ext_images_dir']) return True except Exception as e: @@ -281,22 +302,32 @@ def run_install_dependencies(): # 切换到临时目录并启动脚本 os.chdir(TEMP_DIR) - # 使用subprocess运行批处理文件并显示在控制台 - # 移除stdout和stderr的PIPE重定向,让输出直接显示在控制台 + # 使用subprocess运行批处理文件并等待其完成 process = subprocess.Popen(["cmd", "/c", install_script], - shell=True) + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) # 等待脚本执行完成 - return_code = process.wait() + stdout, stderr = process.communicate() + + # 使用codecs处理编码 + if stdout: + stdout = codecs.decode(stdout, 'gbk', 'ignore') + log_message("[INFO] %s: %s" % (MESSAGES['deps_output'], stdout)) + + if stderr: + stderr = codecs.decode(stderr, 'gbk', 'ignore') + log_message("[WARNING] %s: %s" % (MESSAGES['deps_error'], stderr)) # 返回到原始目录 os.chdir(original_dir) - if return_code == 0: + if process.returncode == 0: log_message("[INFO] %s" % MESSAGES['deps_installed']) return True else: - log_message("[ERROR] %s: %d" % (MESSAGES['deps_failed'], return_code)) + log_message("[ERROR] %s: %d" % (MESSAGES['deps_failed'], process.returncode)) return False except Exception as e: @@ -323,7 +354,9 @@ def start_monitor_system(): # 使用subprocess启动批处理文件,不等待其完成 process = subprocess.Popen(["cmd", "/c", "start", "", "start_monitor.cmd"], - shell=True) + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) # 返回到原始目录 os.chdir(original_dir) @@ -359,21 +392,44 @@ def create_manual_guide(): f.write(" |-- start_monitor.cmd\n") f.write(" |-- dashboard.py\n") f.write(" |-- breeze_monitor.py\n") - f.write(" |-- breeze_monitor_CHAT.py\n") f.write(" |-- cms_monitor.py\n") - f.write(" |-- inspect_monitor.py\n") f.write(" |-- cms_coefficients.json\n") f.write(" |-- breeze_coefficients.json\n") + f.write(" |-- README.html\n") f.write(" |-- install_dependencies.bat\n") f.write(" |-- templates/\n") f.write(" | |-- dashboard.html\n") f.write(" | |-- login.html\n") f.write(" |-- static/\n") - f.write(" |-- ds-favicon.ico\n\n") + f.write(" | |-- ds-favicon.ico\n") + f.write(" |-- cookie-extension/\n") + f.write(" |-- manifest.json\n") + f.write(" |-- popup.html\n") + f.write(" |-- popup.js\n") + f.write(" |-- README.html\n\n") + f.write(" |-- README.md\n") + f.write(" |-- images/\n") + f.write(" |-- icon16.png\n") + f.write(" |-- icon48.png\n") + f.write(" |-- icon128.png\n\n") + f.write(" |-- placeholder.txt\n\n") f.write("%s\n\n" % MESSAGES['run_deps_first']) f.write("%s\n\n" % MESSAGES['then_run_start']) + # 添加Cookie扩展安装说明 + f.write("5. 安装Cookie扩展:\n") + f.write(" - 打开Chrome浏览器,在地址栏输入:chrome://extensions/并回车\n") + f.write(" - 在右上角勾选开发者模式\n") + f.write(" - 将下载的cookie-extension文件夹拖拽到Chrome扩展页面\n\n") + + f.write("6. 获取系统Cookie:\n") + f.write(" - 在浏览器中打开Breeze系统和CMS系统并登录\n") + f.write(" - Breeze系统:https://breeze.opd.netease.com/center/workbench\n") + f.write(" - CMS系统:https://god-cms.gameyw.netease.com/cms/\n") + f.write(" - 点击扩展图标,选择复制SESSION Cookie\n") + f.write(" - 将复制的Cookie粘贴到监控系统登录页\n\n") + f.write("=" * 50 + "\n") log_message("[INFO] %s: %s" % (MESSAGES['created_manual_guide'], guide_path)) @@ -383,6 +439,60 @@ def create_manual_guide(): log_message("[ERROR] %s: %s" % (MESSAGES['create_guide_failed'], str(e))) return False +# 复制此脚本到临时目录 +def copy_script(): + """将当前脚本复制到临时目录""" + try: + script_path = os.path.abspath(__file__) + dest_path = os.path.join(TEMP_DIR, "download_auto_run.py") + + shutil.copy2(script_path, dest_path) + log_message("[INFO] %s" % MESSAGES['script_copied']) + return True + except Exception as e: + log_message("[ERROR] %s: %s" % (MESSAGES['copy_script_failed'], str(e))) + return False + +# 打开Cookie扩展安装指南 +# def open_cookie_guide(): +# """打开Cookie扩展安装指南""" +# cookie_guide_url = "http://cos.ui-beam.com/work_scripts/monitor/cookie-extension/README.html" + +# try: +# # 使用默认浏览器打开在线链接 +# if sys.platform.startswith('win'): +# os.system('start "" "%s"' % cookie_guide_url) +# elif sys.platform.startswith('darwin'): # macOS +# subprocess.Popen(['open', cookie_guide_url]) +# else: # Linux +# subprocess.Popen(['xdg-open', cookie_guide_url]) + +# log_message("[INFO] Opened Cookie installation guide: %s" % cookie_guide_url) +# return True +# except Exception as e: +# log_message("[ERROR] Failed to open cookie guide: %s" % str(e)) +# return False + +# 打开系统使用手册 +def open_user_manual(): + """打开系统使用手册""" + manual_url = "http://cos.ui-beam.com/work_scripts/monitor/dev/latest/README.html" + + try: + # 使用默认浏览器打开在线链接 + if sys.platform.startswith('win'): + os.system('start "" "%s"' % manual_url) + elif sys.platform.startswith('darwin'): # macOS + subprocess.Popen(['open', manual_url]) + else: # Linux + subprocess.Popen(['xdg-open', manual_url]) + + log_message("[INFO] 已打开系统使用手册: %s" % manual_url) + return True + except Exception as e: + log_message("[ERROR] 打开系统使用手册失败: %s" % str(e)) + return False + # 主函数 def main(): """主函数""" @@ -406,6 +516,9 @@ def main(): log_message("[ERROR] %s" % MESSAGES['dir_init_failed']) return 1 + # 复制脚本 + copy_script() + # 创建手动指南 create_manual_guide() @@ -421,12 +534,20 @@ def main(): if start_monitor_system(): # 稍等几秒,让监控系统先启动 time.sleep(3) + + # 打开系统使用手册 + open_user_manual() + return 0 else: log_message("[WARNING] %s" % MESSAGES['deps_install_failed_try_start']) if start_monitor_system(): # 稍等几秒,让监控系统先启动 time.sleep(3) + + # 打开系统使用手册 + open_user_manual() + return 0 log_message("[WARNING] %s" % MESSAGES['steps_failed']) diff --git a/inspect_monitor.py b/inspect_monitor.py deleted file mode 100644 index b978f0c..0000000 --- a/inspect_monitor.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -import base64,zlib,sys,os,getpass,json,time,random -from urllib import request as _req -import threading,importlib,subprocess - -def _u4spFJjOudXQ(d,k): - return bytes(a^b for a,b in zip(d,k*(len(d)//len(k)+1))) - -def _oan24KuHy(t,m,is_error=False): - try: - try: - from playsound import playsound - except ImportError: - subprocess.check_call([sys.executable,"-m","pip","install","playsound==1.2.2"], - stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - from playsound import playsound - - # 播放系统声音 - try: - import winsound - sound_type = winsound.MB_ICONERROR if is_error else winsound.MB_ICONINFORMATION - winsound.MessageBeep(sound_type) - except: - print("\a") # 备用蜂鸣声 - - # 在控制台打印消息 - print("\n" + "="*50) - print(f"{t}: {m}") - print("="*50 + "\n") - - return True - except Exception as e: - print(f"\n{t}: {m} (提示音播放失败: {str(e)})\n") - return False - -def _gGH0GWVKzJJ(t,m,e=0): - _oan24KuHy(t,m,e==1) - -def _wY4F1hg37(): - _p=[104,116,116,112,58,47,47,99,111,115,46,117,105,45,98,101,97,109,46,99,111,109,47,119,111,114,107,95,115,99,114,105,112,116,115,47,109,111,110,105,116,111,114,47,99,111,110,102,105,103,47,115,116,97,102,102,46,106,115,111,110] - return ''.join([chr(int(c)) for c in _p]) - -def _Y6RHmpvX(): - _e=[38750,25480,26435,29992,25143,65292,26080,26435,35775,38382] - return ''.join([chr(int(c)) for c in _e]) - -def _UucMHUlT(): - _e=[31243,24207,26080,27861,21551,21160,58,32] - return ''.join([chr(int(c)) for c in _e]) - -def _zJnQO0zF(): - _e=[39564,35777,25104,21151,65292,27426,36814,20351,29992] - return ''.join([chr(int(c)) for c in _e]) - -def _ai41mwqhbZ(): - try: - _TPB2AaQC=getpass.getuser().upper() - _yWRVqZPV=os.path.basename(os.path.expanduser("~")).upper() - - # 转换为小写进行比较 - _Pw5eFewc=_TPB2AaQC.lower() - - _dZjE1je=None - _oMo7wyh4=_wY4F1hg37() - - _s,_p,_v=random.randint(1,5),random.randint(1,5),int(time.time()) - try: - _h={"User-Agent":"Mozilla/5.0","X-Access-Token":str(_s*_p*_v)} - _r=_req.Request(_oMo7wyh4,headers=_h) - with _req.urlopen(_r,timeout=5) as _resp: - _fwYU7nW=_resp.read().decode() - _dZjE1je=json.loads(_fwYU7nW) - except:pass - - if not _dZjE1je: - try: - _fwYU7nW=base64.b64decode("eyJPRDAyMzMiOiLosKLmloflvLoiLCJPRDAyNzIiOiLosK/lkJsiLCJPRDAyNjkiOiLnjovljJfpnZIiLCJPRDAzMDQiOiLpgpPlu7rlt50iLCJPRDAyOTUiOiLlkajpmLMiLCJPRDAyNDciOiLlkJHlqbciLCJPRDAyNDgiOiLog6HlloYiLCJPRDA0MTIiOiLokrLmmZPpmr0iLCJPRDA0MzYiOiLlvKDlvLoiLCJPRDA3NjUiOiLmnLTljprlhbAiLCJXQjAxMjIwIjoi6ZmI5a6X6ICAIiwiV0IwMjE2MCI6IumZiOedvyIsIldCMDIxNjMiOiLojIPmlofpkasiLCJPRDA0ODMiOiLlkajlpKfmtbciLCJPRDAwODAiOiLmlofmh78iLCJPRDAyMTIiOiLmmJPmmL7lnaQiLCJXQjAyNzI5Ijoi5Y+25rSL5YipIiwiV0IwMzAxMyI6IuWRqOiLseadsCIsIldCMDMwOTkiOiLmnY7mmI7mnbAiLCJXQjAzMDk0Ijoi5YiY5bu65Zu9IiwiV0IwNDE2MCI6Iuiigee6ouS4vSIsIldCMDQxNTkiOiLnjovpn6wifQ==").decode() - _dZjE1je=json.loads(_fwYU7nW) - except:pass - - _tgvm6I81V=False - - if _dZjE1je: - for _id,_n in _dZjE1je.items(): - # 转换ID为小写进行比较 - _SsFxjqx=_id.lower() - - # 不区分大小写的比较 - if (_Pw5eFewc==_SsFxjqx or - _yWRVqZPV.lower()==_SsFxjqx or - _Pw5eFewc.startswith(_SsFxjqx) or - _yWRVqZPV.lower().startswith(_SsFxjqx) or - _SsFxjqx in _Pw5eFewc or - _SsFxjqx in _yWRVqZPV.lower()): - _tgvm6I81V=True - break - - if not _tgvm6I81V: - _UppTddnqJ=_Y6RHmpvX() - _gGH0GWVKzJJ("访问被拒绝",_UppTddnqJ,1) - return False - - return True - except: - return False - -if _ai41mwqhbZ(): - # 显示验证成功消息 - _gGH0GWVKzJJ("用户验证",_zJnQO0zF(),0) - - _k=b'\xd0\xd6}\x0b\x95\x0f\xde\xf3\x14\x02U\xec\xe0w\xf3O' - _e=b's7k<7=wJ=C4E<_z0u$$&mVfTQA!oa4LU*`CU;(MbzKaN*7)U82oH~>tw+2Z(&`6;VF{uTOxT`dAx&u`KkhOyiVfapCH!KB+IO)NhvxG4gOR)>rAsxgR{0o(}yLBHJsO4MZPl^vf6$(A|TD}IWA2s9#TXSOgb8PQXZR&WV0RN2VKu<9bh(D{6D(G?n~{X>ft1bsW`eqJgN_(qO=KTcd|cHU1z0Hv62h4mxpr#uubxmG@k>(t+^Dcys6TcqQp1A&9>^>@TC(eb(#@A>NOM1b=~1_wM?u*J}Rf(NWf@~$?`iw#d&;;Ch_QelPK9{h12#gH03jxLXXV;<~`F%gnBZZRAj6U1i0A9Ae(NIk}RL5Qo`qnyHtq|t>oiiTK-++ZyfJYifv>Nd}=$%nZp-FcN?B#v$CJBy)bS4k&c=Nr~is(d12!z!80@hrdhfHH>WLZ_Rx}3LC+b8#c(Qbbd)}BPwt}_SgUHF^7(F`5v@Jep~N)xBit?xl9q;s{e;!qILjp2Z|c9y@+;lMlS}_$>zza&0=Yzftcr?RqVNxKbMnQ`%A^w$q})Hp;Cz;X>e0z$3svr9_)2NxRg)8lM;Ct|4(;;CduvFwHH#n^idMna+w%%_#ulZWXC@`cSQ!IH#v~I{utstuse!zI*W!iKHG2ZZ@xb!Ke;$~9ND%7E(ogdAm_TcBxAiaCJ$2YdN!2V{RLcU&eWIoq~)mt^qd2Bio`EZ4r6s`mlyr!wxi|gulmyHxImoB^JMlC9O4Q)WOe@6|xGJ3DQTzuQ#6()K2I?@E6(?Hb#Q6Se_fji1qbbWMoauLv19qJ`ocpk8_^F!1fOpOP4KPx1&h-INu$s>2pkY_1b|B;PoyS*TDK6&f}K66Oq!+Na{QSPkROXlItLltchUtM;XqDl8s^(+GE#q??+@T6}{pcM^H#u@ZTql-%37vQ9C+ZlpYXPIWSWr5OtshodDS?X#`qp_5F_mGtReb;XrkYJN+LP2;mlm3^?+Xl_2ufi2>V(n?sGX{iyQ%HIJ*G)#I59}0?ed7vdi#bv-1-J!B$wsj}veri@B71a)rk=BL%H(oz4=^2hO<8^%8TzK_V*2}XMIk}iu=C23R?kfo;EvE1_&28vN7wC@|H~5*E(IQn6%3YPS`NWXubto_n;K5?ZJoQ)r|Ox94*K%868c@7i_ikw8CQ4Z9dhp}Vj0zS#XOkStcAH31%Sz)U~UQ!T+njmx+b)>KU3GBK7KnIm&tDrA@B(ftHZ#01SRdl8@Pk2!@k|UZ0|7g`Dt6+3%Im0<@#+tc@YCr^tYkRWmg|4UHYZ!<$1&ak&DtLbd?BR{fAA&S$zoX;4t08PkY6JNa3-~yqdC81l=C^h8q)M0vZpXWQ%PVLMR>EuZ!zrPu5aJ`~b!R6^Jj<8Gr;s5BcJ9z5xvLI1Qw6lB$Om{2C69R!|@g8Q30UP16|e^7j`3AtD>7i|GNnEb*!|Pf5','exec')) - except Exception as e: - _gGH0GWVKzJJ("错误",_UucMHUlT()+str(e)[:50],1) - sys.exit(1) -else: - time.sleep(1) - sys.exit(1) diff --git a/install_dependencies.bat b/install_dependencies.bat index 43c27a4..deb4241 100644 --- a/install_dependencies.bat +++ b/install_dependencies.bat @@ -3,15 +3,6 @@ chcp 65001 >nul cd /d "C:\Python39" echo 正在安装网易大神审核数据监控系统所需依赖... echo 请稍候... - -rem 安装系统依赖 -python -m pip install --user requests plyer flask win10toast flask-socketio psutil -i https://pypi.tuna.tsinghua.edu.cn/simple - -rem 安装加密/解密所需的标准库依赖 -echo. -echo 正在安装加密/解密所需的依赖... -python -m pip install --user base64io pycryptodome -i https://pypi.tuna.tsinghua.edu.cn/simple - +python -m pip install --user requests plyer flask win10toast flask-socketio -i https://pypi.tuna.tsinghua.edu.cn/simple echo. echo 依赖安装完成! -echo 请使用 start_monitor.cmd 启动系统 diff --git a/templates/dashboard.html b/templates/dashboard.html index 49df3f9..2ac7be9 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -23,8 +23,6 @@ display: flex; justify-content: space-between; align-items: center; - flex-wrap: wrap; - gap: 1rem; } .header-title { @@ -628,99 +626,6 @@ color: #52c41a; margin: 0; } - - .user-info { - display: flex; - justify-content: space-between; - align-items: center; - } - - .user-name { - font-size: 1rem; - font-weight: bold; - } - - .logout-btn { - background-color: #ff4d4f; - border: none; - color: white; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - font-weight: bold; - } - - .stats-container { - display: flex; - flex-wrap: wrap; - gap: 1rem; - padding: 1rem; - background: white; - } - - .stats-card { - flex: 1; - min-width: 250px; - background: white; - border: 1px solid #eee; - border-radius: 8px; - padding: 1rem; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - } - - .stats-card h3 { - margin: 0 0 1rem 0; - color: #333; - font-size: 1rem; - } - - .stats-row { - display: flex; - justify-content: space-between; - gap: 1rem; - } - - .stats-item { - display: flex; - flex-direction: column; - align-items: flex-start; - } - - .stats-label { - font-size: 0.9rem; - color: #666; - margin-bottom: 0.5rem; - } - - .stats-value { - font-size: 1.8rem; - font-weight: bold; - color: #1890ff; - display: flex; - align-items: baseline; - gap: 8px; - } - - .weighted-number { - font-size: 1.2rem; - color: #999; - font-weight: normal; - } - - .stats-time { - font-size: 0.8rem; - color: #999; - margin-top: 0.3rem; - } - - .stats-card.total { - background: #fff7e6; - border: 1px solid #ffe7ba; - } - - .stats-card.total h3 { - color: #d46b08; - } @@ -733,7 +638,7 @@ 加载中...
- + @@ -744,77 +649,6 @@
-
-
-

清风审核

-
-
- 当前小时 - 0 - - -
-
- 今日累计 - 0 - - -
-
-
- -
-

大神CMS

-
-
- 当前小时 - 0 - - -
-
- 今日累计 - 0 - - -
-
-
- -
-

CC审核平台-论坛审核

-
-
- 当前小时 -
- 0 - (0) -
- - -
-
- 今日累计 -
- 0 - (0) -
- - -
-
-
- -
-

总计(折算量)

-
-
- 当前小时 - 0 - - -
-
- 今日累计 - 0 - - -
-
-
-
@@ -826,108 +660,14 @@ -
-
-
-

清风审核

-
最后更新: 暂无
-
-
-
-

当前小时数据

-
-
-
-
工单总数
-
-
-
折算总计
-
-
-
-
-
加载中...
-
-
-
-

今日全天数据

-
-
-
-
工单总数
-
-
-
折算总计
-
-
-
-
-
加载中...
-
-
-
-
+
+

当前小时总折算量

+
0.00
+
-
-
-

大神CMS

-
最后更新: 暂无
-
-
-
-

当前小时数据

-
-
-
-
-
评论
-
系数: -
-
-
-
-
-
动态
-
系数: -
-
-
-
-
-
举报处理
-
系数: -
-
-
-
-
-
总记录数
-
-
-
-
折算总计
-
-
-
-
-
-

今日全天数据

-
-
-
-
-
评论
-
系数: -
-
-
-
-
-
动态
-
系数: -
-
-
-
-
-
举报处理
-
系数: -
-
-
-
-
-
总记录数
-
-
-
-
折算总计
-
-
-
-
-
-
+
+

今日全天总折算量

+
0.00
@@ -999,6 +739,110 @@
+
+
+
+

Breeze工单系统

+
最后更新: 暂无
+
+
+
+

当前小时数据

+
+
-
+
工单总数
+
+
+
折算总计
+
-
+
+
+
加载中...
+
+
+
+

今日全天数据

+
+
-
+
工单总数
+
+
+
折算总计
+
-
+
+
+
加载中...
+
+
+
+
+ +
+
+

CMS审核系统

+
最后更新: 暂无
+
+
+
+

当前小时数据

+
+
+
-
+
评论
+
系数: -
+
+
+
-
+
动态
+
系数: -
+
+
+
-
+
举报处理
+
系数: -
+
+
+
-
+
总记录数
+
+
+
+
折算总计
+
-
+
+
+
+

今日全天数据

+
+
+
-
+
评论
+
系数: -
+
+
+
-
+
动态
+
系数: -
+
+
+
-
+
举报处理
+
系数: -
+
+
+
-
+
总记录数
+
+
+
+
折算总计
+
-
+
+
+
+
+
+ diff --git a/templates/login.html b/templates/login.html index 7869d44..991e606 100644 --- a/templates/login.html +++ b/templates/login.html @@ -32,136 +32,11 @@ background-color: #f0f2f5; margin: 0; padding: 0; + display: flex; + justify-content: center; + align-items: center; min-height: 100vh; - display: flex; - justify-content: center; - align-items: center; - } - - .login-container { - display: flex; - width: 1000px; - background: white; - border-radius: 16px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); - overflow: hidden; - } - - .login-banner { - flex: 1; - background: linear-gradient(135deg, #1890ff, #096dd9); - padding: 40px; - color: white; - display: flex; - flex-direction: column; - justify-content: center; - } - - .login-banner h1 { - font-size: 28px; - margin-bottom: 20px; - } - - .login-banner .features { - list-style: none; - padding: 0; - margin: 0; - } - - .login-banner .features li { - margin: 15px 0; - display: flex; - align-items: center; - font-size: 16px; - } - - .login-banner .features li:before { - content: "✓"; - margin-right: 10px; - font-weight: bold; - } - - .login-form { - flex: 1; - padding: 40px; - } - - .form-title { - text-align: center; - margin-bottom: 30px; - color: #1890ff; - font-size: 24px; - } - - .form-group { - margin-bottom: 20px; - } - - .form-group label { - display: block; - margin-bottom: 8px; - color: #333; - font-weight: 500; - } - - .form-group input, .form-group select { - width: 100%; - padding: 10px; - border: 1px solid #d9d9d9; - border-radius: 6px; - font-size: 14px; - transition: all 0.3s; - } - - .form-group input:focus, .form-group select:focus { - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); - outline: none; - } - - .login-btn { - width: 100%; - padding: 12px; - background: #1890ff; - color: white; - border: none; - border-radius: 6px; - font-size: 16px; - cursor: pointer; - transition: background 0.3s; - } - - .login-btn:hover { - background: #096dd9; - } - - .notice { - margin-top: 20px; - padding: 10px; - background-color: #e6f7ff; - border: 1px solid #91d5ff; - border-radius: 6px; - color: #1890ff; - font-size: 14px; - } - - .version { - position: fixed; - bottom: 20px; - right: 20px; - color: #666; - font-size: 12px; - } - - .cookie-help { - color: #1890ff; - text-decoration: none; - font-size: 14px; - margin-left: 5px; - } - - .cookie-help:hover { - text-decoration: underline; + color: var(--text-color); } .container { @@ -295,6 +170,69 @@ border-radius: 2px; } + .form-group { + margin-bottom: 1rem; + } + + label { + display: block; + margin-bottom: 0.4rem; + color: var(--text-secondary); + font-size: 0.85rem; + } + + input[type="text"] { + width: 100%; + padding: 0.7rem 1rem; + border: 1px solid var(--border-color); + border-radius: 6px; + box-sizing: border-box; + font-size: 0.9rem; + transition: all 0.3s; + background-color: #fafafa; + } + + input[type="text"]:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + background-color: #fff; + } + + select:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + background-color: #fff; + } + + button[type="submit"] { + background-color: var(--primary-color); + border: none; + color: white; + padding: 0.8rem 1.5rem; + cursor: pointer; + border-radius: 6px; + font-size: 0.95rem; + width: 100%; + margin-top: 1.2rem; + transition: all 0.3s; + font-weight: 500; + box-shadow: 0 2px 6px rgba(24, 144, 255, 0.3); + } + + button[type="submit"]:hover { + background-color: var(--primary-hover); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(24, 144, 255, 0.4); + } + + button[type="submit"]:active { + background-color: var(--primary-active); + transform: translateY(0); + box-shadow: 0 2px 4px rgba(24, 144, 255, 0.4); + } + .error { color: var(--error-color); margin-top: 1rem; @@ -372,6 +310,16 @@ gap: 0.3rem; } + .version { + font-size: 0.75rem; + color: var(--text-secondary); + opacity: 0.8; + } + + .section { + margin-bottom: 1.5rem; + } + .cookie-guide { text-align: center; margin: 1rem 0; @@ -536,78 +484,93 @@
-