commit 03e2d4f092bb7ddc8552e2b4e36786b2ac77eb65 Author: Jeffreytsai1004 Date: Fri Apr 18 01:20:11 2025 +0800 Update diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d381cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0195602 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# CGNICO 任务报告系统 + +这是一个用于跟踪和报告工具使用情况的系统,专为群晖DSM的Webstation环境优化。 + +## 系统功能 + +- 记录工具使用数据(用户名、工具名称、任务名称、节省时间、花费时间) +- 提供数据可视化界面,展示工具使用统计和趋势 +- 支持数据筛选、排序和导出 +- 提供API接口,方便其他应用程序提交数据 +- 支持定期生成报告 + +## 技术栈 + +- PHP 7.4+ +- MariaDB 10 +- HTML/CSS/JavaScript +- Chart.js (用于数据可视化) + +## 文件结构 + +- `index.php` - 主页和API处理 +- `query_task_data.php` - 数据查询和可视化界面 +- `api.php` - 独立的API接口 +- `client_example.php` - API使用示例 +- `insert_test_data.php` - 插入测试数据的工具 +- `check_data.php` - 检查数据库记录的工具 +- `generate_report.php` - 定期报告生成工具 +- `static/` - 静态资源文件夹 + +## 部署指南 + +### 系统要求 + +- 群晖DSM系统 +- Webstation已安装(使用Apache HTTP Server 2.4) +- PHP 7.4+ +- MariaDB 10 + +### 部署步骤 + +1. 将所有文件复制到群晖DSM的Web目录:`/volume1/web/task-reporter` + +2. 设置正确的文件权限: + + ```bash + chmod -R 755 /volume1/web/task-reporter + chown -R http:http /volume1/web/task-reporter + ``` + +3. 在MariaDB中创建数据库和用户: + + ```sql + CREATE DATABASE task_reporter; + CREATE USER 'task_reporter'@'%' IDENTIFIED BY 'Pass12349ers!'; + GRANT ALL PRIVILEGES ON task_reporter.* TO 'task_reporter'@'%'; + FLUSH PRIVILEGES; + ``` + +4. 在Webstation中配置网站: + - 虚拟主机名称:toolstrack.cgnico.com + - 端口:9000 + - 文档根目录:/volume1/web/task-reporter + - 启用PHP + - Web服务器:Apache HTTP Server 2.4 + +### 访问系统 + +- 本地访问:[http://192.168.2.4:9000](http://192.168.2.4:9000) +- 域名访问:[https://toolstrack.cgnico.com](https://toolstrack.cgnico.com) + +## API使用 + +系统提供了简单的API接口,用于提交任务数据: + +```bash +POST [https://toolstrack.cgnico.com/api.php](https://toolstrack.cgnico.com/api.php) +``` + +请求体示例(JSON格式): + +```json +{ + "username": "用户名", + "tool_name": "工具名称", + "task_name": "任务名称", + "time_saved": "1.5", + "time_cost": "0.2", + "timestamp": "2025-04-13 12:30:00" // 可选 +} +``` + +成功响应示例: + +```json +{ + "success": true, + "message": "2025-04-13 12:30:00 工具名称-任务名称 reported successfully!", + "id": 123 +} +``` + +详细的API使用示例请参考 `client_example.php` 文件。 + +## 定期报告功能 + +系统支持定期生成工具使用报告: + +### 功能特点 + +- 自动生成全面的工具使用报告 +- 支持多种时间范围(周报、月报、季报、年报) +- 包含总体统计、工具排名、用户排名和每日趋势 +- 自动保存报告历史记录 +- 支持在线查看和下载报告 + +### 使用方法 + +1. **手动生成报告**: + 访问 [https://toolstrack.cgnico.com/generate_report.php](https://toolstrack.cgnico.com/generate_report.php) 页面,选择报告时间范围,点击"生成报告"按钮。 + +2. **设置定期执行**: + 在群晖DSM的控制面板中,找到"任务计划",添加新的计划任务: + + ```bash + # 每周日凌晨生成周报 + 0 0 * * 7 php /volume1/web/task-reporter/generate_report.php + + # 或每月1日生成月报 + 0 0 1 * * php /volume1/web/task-reporter/generate_report.php + ``` + +3. **查看历史报告**: + 在生成报告页面下方可以看到历史报告列表,点击"查看"或"下载"按钮访问历史报告。 + +所有生成的报告都会保存在 `reports` 目录下,以日期命名,方便随时查看历史报告。也可以在主页上通过"查看报告"按钮直接访问最新报告。 + +## 联系方式 + +如有问题,请联系:[jeffreytsai1004@gmail.com](mailto:jeffreytsai1004@gmail.com) diff --git a/TaskReporter.py b/TaskReporter.py new file mode 100644 index 0000000..d1b14a1 --- /dev/null +++ b/TaskReporter.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +TaskReporter - Task Reporting Tool + +This script is designed to record and track user task execution. It sends task information +(including username, tool name, task name, time saved, time cost) to a specified server for statistics. + +Usage: + + #---------------------------------- usage 1: decorator ---------------------------------- + from TaskReporter import task_reporter_decorator + + debug_mode = True + + @task_reporter_decorator(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5) + def your_function(): + # your code + + #---------------------------------- usage 2: function ---------------------------------- + from TaskReporter import task_reporter + + debug_mode = True + + def your_function(): + start_time = time.time() + # your function code + task_reporter(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5, time_cost=round(time.time()-start_time, 3)) + +""" + +import os, sys +from functools import wraps +import time, datetime, json + +# add lib path to import requests +py_version = sys.version_info.major +minor_version = sys.version_info.minor +version = "py{}{}".format(py_version, minor_version) if py_version in [2,3] and minor_version<=7 else "py38+" +lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "reporter_lib", version) +if lib_path not in sys.path: + sys.path.append(lib_path) + +import requests +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry + + +class TaskReporter: + def __init__(self): + self.session = requests.Session() + self.session.mount('http://', HTTPAdapter(max_retries=3)) + self.session.mount('https://', HTTPAdapter(max_retries=3)) + self.server_status = {'available': True, 'last_check': 0} + self.SERVER_URL = 'https://toolstrack.cgnico.com' + self.cache_file = os.path.join(os.environ.get('APPDATA'), 'task_cache.json') + + def report_task(self, data, is_cached_task=False): + # first check and send cached data + if not is_cached_task: + self._try_send_cached_tasks() + + task_has_cached = False + + current_time = time.time() + if not self.server_status['available'] and not is_cached_task: + if current_time - self.server_status['last_check'] < 10: + self._cache_task(data) + return False + + try: + response = self.session.post( + '{}/api.php'.format(self.SERVER_URL), + json=data, + timeout=1 + ) + self.server_status['available'] = True + print(response.json()) + return response.json() + except (requests.exceptions.RequestException, TimeoutError) as e: + self.server_status['available'] = False + self.server_status['last_check'] = current_time + if not task_has_cached and not is_cached_task: self._cache_task(data) + task_has_cached = True + print("Failed to connect to server, task has been cached\n{}".format(str(e))) + # print("Failed to connect to server: {}".format(str(e))) + return False + + def _cache_task(self, data): + try: + data['timestamp'] = (datetime.datetime.utcnow() + datetime.timedelta(hours=8)).isoformat() + + cache = self._read_cache() + cache.append(data) + + with open(self.cache_file, 'w', encoding='utf-8') as f: + json.dump(cache, f, ensure_ascii=False, indent=2) + + # print("Task cached: {}".format(self.cache_file)) + except Exception as e: + print("Cache task failed:{}".format(str(e))) + + def _read_cache(self): + try: + if os.path.exists(self.cache_file): + with open(self.cache_file, 'r', encoding='utf-8') as f: + return json.load(f) + return [] + except Exception as e: + print("Read cache failed: {}".format(str(e))) + return [] + + def _try_send_cached_tasks(self): + if not os.path.exists(self.cache_file): + return + + cache = self._read_cache() + if not cache: + return + + # print("Find {} cached tasks, try to send again...".format(len(cache))) + if not self.server_status['available']: + return + + successful_indices = [] + for i, task in enumerate(cache): + try: + response = self.report_task(task, is_cached_task=True) + if response != False: + successful_indices.append(i) + print("Cached task sent successfully: {}".format(task.get('task_name'))) + else: + # print("Cached task sent failed") + break + except Exception as e: + print("Send cached task failed: {}".format(str(e))) + break + + if successful_indices: + new_cache = [task for i, task in enumerate(cache) if i not in successful_indices] + if new_cache: + with open(self.cache_file, 'w', encoding='utf-8') as f: + json.dump(new_cache, f, ensure_ascii=False, indent=2) + print("{} tasks left to send".format(len(new_cache))) + else: + os.remove(self.cache_file) + print("All cached tasks sent") + +reporter = TaskReporter() + +def task_reporter_decorator(tool_name, task_name, debug=False, time_saved=0): + """ + task reporter decorator + :param tool_name: name of the tool, eg "MetaBox" + :param task_name: name of the task, eg "CheckUV" + :param debug: set to True when develop, and set it to False when release the tool to artists + :param time_saved: time saved for this task, usually should communicate with artists to get a reasonable value + """ + # reporter = TaskReporter() + def run_and_report(func): + @wraps(func) + def execute_task(*args, **kwargs): + # record start time + start_time = time.time() + + # execute original function + try: + result = func(*args, **kwargs) + return result + except Exception as e: + raise e + finally: + # report task - 无论函数执行成功与否都发送报告 + success = result != False + data= { + 'username': os.environ.get('USERNAME'), + 'tool_name': tool_name + "(Debug)" if debug else tool_name, + 'task_name': task_name, + 'time_saved': str(time_saved if success else 0), # 如果失败,节省时间为0 + 'time_cost': str(round(time.time() - start_time, 3)), # 确保是字符串格式 + 'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 使用文档中指定的时间格式 + 'status': "success" if success else "failed" # 添加状态字段 + } + reporter.report_task(data) + + return execute_task + return run_and_report + +def task_reporter(tool_name, task_name, debug=False, time_saved=0, time_cost=0): + ''' + task reporter function + :param tool_name: name of the tool, eg "MetaBox" + :param task_name: name of the task, eg "CheckUV" + :param debug: set to True when develop, and set it to False when release the tool to artists + :param time_saved: time saved for this task, usually should communicate with artists to get a reasonable value + :param time_cost: time cost for this task, you can get it from + time_cost = round(time.time() - start_time, 3) + ''' + # reporter = TaskReporter() + data = { + 'username': os.environ.get('USERNAME'), + 'tool_name': tool_name + "(Debug)" if debug else tool_name, + 'task_name': task_name, + 'time_saved': str(time_saved), # 确保是字符串格式 + 'time_cost': str(round(time_cost, 3)), # 确保是字符串格式 + 'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 使用文档中指定的时间格式 + } + reporter.report_task(data) + + +if __name__ == '__main__': + #---------------------------------- usage 1: decorator ---------------------------------- + debug_mode = True + @task_reporter_decorator(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5) + def check_uv_dec(): + print("Check UV, and report by decorator") + + #---------------------------------- usage 2: function ---------------------------------- + def check_uv_func(): + debug_mode = True + start_time = time.time() + + # function code + print("Check UV, and report by function") + + task_reporter(tool_name='MetaBox', task_name='CheckUV', debug=debug_mode, time_saved=5, time_cost=round(time.time()-start_time, 3)) + + check_uv_dec() + check_uv_func() + diff --git a/api.php b/api.php new file mode 100644 index 0000000..2167843 --- /dev/null +++ b/api.php @@ -0,0 +1,140 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->exec("SET NAMES utf8"); +} catch (PDOException $e) { + echo json_encode(["error" => "数据库连接失败: " . $e->getMessage()]); + exit; +} + +// 记录请求到日志文件 +function log_request($message) { + $log_file = __DIR__ . '/api_log.txt'; + $timestamp = date('Y-m-d H:i:s'); + $log_message = "[$timestamp] $message\n"; + file_put_contents($log_file, $log_message, FILE_APPEND); +} + +// 处理添加任务的POST请求 +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + try { + log_request("收到POST请求"); + $raw_data = file_get_contents('php://input'); + log_request("原始数据: $raw_data"); + + $data = json_decode($raw_data, true); + + if (!$data) { + throw new Exception("无效的JSON数据: " . json_last_error_msg()); + } + + log_request("解析后的数据: " . print_r($data, true)); + + // 验证必要字段 + $required_fields = ['username', 'tool_name', 'task_name', 'time_saved', 'time_cost']; + foreach ($required_fields as $field) { + if (!isset($data[$field]) || (is_string($data[$field]) && trim($data[$field]) === '')) { + throw new Exception("缺少必要字段: $field"); + } + } + + // 如果没有提供时间戳,使用当前时间 + if (!isset($data['timestamp']) || empty($data['timestamp'])) { + $data['timestamp'] = date('Y-m-d H:i:s'); + log_request("使用当前时间作为时间戳: {$data['timestamp']}"); + } + + // 确保时间戳格式正确 + if (strpos($data['timestamp'], 'T') !== false) { + $parts = explode(".", $data['timestamp']); + $data['timestamp'] = str_replace("T", " ", $parts[0]); + log_request("转换ISO时间戳格式: {$data['timestamp']}"); + } + + // 准备SQL语句 + $stmt = $pdo->prepare("INSERT INTO task (username, tool_name, task_name, time_saved, time_cost, timestamp) + VALUES (:username, :tool_name, :task_name, :time_saved, :time_cost, :timestamp)"); + + // 绑定参数 + $stmt->bindParam(':username', $data['username']); + $stmt->bindParam(':tool_name', $data['tool_name']); + $stmt->bindParam(':task_name', $data['task_name']); + $stmt->bindParam(':time_saved', $data['time_saved']); + $stmt->bindParam(':time_cost', $data['time_cost']); + $stmt->bindParam(':timestamp', $data['timestamp']); + + log_request("准备执行SQL插入"); + + // 执行SQL + $result = $stmt->execute(); + + if ($result) { + $last_id = $pdo->lastInsertId(); + log_request("数据成功插入,ID: $last_id"); + + echo json_encode([ + "success" => true, + "message" => "{$data['timestamp']} {$data['tool_name']}-{$data['task_name']} reported successfully!", + "id" => $last_id + ]); + } else { + log_request("数据插入失败"); + throw new Exception("数据插入失败"); + } + } catch (Exception $e) { + log_request("错误: " . $e->getMessage()); + http_response_code(400); + echo json_encode([ + "success" => false, + "error" => $e->getMessage() + ]); + } + exit; +} + +// 处理简单的GET请求,用于测试API是否正常工作 +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + echo json_encode([ + "status" => "API正常工作", + "message" => "使用POST请求提交任务数据", + "example" => [ + "username" => "用户名", + "tool_name" => "工具名称", + "task_name" => "任务名称", + "time_saved" => "节省时间(小时)", + "time_cost" => "花费时间(小时)", + "timestamp" => "可选,格式:YYYY-MM-DD HH:MM:SS" + ], + "contact" => "jeffreytsai1004@gmail.com" + ]); + exit; +} + +// 如果不是GET或POST请求,返回错误 +http_response_code(405); +echo json_encode(["error" => "不支持的请求方法"]); diff --git a/check_data.php b/check_data.php new file mode 100644 index 0000000..2cb405f --- /dev/null +++ b/check_data.php @@ -0,0 +1,67 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->exec("SET NAMES utf8"); + echo "数据库连接成功!
"; +} catch (PDOException $e) { + die("数据库连接失败: " . $e->getMessage()); +} + +// 查询最近的记录 +try { + $stmt = $pdo->query("SELECT * FROM task ORDER BY id DESC LIMIT 20"); + $records = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo "

最近20条记录

"; + + if (count($records) > 0) { + echo ""; + echo ""; + + foreach ($records as $record) { + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + + echo "
ID用户名工具名称任务名称节省时间花费时间时间戳
{$record['id']}{$record['username']}{$record['tool_name']}{$record['task_name']}{$record['time_saved']}{$record['time_cost']}{$record['timestamp']}
"; + } else { + echo "没有找到记录"; + } +} catch (PDOException $e) { + echo "查询失败: " . $e->getMessage(); +} + +// 检查API请求日志 +echo "

API请求日志检查

"; + +// 检查api.php是否正确处理请求 +echo "

请确认以下几点:

"; +echo "
    "; +echo "
  1. api.php文件是否正确接收和处理请求
  2. "; +echo "
  3. 数据库连接信息是否正确
  4. "; +echo "
  5. TaskReporter.py是否正确发送数据到api.php
  6. "; +echo "
"; + +// 显示刷新页面的链接 +echo "

刷新数据查询页面

"; +?> diff --git a/clean_old_reports.php b/clean_old_reports.php new file mode 100644 index 0000000..6ae5473 --- /dev/null +++ b/clean_old_reports.php @@ -0,0 +1,93 @@ + 30, + + // 报告目录 + 'reports_dir' => __DIR__ . '/reports/', + + // 日志文件 + 'log_file' => __DIR__ . '/logs/clean_reports.log', +]; + +// 记录日志 +function log_message($message) { + global $config; + + // 确保日志目录存在 + $log_dir = dirname($config['log_file']); + if (!is_dir($log_dir)) { + mkdir($log_dir, 0755, true); + } + + $date = date('Y-m-d H:i:s'); + $log_entry = "[$date] $message\n"; + + // 写入日志文件 + file_put_contents($config['log_file'], $log_entry, FILE_APPEND); + + // 如果是CLI模式,也输出到控制台 + if (php_sapi_name() === 'cli') { + echo $log_entry; + } +} + +// 主程序 +try { + log_message('===== 开始执行报告清理脚本 ====='); + + // 检查报告目录是否存在 + if (!is_dir($config['reports_dir'])) { + throw new Exception("报告目录不存在: {$config['reports_dir']}"); + } + + // 获取所有报告文件 + $reports = glob($config['reports_dir'] . 'report_*.html'); + + if (empty($reports)) { + log_message('没有找到需要清理的报告文件'); + exit; + } + + log_message('找到 ' . count($reports) . ' 个报告文件'); + + // 按修改时间排序(最新的在前) + usort($reports, function($a, $b) { + return filemtime($b) - filemtime($a); + }); + + // 保留最近的N个报告 + $reports_to_keep = array_slice($reports, 0, $config['keep_recent']); + $reports_to_delete = array_slice($reports, $config['keep_recent']); + + log_message('将保留最近的 ' . count($reports_to_keep) . ' 个报告'); + log_message('将删除 ' . count($reports_to_delete) . ' 个旧报告'); + + // 删除旧报告 + foreach ($reports_to_delete as $report) { + $filename = basename($report); + if (unlink($report)) { + log_message("已删除: $filename"); + } else { + log_message("删除失败: $filename"); + } + } + + log_message('===== 报告清理完成 ====='); + +} catch (Exception $e) { + log_message('错误: ' . $e->getMessage()); +} +?> diff --git a/client_example.php b/client_example.php new file mode 100644 index 0000000..e0ea6d0 --- /dev/null +++ b/client_example.php @@ -0,0 +1,79 @@ + 'JeffreyTsai', + 'tool_name' => 'MetaBox', + 'task_name' => 'ExampleTask', + 'time_saved' => '1.5', // 节省时间(小时) + 'time_cost' => '0.2', // 花费时间(小时) + // 'timestamp' => date('Y-m-d H:i:s') // 可选,如果不提供将使用服务器时间 +]; + +// 将数据转换为JSON +$json_data = json_encode($data); + +// 设置cURL选项 +$ch = curl_init($api_url); +curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); +curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json_data) +]); + +// 执行请求 +$response = curl_exec($ch); +$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +// 输出结果 +echo "HTTP响应代码: $http_code\n"; +echo "响应内容:\n"; +echo $response; + +/** + * Python客户端示例 + * + * 以下是使用Python向API提交数据的示例代码 + * + * ```python + * import requests + * import json + * from datetime import datetime + * + * # 设置API地址 + * api_url = 'https://toolstrack.cgnico.com/api.php' + * # 本地测试可以使用: + * # api_url = 'http://192.168.2.4:9000/api.php' + * + * # 准备要提交的数据 + * data = { + * 'username': 'JeffreyTsai', + * 'tool_name': 'MetaBox', + * 'task_name': 'ExampleTask', + * 'time_saved': '1.5', # 节省时间(小时) + * 'time_cost': '0.2', # 花费时间(小时) + * # 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 可选 + * } + * + * # 发送POST请求 + * response = requests.post(api_url, json=data) + * + * # 输出结果 + * print(f"HTTP响应代码: {response.status_code}") + * print("响应内容:") + * print(response.json()) + * ``` + */ diff --git a/data.csv b/data.csv new file mode 100644 index 0000000..56f808a --- /dev/null +++ b/data.csv @@ -0,0 +1,9 @@ +index,username,tool_name,task_name,timestamp +1,JeffreyTsai,MetaBox,StudioLibrary,2025-01-20 17:53:54 +2,JeffreyTsai,MetaBox,UVTools,2025-01-20 17:53:54 +3,JeffreyTsai,MetaBox,UVTools,2025-01-20 17:53:54 +4,JeffreyTsai,MetaBox,ahoge,2025-01-21 10:23:37 +5,JeffreyTsai,MetaBox,gscurvetool,2025-01-21 10:23:37 +6,JeffreyTsai,MetaBox,DebugTool,2025-01-21 10:29:27 +7,JeffreyTsai,MetaBox,CheckNormal,2025-01-21 10:42:27 +8,JeffreyTsai,MetaBox,CheckNormal,2025-01-21 10:42:27 diff --git a/generate_report.php b/generate_report.php new file mode 100644 index 0000000..1717433 --- /dev/null +++ b/generate_report.php @@ -0,0 +1,642 @@ + '192.168.2.4', + 'db_port' => 3307, + 'db_name' => 'task_reporter', + 'db_user' => 'task_reporter', + 'db_pass' => 'Pass12349ers!', + + // 报告生成选项 + 'author' => 'CGNICO Task Reporter', // 报告作者 + + // 报告配置 + 'report_days' => 7, // 报告包含的天数(例如:7表示过去一周) + 'report_title' => 'CGNICO工具使用报告', // 报告标题 +]; + +// 根据报告天数设置标题 +if (isset($_GET['days'])) { + $config['report_days'] = intval($_GET['days']); + + // 根据天数调整报告标题 + if ($config['report_days'] == 7) { + $config['report_title'] = 'CGNICO工具使用周报'; + } elseif ($config['report_days'] == 30) { + $config['report_title'] = 'CGNICO工具使用月报'; + } elseif ($config['report_days'] == 90) { + $config['report_title'] = 'CGNICO工具使用季报'; + } elseif ($config['report_days'] == 365) { + $config['report_title'] = 'CGNICO工具使用年报'; + } +} + +// 创建数据库连接 +try { + $pdo = new PDO( + "mysql:host={$config['db_host']};port={$config['db_port']};dbname={$config['db_name']}", + $config['db_user'], + $config['db_pass'] + ); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->exec("SET NAMES utf8"); +} catch (PDOException $e) { + die("数据库连接失败: " . $e->getMessage()); +} + +// 生成报告 +function generate_report($pdo, $days) { + $report = []; + + // 获取日期范围 + $end_date = date('Y-m-d'); + $start_date = date('Y-m-d', strtotime("-{$days} days")); + + // 1. 总体统计 + $stmt = $pdo->prepare(" + SELECT + COUNT(*) as total_tasks, + SUM(time_saved) as total_time_saved, + COUNT(DISTINCT username) as total_users, + COUNT(DISTINCT tool_name) as total_tools + FROM task + WHERE timestamp BETWEEN :start_date AND :end_date + AND tool_name NOT LIKE '%(Debug)%' + "); + $stmt->bindParam(':start_date', $start_date); + $stmt->bindParam(':end_date', $end_date); + $stmt->execute(); + $report['summary'] = $stmt->fetch(PDO::FETCH_ASSOC); + + // 2. 工具使用统计 + $stmt = $pdo->prepare(" + SELECT + tool_name, + COUNT(*) as usage_count, + SUM(time_saved) as time_saved + FROM task + WHERE timestamp BETWEEN :start_date AND :end_date + AND tool_name NOT LIKE '%(Debug)%' + GROUP BY tool_name + ORDER BY usage_count DESC + LIMIT 10 + "); + $stmt->bindParam(':start_date', $start_date); + $stmt->bindParam(':end_date', $end_date); + $stmt->execute(); + $report['tools'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // 3. 用户使用统计 + $stmt = $pdo->prepare(" + SELECT + username, + COUNT(*) as usage_count, + SUM(time_saved) as time_saved + FROM task + WHERE timestamp BETWEEN :start_date AND :end_date + AND tool_name NOT LIKE '%(Debug)%' + GROUP BY username + ORDER BY usage_count DESC + LIMIT 10 + "); + $stmt->bindParam(':start_date', $start_date); + $stmt->bindParam(':end_date', $end_date); + $stmt->execute(); + $report['users'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // 4. 每日使用趋势 + $stmt = $pdo->prepare(" + SELECT + DATE(timestamp) as date, + COUNT(*) as usage_count, + SUM(time_saved) as time_saved + FROM task + WHERE timestamp BETWEEN :start_date AND :end_date + AND tool_name NOT LIKE '%(Debug)%' + GROUP BY DATE(timestamp) + ORDER BY date + "); + $stmt->bindParam(':start_date', $start_date); + $stmt->bindParam(':end_date', $end_date); + $stmt->execute(); + $report['daily'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + + return [ + 'report' => $report, + 'start_date' => $start_date, + 'end_date' => $end_date + ]; +} + +// 生成HTML报告 +function generate_html_report($report_data) { + $report = $report_data['report']; + $start_date = $report_data['start_date']; + $end_date = $report_data['end_date']; + + $html = ' + + + + + CGNICO工具使用报告 + + + +

CGNICO工具使用报告

+

报告时间范围: ' . $start_date . ' 至 ' . $end_date . '

+ +
+

总体统计

+

在过去的 ' . round((strtotime($end_date) - strtotime($start_date)) / 86400) . ' 天中:

+ +
+ +

工具使用排名

+ + + + + + + '; + + $rank = 1; + foreach ($report['tools'] as $tool) { + $html .= ' + + + + + + '; + } + + $html .= ' +
排名工具名称使用次数节省时间 (小时)
' . $rank++ . '' . htmlspecialchars($tool['tool_name']) . '' . number_format($tool['usage_count']) . '' . number_format($tool['time_saved'], 2) . '
+ +

用户使用排名

+ + + + + + + '; + + $rank = 1; + foreach ($report['users'] as $user) { + $html .= ' + + + + + + '; + } + + $html .= ' +
排名用户名使用次数节省时间 (小时)
' . $rank++ . '' . htmlspecialchars($user['username']) . '' . number_format($user['usage_count']) . '' . number_format($user['time_saved'], 2) . '
+ +

每日使用趋势

+ + + + + + '; + + foreach ($report['daily'] as $day) { + $html .= ' + + + + + '; + } + + $html .= ' +
日期使用次数节省时间 (小时)
' . $day['date'] . '' . number_format($day['usage_count']) . '' . number_format($day['time_saved'], 2) . '
+ +
+

此报告由CGNICO Task Reporter自动生成于 ' . date('Y-m-d H:i:s') . '

+
+ + '; + + return $html; +} + +// 记录日志 +function log_message($message) { + $log_dir = __DIR__ . '/logs'; + if (!is_dir($log_dir)) { + mkdir($log_dir, 0755, true); + } + + $log_file = $log_dir . '/report_' . date('Y-m-d') . '.log'; + $time = date('Y-m-d H:i:s'); + file_put_contents($log_file, "[$time] $message\n", FILE_APPEND); +} + +// 主程序 +try { + // 创建报告目录(如果不存在) + $report_dir = __DIR__ . '/reports/'; + if (!is_dir($report_dir)) { + mkdir($report_dir, 0755, true); + } + + // 检查是否需要生成新报告 + $generate_new_report = false; + $report_file = $report_dir . 'report_' . date('Y-m-d') . '.html'; + + if (isset($_GET['generate']) && $_GET['generate'] == '1') { + $generate_new_report = true; + } elseif (!file_exists($report_file)) { + // 如果今天的报告不存在,自动生成 + $generate_new_report = true; + } + + if ($generate_new_report) { + log_message('===== 开始执行报告生成脚本 ====='); + + // 生成报告数据 + log_message('开始生成报告数据...'); + $days = $config['report_days']; + $report_data = generate_report($pdo, $days); + log_message('报告数据生成完成'); + + // 生成HTML报告 + log_message('生成HTML报告...'); + $html_report = generate_html_report($report_data); + log_message('HTML报告生成完成'); + + // 保存报告到文件 + file_put_contents($report_file, $html_report); + log_message('报告已保存到: ' . $report_file); + } + + // 开始HTML输出 +?> + + + + + + CGNICO工具使用报告生成器 + + + +
+

CGNICO工具使用报告生成器

+ + +
+

报告已生成成功!

+

报告时间范围: 过去天 ()

+
+ + + + +
+

生成新报告

+
+
+ + +
+ + +
+
+ + \n"; + echo "

历史报告

\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + rsort($reports); // 按日期降序排序 + + foreach (array_slice($reports, 0, 10) as $report) { // 只显示最近10个 + $filename = basename($report); + $date = substr($filename, 7, 10); // 从 report_YYYY-MM-DD.html 提取日期 + + // 尝试确定报告类型 + $report_type = "常规报告"; + $file_content = file_get_contents($report); + if (strpos($file_content, "工具使用周报") !== false) { + $report_type = "周报"; + } elseif (strpos($file_content, "工具使用月报") !== false) { + $report_type = "月报"; + } elseif (strpos($file_content, "工具使用季报") !== false) { + $report_type = "季报"; + } elseif (strpos($file_content, "工具使用年报") !== false) { + $report_type = "年报"; + } + + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + } + + echo "
报告日期报告类型操作
{$date}{$report_type}\n"; + echo "查看\n"; + echo "下载\n"; + echo "
\n"; + echo "
\n"; + } + } + ?> + + + + + +getMessage()); + ?> + + + + + + 错误 - CGNICO工具使用报告生成器 + + + +
+

发生错误

+
+

错误: getMessage(); ?>

+
+ 返回首页 +
+ + + diff --git a/index.php b/index.php new file mode 100644 index 0000000..89e022f --- /dev/null +++ b/index.php @@ -0,0 +1,585 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->exec("SET NAMES utf8"); +} catch (PDOException $e) { + die("数据库连接失败: " . $e->getMessage()); +} + +// 创建任务表(如果不存在) +try { + $sql = "CREATE TABLE IF NOT EXISTS task ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(100) NOT NULL, + tool_name VARCHAR(100) NOT NULL, + task_name VARCHAR(100) NOT NULL, + time_saved VARCHAR(100) NOT NULL, + time_cost VARCHAR(100) NOT NULL, + timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )"; + $pdo->exec($sql); +} catch (PDOException $e) { + die("创建表失败: " . $e->getMessage()); +} + +// 处理添加任务的POST请求 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'add_task') { + try { + $data = json_decode(file_get_contents('php://input'), true); + + if (!$data) { + throw new Exception("无效的JSON数据"); + } + + $stmt = $pdo->prepare("INSERT INTO task (username, tool_name, task_name, time_saved, time_cost, timestamp) + VALUES (:username, :tool_name, :task_name, :time_saved, :time_cost, :timestamp)"); + + $stmt->bindParam(':username', $data['username']); + $stmt->bindParam(':tool_name', $data['tool_name']); + $stmt->bindParam(':task_name', $data['task_name']); + $stmt->bindParam(':time_saved', $data['time_saved']); + $stmt->bindParam(':time_cost', $data['time_cost']); + $stmt->bindParam(':timestamp', $data['timestamp']); + + $stmt->execute(); + + $time = explode(".", $data['timestamp'])[0]; + $time = str_replace("T", " ", $time); + + header('Content-Type: application/json'); + echo json_encode([ + "message" => "$time {$data['tool_name']}-{$data['task_name']} reported successfully!" + ]); + exit; + } catch (Exception $e) { + header('Content-Type: application/json'); + http_response_code(400); + echo json_encode(["error" => $e->getMessage()]); + exit; + } +} + +// 处理查询任务数据的GET请求 +if (isset($_GET['action']) && $_GET['action'] === 'query_task_data') { + try { + // 获取筛选参数 + $start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null; + $end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null; + $task_name = isset($_GET['task_name']) ? $_GET['task_name'] : null; + $tool_name = isset($_GET['tool_name']) ? $_GET['tool_name'] : null; + $time_saved = isset($_GET['time_saved']) ? $_GET['time_saved'] : null; + $time_cost = isset($_GET['time_cost']) ? $_GET['time_cost'] : null; + $show_debug = isset($_GET['show_debug']) && $_GET['show_debug'] === 'true'; + + // 获取排序参数 + $sort_by = isset($_GET['sort_by']) ? $_GET['sort_by'] : 'timestamp'; + $sort_direction = isset($_GET['sort_direction']) ? $_GET['sort_direction'] : 'desc'; + $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; + $per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 50; + + // 构建查询 + $query = "SELECT * FROM task WHERE 1=1"; + $params = []; + + // 应用日期筛选 + if ($start_date) { + $query .= " AND timestamp >= :start_date"; + $params[':start_date'] = $start_date . " 00:00:00"; + } + + if ($end_date) { + $query .= " AND timestamp <= :end_date"; + $params[':end_date'] = $end_date . " 23:59:59"; + } + + // 应用任务名称筛选 + if ($task_name) { + $query .= " AND task_name LIKE :task_name"; + $params[':task_name'] = "%$task_name%"; + } + + // 应用工具名称筛选 + if ($tool_name) { + $query .= " AND tool_name LIKE :tool_name"; + $params[':tool_name'] = "%$tool_name%"; + } + + // 排除调试工具(如果不显示调试) + if (!$show_debug) { + $query .= " AND tool_name NOT LIKE '%(Debug)%'"; + } + + // 应用排序 + $valid_sort_fields = ['timestamp', 'time_saved', 'time_cost']; + if (!in_array($sort_by, $valid_sort_fields)) { + $sort_by = 'timestamp'; + } + + $query .= " ORDER BY $sort_by " . ($sort_direction === 'asc' ? 'ASC' : 'DESC'); + + // 应用分页 + $offset = ($page - 1) * $per_page; + $query .= " LIMIT :offset, :limit"; + $params[':offset'] = $offset; + $params[':limit'] = $per_page; + + // 执行查询 + $stmt = $pdo->prepare($query); + foreach ($params as $key => $value) { + if ($key === ':offset' || $key === ':limit') { + $stmt->bindValue($key, $value, PDO::PARAM_INT); + } else { + $stmt->bindValue($key, $value); + } + } + $stmt->execute(); + + $tasks = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // 获取总记录数 + $count_query = "SELECT COUNT(*) FROM task WHERE 1=1"; + $count_params = []; + + if ($start_date) { + $count_query .= " AND timestamp >= :start_date"; + $count_params[':start_date'] = $start_date . " 00:00:00"; + } + + if ($end_date) { + $count_query .= " AND timestamp <= :end_date"; + $count_params[':end_date'] = $end_date . " 23:59:59"; + } + + if ($task_name) { + $count_query .= " AND task_name LIKE :task_name"; + $count_params[':task_name'] = "%$task_name%"; + } + + if ($tool_name) { + $count_query .= " AND tool_name LIKE :tool_name"; + $count_params[':tool_name'] = "%$tool_name%"; + } + + if (!$show_debug) { + $count_query .= " AND tool_name NOT LIKE '%(Debug)%'"; + } + + $count_stmt = $pdo->prepare($count_query); + foreach ($count_params as $key => $value) { + $count_stmt->bindValue($key, $value); + } + $count_stmt->execute(); + $total_count = $count_stmt->fetchColumn(); + + // 获取所有工具名称和任务名称(用于筛选) + $tools_stmt = $pdo->query("SELECT DISTINCT tool_name FROM task ORDER BY tool_name"); + $tools = $tools_stmt->fetchAll(PDO::FETCH_COLUMN); + + $tasks_stmt = $pdo->query("SELECT DISTINCT task_name FROM task ORDER BY task_name"); + $task_names = $tasks_stmt->fetchAll(PDO::FETCH_COLUMN); + + // 返回数据 + $result = [ + 'tasks' => $tasks, + 'total' => $total_count, + 'page' => $page, + 'per_page' => $per_page, + 'total_pages' => ceil($total_count / $per_page), + 'tools' => $tools, + 'task_names' => $task_names + ]; + + header('Content-Type: application/json'); + echo json_encode($result); + exit; + } catch (Exception $e) { + header('Content-Type: application/json'); + http_response_code(500); + echo json_encode(["error" => $e->getMessage()]); + exit; + } +} + +// 处理获取工具统计数据的GET请求 +if (isset($_GET['action']) && $_GET['action'] === 'get_tool_statistics') { + try { + // 获取日期范围 + $start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null; + $end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null; + + // 构建查询 + $query = "SELECT tool_name, COUNT(*) as request_count, SUM(time_saved) as time_saved + FROM task + WHERE tool_name NOT LIKE '%(Debug)%'"; + $params = []; + + if ($start_date) { + $query .= " AND timestamp >= :start_date"; + $params[':start_date'] = $start_date . " 00:00:00"; + } + + if ($end_date) { + $query .= " AND timestamp <= :end_date"; + $params[':end_date'] = $end_date . " 23:59:59"; + } + + $query .= " GROUP BY tool_name ORDER BY request_count DESC"; + + // 执行查询 + $stmt = $pdo->prepare($query); + foreach ($params as $key => $value) { + $stmt->bindValue($key, $value); + } + $stmt->execute(); + + $tools_stats = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // 计算总请求数和总节省时间 + $total_requests = 0; + $total_time_saved = 0; + + foreach ($tools_stats as $stat) { + $total_requests += $stat['request_count']; + $total_time_saved += $stat['time_saved']; + } + + // 返回数据 + $result = [ + 'tools' => $tools_stats, + 'total_requests' => $total_requests, + 'total_time_saved' => $total_time_saved + ]; + + header('Content-Type: application/json'); + echo json_encode($result); + exit; + } catch (Exception $e) { + header('Content-Type: application/json'); + http_response_code(500); + echo json_encode(["error" => $e->getMessage()]); + exit; + } +} + +// 处理导出CSV的GET请求 +if (isset($_GET['action']) && $_GET['action'] === 'export_csv') { + try { + // 获取筛选参数 + $start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null; + $end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null; + $task_name = isset($_GET['task_name']) ? $_GET['task_name'] : null; + $tool_name = isset($_GET['tool_name']) ? $_GET['tool_name'] : null; + $show_debug = isset($_GET['show_debug']) && $_GET['show_debug'] === 'true'; + + // 构建查询 + $query = "SELECT id, username, tool_name, task_name, time_saved, time_cost, timestamp + FROM task WHERE 1=1"; + $params = []; + + if ($start_date) { + $query .= " AND timestamp >= :start_date"; + $params[':start_date'] = $start_date . " 00:00:00"; + } + + if ($end_date) { + $query .= " AND timestamp <= :end_date"; + $params[':end_date'] = $end_date . " 23:59:59"; + } + + if ($task_name) { + $query .= " AND task_name LIKE :task_name"; + $params[':task_name'] = "%$task_name%"; + } + + if ($tool_name) { + $query .= " AND tool_name LIKE :tool_name"; + $params[':tool_name'] = "%$tool_name%"; + } + + if (!$show_debug) { + $query .= " AND tool_name NOT LIKE '%(Debug)%'"; + } + + $query .= " ORDER BY timestamp DESC"; + + // 执行查询 + $stmt = $pdo->prepare($query); + foreach ($params as $key => $value) { + $stmt->bindValue($key, $value); + } + $stmt->execute(); + + $tasks = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // 设置CSV文件头 + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename=task_data.csv'); + + // 创建CSV输出流 + $output = fopen('php://output', 'w'); + + // 添加CSV头行 + fputcsv($output, ['ID', 'Username', 'Tool Name', 'Task Name', 'Time Saved', 'Time Cost', 'Timestamp']); + + // 添加数据行 + foreach ($tasks as $task) { + fputcsv($output, [ + $task['id'], + $task['username'], + $task['tool_name'], + $task['task_name'], + $task['time_saved'], + $task['time_cost'], + $task['timestamp'] + ]); + } + + fclose($output); + exit; + } catch (Exception $e) { + header('Content-Type: application/json'); + http_response_code(500); + echo json_encode(["error" => $e->getMessage()]); + exit; + } +} + +// 处理获取特定工具任务的GET请求 +if (isset($_GET['action']) && $_GET['action'] === 'get_tool_tasks') { + try { + $tool_name = isset($_GET['tool_name']) ? $_GET['tool_name'] : null; + + if (!$tool_name) { + throw new Exception("Tool name is required"); + } + + // 查询该工具的所有任务 + $stmt = $pdo->prepare("SELECT task_name, COUNT(*) as total_requests, SUM(time_saved) as time_saved + FROM task + WHERE tool_name = :tool_name AND tool_name NOT LIKE '%(Debug)%' + GROUP BY task_name + ORDER BY total_requests DESC"); + $stmt->bindParam(':tool_name', $tool_name); + $stmt->execute(); + + $tasks = $stmt->fetchAll(PDO::FETCH_ASSOC); + + header('Content-Type: application/json'); + echo json_encode(['tasks' => $tasks]); + exit; + } catch (Exception $e) { + header('Content-Type: application/json'); + http_response_code(500); + echo json_encode(["error" => $e->getMessage()]); + exit; + } +} + +// 处理获取趋势数据的GET请求 +if (isset($_GET['action']) && $_GET['action'] === 'get_trend_data') { + try { + // 获取查询参数中的日期范围 + $start_date = isset($_GET['start_date']) ? $_GET['start_date'] : null; + $end_date = isset($_GET['end_date']) ? $_GET['end_date'] : null; + + // 如果没有指定日期范围,默认显示最近30天 + if (!$start_date || !$end_date) { + $end_date = date('Y-m-d'); + $start_date = date('Y-m-d', strtotime('-30 days')); + } + + // 将字符串日期转换为日期对象 + $start_datetime = date_create($start_date); + $end_datetime = date_create($end_date); + + // 设置结束日期为当天的最后一秒 + $end_datetime->setTime(23, 59, 59); + + // 使用PDO查询 + $stmt = $pdo->prepare("SELECT DATE(timestamp) as date, COUNT(*) as total_requests, SUM(time_saved) as time_saved + FROM task + WHERE timestamp BETWEEN :start_date AND :end_date + GROUP BY DATE(timestamp) + ORDER BY date"); + $stmt->bindValue(':start_date', $start_datetime->format('Y-m-d 00:00:00')); + $stmt->bindValue(':end_date', $end_datetime->format('Y-m-d H:i:s')); + $stmt->execute(); + + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // 填充所有日期(包括没有数据的日期) + $dates = []; + $requests = []; + $time_saved = []; + + // 创建日期范围内的所有日期 + $date_range = []; + $current = clone $start_datetime; + while ($current <= $end_datetime) { + $date_range[$current->format('Y-m-d')] = [ + 'total_requests' => 0, + 'time_saved' => 0 + ]; + $current->modify('+1 day'); + } + + // 填充实际数据 + foreach ($results as $row) { + $date_range[$row['date']] = [ + 'total_requests' => (int)$row['total_requests'], + 'time_saved' => (float)$row['time_saved'] + ]; + } + + // 转换为数组格式 + foreach ($date_range as $date => $stats) { + $dates[] = $date; + $requests[] = $stats['total_requests']; + $time_saved[] = $stats['time_saved']; + } + + $response_data = [ + 'dates' => $dates, + 'requests' => $requests, + 'timeSaved' => $time_saved + ]; + + header('Content-Type: application/json'); + echo json_encode($response_data); + exit; + } catch (Exception $e) { + header('Content-Type: application/json'); + http_response_code(500); + echo json_encode(["error" => $e->getMessage()]); + exit; + } +} + +// 如果没有特定操作,显示主页 +?> + + + + Task Report System + + + +
+

CGNICO Task Report System

+ + +
+ + diff --git a/insert_test_data.php b/insert_test_data.php new file mode 100644 index 0000000..19edad7 --- /dev/null +++ b/insert_test_data.php @@ -0,0 +1,236 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->exec("SET NAMES utf8"); + echo "数据库连接成功!
"; +} catch (PDOException $e) { + die("数据库连接失败: " . $e->getMessage()); +} + +// 确保表存在 +try { + $sql = "CREATE TABLE IF NOT EXISTS task ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(100) NOT NULL, + tool_name VARCHAR(100) NOT NULL, + task_name VARCHAR(100) NOT NULL, + time_saved VARCHAR(100) NOT NULL, + time_cost VARCHAR(100) NOT NULL, + timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )"; + $pdo->exec($sql); + echo "表结构检查完成!
"; +} catch (PDOException $e) { + die("创建表失败: " . $e->getMessage()); +} + +// 清空现有数据(可选) +try { + $pdo->exec("TRUNCATE TABLE task"); + echo "表已清空,准备插入测试数据...
"; +} catch (PDOException $e) { + echo "清空表失败: " . $e->getMessage() . "
"; +} + +// 准备测试数据 +$test_data = [ + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'MetaBox', + 'task_name' => 'CheckUV', + 'time_saved' => '5.2', + 'time_cost' => '0.3', + 'timestamp' => '2025-04-01 09:30:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'MetaBox', + 'task_name' => 'FixNormals', + 'time_saved' => '3.5', + 'time_cost' => '0.2', + 'timestamp' => '2025-04-01 10:15:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'RigTools', + 'task_name' => 'AutoRig', + 'time_saved' => '12.0', + 'time_cost' => '1.5', + 'timestamp' => '2025-04-02 14:20:00' + ], + [ + 'username' => 'LiuYang', + 'tool_name' => 'TextureManager', + 'task_name' => 'BatchExport', + 'time_saved' => '8.5', + 'time_cost' => '0.8', + 'timestamp' => '2025-04-03 11:45:00' + ], + [ + 'username' => 'LiuYang', + 'tool_name' => 'TextureManager', + 'task_name' => 'ResizeTextures', + 'time_saved' => '4.2', + 'time_cost' => '0.5', + 'timestamp' => '2025-04-03 15:30:00' + ], + [ + 'username' => 'ZhangWei', + 'tool_name' => 'AnimTools', + 'task_name' => 'CycleCheck', + 'time_saved' => '6.8', + 'time_cost' => '0.4', + 'timestamp' => '2025-04-04 09:15:00' + ], + [ + 'username' => 'ZhangWei', + 'tool_name' => 'AnimTools', + 'task_name' => 'KeyCleaner', + 'time_saved' => '5.5', + 'time_cost' => '0.3', + 'timestamp' => '2025-04-04 14:45:00' + ], + [ + 'username' => 'WangFang', + 'tool_name' => 'LightingSetup', + 'task_name' => 'HDRIManager', + 'time_saved' => '7.2', + 'time_cost' => '0.6', + 'timestamp' => '2025-04-05 10:30:00' + ], + [ + 'username' => 'WangFang', + 'tool_name' => 'LightingSetup', + 'task_name' => 'ShadowFix', + 'time_saved' => '3.8', + 'time_cost' => '0.4', + 'timestamp' => '2025-04-05 16:20:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'MetaBox', + 'task_name' => 'CheckUV', + 'time_saved' => '5.0', + 'time_cost' => '0.3', + 'timestamp' => '2025-04-06 11:10:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'MetaBox(Debug)', + 'task_name' => 'TestFeature', + 'time_saved' => '0.5', + 'time_cost' => '1.2', + 'timestamp' => '2025-04-07 09:45:00' + ], + [ + 'username' => 'LiuYang', + 'tool_name' => 'TextureManager', + 'task_name' => 'BatchExport', + 'time_saved' => '8.0', + 'time_cost' => '0.7', + 'timestamp' => '2025-04-08 13:30:00' + ], + [ + 'username' => 'ZhangWei', + 'tool_name' => 'AnimTools', + 'task_name' => 'CycleCheck', + 'time_saved' => '6.5', + 'time_cost' => '0.4', + 'timestamp' => '2025-04-09 10:20:00' + ], + [ + 'username' => 'WangFang', + 'tool_name' => 'LightingSetup', + 'task_name' => 'HDRIManager', + 'time_saved' => '7.0', + 'time_cost' => '0.6', + 'timestamp' => '2025-04-10 15:45:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'RigTools', + 'task_name' => 'AutoRig', + 'time_saved' => '11.5', + 'time_cost' => '1.4', + 'timestamp' => '2025-04-11 09:30:00' + ], + [ + 'username' => 'LiuYang', + 'tool_name' => 'TextureManager', + 'task_name' => 'ResizeTextures', + 'time_saved' => '4.0', + 'time_cost' => '0.5', + 'timestamp' => '2025-04-12 14:15:00' + ], + [ + 'username' => 'ZhangWei', + 'tool_name' => 'AnimTools', + 'task_name' => 'KeyCleaner', + 'time_saved' => '5.2', + 'time_cost' => '0.3', + 'timestamp' => '2025-04-12 16:40:00' + ], + [ + 'username' => 'WangFang', + 'tool_name' => 'LightingSetup', + 'task_name' => 'ShadowFix', + 'time_saved' => '3.5', + 'time_cost' => '0.4', + 'timestamp' => '2025-04-13 10:15:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'MetaBox', + 'task_name' => 'FixNormals', + 'time_saved' => '3.2', + 'time_cost' => '0.2', + 'timestamp' => '2025-04-13 11:30:00' + ], + [ + 'username' => 'JeffreyTsai', + 'tool_name' => 'MetaBox', + 'task_name' => 'CheckUV', + 'time_saved' => '5.1', + 'time_cost' => '0.3', + 'timestamp' => '2025-04-13 13:45:00' + ] +]; + +// 插入测试数据 +$success_count = 0; +$stmt = $pdo->prepare("INSERT INTO task (username, tool_name, task_name, time_saved, time_cost, timestamp) + VALUES (:username, :tool_name, :task_name, :time_saved, :time_cost, :timestamp)"); + +foreach ($test_data as $data) { + try { + $stmt->bindParam(':username', $data['username']); + $stmt->bindParam(':tool_name', $data['tool_name']); + $stmt->bindParam(':task_name', $data['task_name']); + $stmt->bindParam(':time_saved', $data['time_saved']); + $stmt->bindParam(':time_cost', $data['time_cost']); + $stmt->bindParam(':timestamp', $data['timestamp']); + + $stmt->execute(); + $success_count++; + } catch (PDOException $e) { + echo "插入数据失败: " . $e->getMessage() . "
"; + } +} + +echo "成功插入 {$success_count} 条测试数据!
"; +echo "
所有操作已完成!
"; +echo "返回首页 | 查看数据"; +?> diff --git a/query_task_data.php b/query_task_data.php new file mode 100644 index 0000000..16d085a --- /dev/null +++ b/query_task_data.php @@ -0,0 +1,943 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->exec("SET NAMES utf8"); +} catch (PDOException $e) { + die("数据库连接失败: " . $e->getMessage()); +} + +// 获取所有工具名称和任务名称(用于筛选) +$tools_stmt = $pdo->query("SELECT DISTINCT tool_name FROM task ORDER BY tool_name"); +$tools = $tools_stmt->fetchAll(PDO::FETCH_COLUMN); + +$tasks_stmt = $pdo->query("SELECT DISTINCT task_name FROM task ORDER BY task_name"); +$task_names = $tasks_stmt->fetchAll(PDO::FETCH_COLUMN); +?> + + + + Task Report Data + + + + + +
+
+
+ +
+

Task Report Data

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + + + +
+ +
+
+

数据与统计

+
+ +
+
请求数据
+
趋势分析
+
工具统计
+
+ +
+ + + + + + + + + + + + + + + +
ID用户名工具名称任务名称节省时间花费时间时间戳
+ + +
+ + + +
+
+ +
+
+
+
+ + + + + + + diff --git a/static/background.jpg b/static/background.jpg new file mode 100644 index 0000000..60fbf8b Binary files /dev/null and b/static/background.jpg differ diff --git a/templates/data_table.html b/templates/data_table.html new file mode 100644 index 0000000..c3054eb --- /dev/null +++ b/templates/data_table.html @@ -0,0 +1,1387 @@ + + + + CGNCIO Task Report System + + + + + +
+ + + +

Task Report System

+ + +
+
+
Data Details
+
Tool Statistics
+
+
+ + +
+ +
+ +
+ + + to + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ + +
+
+ + +
+ + + + + + + + + + + + + + {% for task in tasks %} + + + + + + + + + {% endfor %} + +
+ Timestamp + + + + UsernameTool NameTask Name + Time Saved (s) + + + + + Time Cost (s) + + + +
{{ task[4] }}{{ task[1] }}{{ task[2] }}{{ task[3] }}{{ task[5] }}{{ task[6] }}
+
+ + + + +
+
+ Total Tasks: {{ tasks|length }} +
+
+ +
+
+
+ +
+ +
+ +
+ + + + + + + to + +
+ + +
+ + +
+
+ + +
+ +
+ +
+ + +
+ +
+

Tool Statistics

+ + + + + + + + + + + {% for tool in tool_stats %} + + + + + + + {% endfor %} + +
IndexTool Name + Total Request + + + + Time Saved (s)
{{ loop.index }}{{ tool.tool_name }}{{ tool.total_requests }}{{ tool.time_saved }}
+
+ + +
+

Task Details

+ + + + + + + + + + + + +
IndexTask NameTotal RequestTime Saved (s)
+
+
+ + + + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..7dce34f --- /dev/null +++ b/templates/index.html @@ -0,0 +1,110 @@ + + + + Task Report System + + + +
+

CGNICO Task Report System

+ +
+ + \ No newline at end of file