mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-05-18 15:09:29 +00:00
fix SiteStatistic
This commit is contained in:
@@ -3,11 +3,12 @@
|
||||
"name": "站点数据统计",
|
||||
"description": "站点统计数据图表。",
|
||||
"labels": "站点,仪表板",
|
||||
"version": "1.6",
|
||||
"version": "1.7",
|
||||
"icon": "statistic.png",
|
||||
"author": "lightolly,jxxghp",
|
||||
"level": 2,
|
||||
"history": {
|
||||
"v1.7": "优化内存占用",
|
||||
"v1.6": "优化了站点数据获取失败时的回退逻辑",
|
||||
"v1.5": "修复了发送增量通知失败等一些问题",
|
||||
"v1.4.1": "支持数据刷新时发送消息通知",
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import gc
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
from typing import Optional, Any, List, Dict, Tuple
|
||||
|
||||
import pytz
|
||||
from app.helper.sites import SitesHelper
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from app import schemas
|
||||
@@ -14,6 +13,7 @@ from app.core.config import settings
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.db.models.siteuserdata import SiteUserData
|
||||
from app.db.site_oper import SiteOper
|
||||
from app.helper.sites import SitesHelper
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.schemas.types import EventType, NotificationType
|
||||
@@ -32,7 +32,7 @@ class SiteStatistic(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "statistic.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.6"
|
||||
plugin_version = "1.7"
|
||||
# 插件作者
|
||||
plugin_author = "lightolly,jxxghp"
|
||||
# 作者主页
|
||||
@@ -263,63 +263,50 @@ class SiteStatistic(_PluginBase):
|
||||
获取最近一次统计的日期、最近一次统计的站点数据、上一次的站点数据
|
||||
如果上一次某个站点数据缺失,则 fallback 到该站点之前最近有数据的日期
|
||||
"""
|
||||
# 获取所有原始数据
|
||||
raw_data_list: List[SiteUserData] = SiteOper().get_userdata()
|
||||
# 优化:只获取最近的站点数据,而不是所有历史数据
|
||||
raw_data_list: List[SiteUserData] = SiteOper().get_userdata_latest()
|
||||
if not raw_data_list:
|
||||
return "", [], []
|
||||
|
||||
# 每个日期、每个站点只保留最后一条数据
|
||||
data_list = list({f"{data.updated_day}_{data.name}": data for data in raw_data_list}.values())
|
||||
|
||||
# 按日期倒序排序
|
||||
data_list.sort(key=lambda x: x.updated_day, reverse=True)
|
||||
|
||||
# 按日期分组数据
|
||||
data_by_day = defaultdict(list)
|
||||
for data in data_list:
|
||||
data_by_day[data.updated_day].append(data)
|
||||
|
||||
# 获取最近一次统计的日期
|
||||
latest_day = data_list[0].updated_day
|
||||
latest_day = raw_data_list[0].updated_day
|
||||
|
||||
# 最近一次统计数据按上传量降序排序
|
||||
latest_data = [data for data in raw_data_list if data.updated_day == latest_day]
|
||||
latest_data.sort(key=lambda x: x.upload or 0, reverse=True)
|
||||
|
||||
# 筛选最近一次统计的数据(可能为空)
|
||||
latest_data = [data for data in data_list if data.updated_day == latest_day]
|
||||
# 最近一次统计按上传量降序排序
|
||||
latest_data.sort(key=lambda x: x.upload, reverse=True)
|
||||
|
||||
# 获取所有日期倒序排序后的列表
|
||||
sorted_dates = sorted(data_by_day.keys(), reverse=True)
|
||||
|
||||
# 计算前一天的日期字符串(相对于最近一次日期)
|
||||
# 计算前一天的日期字符串
|
||||
previous_day_str = (datetime.strptime(latest_day, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
# 获取前一天的站点数据
|
||||
previous_day_sites = data_by_day.get(previous_day_str, [])
|
||||
# 构建前一天站点到数据的映射
|
||||
previous_by_site = {data.name: data for data in previous_day_sites}
|
||||
|
||||
# 获取前一天的数据
|
||||
previous_data_list = SiteOper().get_userdata_by_date(previous_day_str)
|
||||
previous_by_site = {data.name: data for data in previous_data_list}
|
||||
|
||||
# 准备查找早于前一天的日期列表,用于 fallback
|
||||
fallback_dates = [d for d in sorted_dates if d < previous_day_str]
|
||||
|
||||
# 按站点细化进行上一次数据的 fallback 处理
|
||||
# 为当前站点查找对应的前一天数据
|
||||
previous_data = []
|
||||
for current_site in latest_data:
|
||||
site_name = current_site.name
|
||||
# 优先尝试获取前一天的同一站点数据
|
||||
site_prev = previous_by_site.get(site_name)
|
||||
|
||||
# 如果前一天没有该站点的数据,则进行逐日回退查找
|
||||
if site_prev is None or site_prev.err_msg:
|
||||
for d in fallback_dates:
|
||||
# 在每个候选日期中查找对应站点数据
|
||||
candidate = next((x for x in data_by_day[d] if x.name == site_name), None)
|
||||
if candidate:
|
||||
|
||||
# 如果前一天没有该站点数据,尝试查找更早的数据
|
||||
if not site_prev or site_prev.err_msg:
|
||||
# 最多回溯7天,避免查询过多历史数据
|
||||
for i in range(2, 8):
|
||||
fallback_date = (datetime.strptime(latest_day, "%Y-%m-%d") - timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
fallback_data_list = SiteOper().get_userdata_by_date(fallback_date)
|
||||
fallback_by_site = {data.name: data for data in fallback_data_list}
|
||||
candidate = fallback_by_site.get(site_name)
|
||||
if candidate and not candidate.err_msg:
|
||||
site_prev = candidate
|
||||
break
|
||||
|
||||
# 如果找到了上一次的数据,加入结果列表
|
||||
if site_prev:
|
||||
previous_data.append(site_prev)
|
||||
|
||||
# 清理临时变量,帮助垃圾收集
|
||||
del raw_data_list, previous_data_list, previous_by_site
|
||||
gc.collect()
|
||||
|
||||
return latest_day, latest_data, previous_data
|
||||
|
||||
@staticmethod
|
||||
@@ -846,152 +833,80 @@ class SiteStatistic(_PluginBase):
|
||||
dashboard='all'
|
||||
)
|
||||
|
||||
# 站点数据明细
|
||||
site_trs = [
|
||||
{
|
||||
'component': 'tr',
|
||||
'props': {
|
||||
'class': 'text-sm'
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'td',
|
||||
'props': {
|
||||
'class': 'whitespace-nowrap break-keep text-high-emphasis'
|
||||
},
|
||||
'text': data.name
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'text': data.username
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'text': data.user_level
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'props': {
|
||||
'class': 'text-success'
|
||||
},
|
||||
'text': StringUtils.str_filesize(data.upload)
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'props': {
|
||||
'class': 'text-error'
|
||||
},
|
||||
'text': StringUtils.str_filesize(data.download)
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'text': data.ratio
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'text': format_bonus(data.bonus or 0)
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'text': data.seeding
|
||||
},
|
||||
{
|
||||
'component': 'td',
|
||||
'text': StringUtils.str_filesize(data.seeding_size)
|
||||
}
|
||||
]
|
||||
} for data in stattistic_data
|
||||
# 优化:使用更轻量级的方式构建站点数据明细,避免创建过多嵌套对象
|
||||
# 先准备表头
|
||||
table_headers = [
|
||||
{'text': '站点', 'class': 'text-start ps-4'},
|
||||
{'text': '用户名', 'class': 'text-start ps-4'},
|
||||
{'text': '用户等级', 'class': 'text-start ps-4'},
|
||||
{'text': '上传量', 'class': 'text-start ps-4'},
|
||||
{'text': '下载量', 'class': 'text-start ps-4'},
|
||||
{'text': '分享率', 'class': 'text-start ps-4'},
|
||||
{'text': '魔力值', 'class': 'text-start ps-4'},
|
||||
{'text': '做种数', 'class': 'text-start ps-4'},
|
||||
{'text': '做种体积', 'class': 'text-start ps-4'}
|
||||
]
|
||||
|
||||
# 构建表头行
|
||||
header_row = {
|
||||
'component': 'thead',
|
||||
'content': [
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {'class': header['class']},
|
||||
'text': header['text']
|
||||
} for header in table_headers
|
||||
]
|
||||
}
|
||||
|
||||
# 构建数据行,避免在列表推导式中创建复杂嵌套
|
||||
table_rows = []
|
||||
for data in stattistic_data:
|
||||
# 预先计算所有需要的值
|
||||
row_data = [
|
||||
{'text': data.name, 'class': 'whitespace-nowrap break-keep text-high-emphasis'},
|
||||
{'text': data.username, 'class': ''},
|
||||
{'text': data.user_level, 'class': ''},
|
||||
{'text': StringUtils.str_filesize(data.upload), 'class': 'text-success'},
|
||||
{'text': StringUtils.str_filesize(data.download), 'class': 'text-error'},
|
||||
{'text': data.ratio, 'class': ''},
|
||||
{'text': format_bonus(data.bonus or 0), 'class': ''},
|
||||
{'text': data.seeding, 'class': ''},
|
||||
{'text': StringUtils.str_filesize(data.seeding_size), 'class': ''}
|
||||
]
|
||||
|
||||
# 构建单行配置
|
||||
row_content = []
|
||||
for cell_data in row_data:
|
||||
cell = {'component': 'td', 'text': cell_data['text']}
|
||||
if cell_data['class']:
|
||||
cell['props'] = {'class': cell_data['class']}
|
||||
row_content.append(cell)
|
||||
|
||||
table_rows.append({
|
||||
'component': 'tr',
|
||||
'props': {'class': 'text-sm'},
|
||||
'content': row_content
|
||||
})
|
||||
|
||||
# 拼装页面
|
||||
return [
|
||||
page = [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': site_totals + [
|
||||
# 各站点数据明细
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'props': {'cols': 12},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTable',
|
||||
'props': {
|
||||
'hover': True
|
||||
},
|
||||
'props': {'hover': True},
|
||||
'content': [
|
||||
{
|
||||
'component': 'thead',
|
||||
'content': [
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '站点'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '用户名'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '用户等级'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '上传量'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '下载量'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '分享率'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '魔力值'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '做种数'
|
||||
},
|
||||
{
|
||||
'component': 'th',
|
||||
'props': {
|
||||
'class': 'text-start ps-4'
|
||||
},
|
||||
'text': '做种体积'
|
||||
}
|
||||
]
|
||||
},
|
||||
header_row,
|
||||
{
|
||||
'component': 'tbody',
|
||||
'content': site_trs
|
||||
'content': table_rows
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1001,6 +916,11 @@ class SiteStatistic(_PluginBase):
|
||||
}
|
||||
]
|
||||
|
||||
# 清理垃圾
|
||||
gc.collect()
|
||||
|
||||
return page
|
||||
|
||||
def stop_service(self):
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user