Files
MovieLog_Server/lib/stats.php
T
laoyang dd957d45bd refactor: 拆分 config.php 为 lib/ 模块,消除页面层重复代码
纯结构性重构,不改变任何功能。

- config.php 由 739 行缩减为 42 行,改为纯引导入口;新增 ROOT_DIR 常量
  统一拼接绝对路径(lib/ 内模块的 __DIR__ 会指向 lib/,必须替换)
- 新增 lib/ 五个模块 db.php / helpers.php / auth.php / movies.php / stats.php,
  由 config.php 按 db → helpers → auth → movies → stats 顺序加载
- add.php、edit.php 各自约 80 行重复的 POST 处理改为调用
  resolveCinemaId / resolvePersonIds / linkViewers / uploadImageFile /
  saveBase64Image / uploadMultiplePhotos / deleteStoredFile
- cinemas.php、persons.php 中重复 4 次的颜色校验改用 normalizeHexColor()
- toggleNewViewer / removeViewerRow / toggleNewCinema 下沉至 js/main.js,
  add.php 中的 removeViewer 统一更名为 removeViewerRow
- 删除死代码:imageUrl()(全项目无调用);cinemas.php 配色数组中本就会被
  array_slice 丢弃的 extra 颜色逻辑,简化为单条扁平 50 元素数组
- README 目录结构补充 lib/ 说明

验证方式:以便携 PHP 8.4 搭建 A/B 回归环境,播种固定数据后遍历 47 个
GET/POST 端点录制「状态码 + 响应体」。重构前后 42 个逐字节相同、2 个仅
上传文件名时间戳不同、3 个差异均为内联脚本下沉(已单独校验函数体逐字符
等价,剥离脚本后 HTML 标记完全一致)。24 个 PHP 文件语法检查全部通过。
2026-09-21 12:56:30 +08:00

330 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// ============================================
// 文件: lib/stats.php
// 说明: 统计仪表板与年度报告的查询逻辑
// ============================================
// ===== 统计 =====
function getStatsOverview() {
$db = getDB();
$stats = [];
$stmt = $db->query("SELECT COUNT(*) FROM movies");
$stats['total_movies'] = $stmt->fetchColumn();
$stmt = $db->query("SELECT COUNT(*) FROM cinemas");
$stats['total_cinemas'] = $stmt->fetchColumn();
$stmt = $db->query("SELECT COUNT(*) FROM persons");
$stats['total_persons'] = $stmt->fetchColumn();
$stmt = $db->query("SELECT COUNT(*) FROM viewers");
$stats['total_viewership'] = $stmt->fetchColumn();
$stmt = $db->query("SELECT MIN(watch_date) as first, MAX(watch_date) as last FROM movies WHERE watch_date IS NOT NULL");
$row = $stmt->fetch();
$stats['first_date'] = $row['first'];
$stats['last_date'] = $row['last'];
return $stats;
}
function statsByCinema() {
$db = getDB();
$stmt = $db->query("
SELECT c.id, c.name, c.color, COUNT(m.id) as count
FROM cinemas c
LEFT JOIN movies m ON c.id = m.cinema_id
GROUP BY c.id
ORDER BY count DESC
");
return $stmt->fetchAll();
}
function statsByPerson() {
$db = getDB();
$stmt = $db->query("
SELECT p.id, p.name, p.color, p.avatar, COUNT(v.movie_id) as count
FROM persons p
LEFT JOIN viewers v ON p.id = v.person_id
GROUP BY p.id
ORDER BY count DESC
");
return $stmt->fetchAll();
}
function statsByMonth($limit = 12) {
$db = getDB();
$stmt = $db->prepare("
SELECT strftime('%Y-%m', watch_date) as month, COUNT(*) as count
FROM movies
WHERE watch_date IS NOT NULL
GROUP BY month
ORDER BY month DESC
LIMIT ?
");
$stmt->execute([$limit]);
return $stmt->fetchAll();
}
function statsByYear() {
$db = getDB();
$stmt = $db->query("
SELECT strftime('%Y', watch_date) as year, COUNT(*) as count
FROM movies
WHERE watch_date IS NOT NULL
GROUP BY year
ORDER BY year DESC
");
return $stmt->fetchAll();
}
// ===== 年度报告 =====
function getAnnualStats($year) {
$db = getDB();
$stats = [];
// 获取当前管理员
$admin = getCurrentAdmin();
$self_person_id = $admin['person_id'] ?? null;
$stats['self_person_name'] = $admin['person_name'] ?? null;
$stats['self_person_avatar'] = $admin['person_avatar'] ?? null;
if (!$self_person_id) {
// 未关联观影人,返回空数据
$stats['total'] = 0;
$stats['days'] = 0;
$stats['top_cinema'] = '无';
$stats['top_cinema_count'] = 0;
$stats['top_person'] = '无';
$stats['top_person_count'] = 0;
$stats['first_date'] = null;
$stats['last_date'] = null;
$stats['most_viewers_movie'] = '无';
$stats['most_viewers_count'] = 0;
$stats['month_distribution'] = array_fill(1, 12, 0);
$stats['weekday_distribution'] = ['日'=>0,'一'=>0,'二'=>0,'三'=>0,'四'=>0,'五'=>0,'六'=>0];
$stats['avg_per_month'] = 0;
$stats['avg_days_between'] = 0;
$stats['keywords'] = [];
$stats['recent_movies'] = [];
$stats['my_movies'] = [];
$stats['common_viewers'] = [];
return $stats;
}
// 1. 总观影数(当前用户参与的电影)
$stmt = $db->prepare("
SELECT COUNT(DISTINCT m.id)
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
");
$stmt->execute([$year, $self_person_id]);
$stats['total'] = (int)$stmt->fetchColumn();
// 2. 观影天数
$stmt = $db->prepare("
SELECT COUNT(DISTINCT m.watch_date)
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
AND m.watch_date IS NOT NULL
");
$stmt->execute([$year, $self_person_id]);
$stats['days'] = (int)$stmt->fetchColumn();
// 3. 最常去的影院
$stmt = $db->prepare("
SELECT c.name, COUNT(m.id) as count
FROM movies m
JOIN viewers v ON m.id = v.movie_id
LEFT JOIN cinemas c ON m.cinema_id = c.id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
AND m.cinema_id IS NOT NULL
GROUP BY m.cinema_id
ORDER BY count DESC LIMIT 1
");
$stmt->execute([$year, $self_person_id]);
$top_cinema = $stmt->fetch();
$stats['top_cinema'] = $top_cinema ? $top_cinema['name'] : '无';
$stats['top_cinema_count'] = $top_cinema ? (int)$top_cinema['count'] : 0;
// 4. 最常一起观影的人(与当前用户同场最多的人)
// 先获取当前用户看过的所有电影ID
$stmt = $db->prepare("
SELECT DISTINCT m.id
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
");
$stmt->execute([$year, $self_person_id]);
$movie_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
// 共同观影人统计
$common_viewers = [];
if (!empty($movie_ids)) {
$placeholders = implode(',', array_fill(0, count($movie_ids), '?'));
$params = array_merge($movie_ids, [$self_person_id]);
$stmt = $db->prepare("
SELECT p.id, p.name, p.avatar, COUNT(*) as count
FROM viewers v
JOIN persons p ON v.person_id = p.id
WHERE v.movie_id IN ($placeholders)
AND v.person_id != ?
GROUP BY v.person_id
ORDER BY count DESC
");
$stmt->execute($params);
$common_viewers = $stmt->fetchAll();
}
$stats['common_viewers'] = $common_viewers;
// 最常一起观影的人(取第一个)
$stats['top_person'] = !empty($common_viewers) ? $common_viewers[0]['name'] : '无';
$stats['top_person_count'] = !empty($common_viewers) ? (int)$common_viewers[0]['count'] : 0;
// 5. 最早和最晚日期
$stmt = $db->prepare("
SELECT MIN(m.watch_date) as first, MAX(m.watch_date) as last
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
AND m.watch_date IS NOT NULL
");
$stmt->execute([$year, $self_person_id]);
$row = $stmt->fetch();
$stats['first_date'] = $row['first'];
$stats['last_date'] = $row['last'];
// 6. 单次最多人的电影(当前用户参与的电影中)
$stmt = $db->prepare("
SELECT m.id, m.title, COUNT(v.id) as viewer_count
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND m.id IN (SELECT movie_id FROM viewers WHERE person_id = ?)
GROUP BY m.id
ORDER BY viewer_count DESC LIMIT 1
");
$stmt->execute([$year, $self_person_id]);
$most_viewers = $stmt->fetch();
$stats['most_viewers_movie'] = $most_viewers ? $most_viewers['title'] : '无';
$stats['most_viewers_count'] = $most_viewers ? (int)$most_viewers['viewer_count'] : 0;
// 7. 月份分布
$stmt = $db->prepare("
SELECT strftime('%m', m.watch_date) as month, COUNT(*) as count
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
AND m.watch_date IS NOT NULL
GROUP BY month
ORDER BY month
");
$stmt->execute([$year, $self_person_id]);
$month_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$stats['month_distribution'] = [];
for ($m=1; $m<=12; $m++) {
$key = str_pad($m, 2, '0', STR_PAD_LEFT);
$stats['month_distribution'][$m] = isset($month_data[$key]) ? (int)$month_data[$key] : 0;
}
// 8. 星期分布
$stmt = $db->prepare("
SELECT strftime('%w', m.watch_date) as wday, COUNT(*) as count
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
AND m.watch_date IS NOT NULL
GROUP BY wday
ORDER BY wday
");
$stmt->execute([$year, $self_person_id]);
$wday_data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$weekdays = ['日','一','二','三','四','五','六'];
$stats['weekday_distribution'] = [];
foreach ($weekdays as $idx => $name) {
$stats['weekday_distribution'][$name] = isset($wday_data[$idx]) ? (int)$wday_data[$idx] : 0;
}
// 9. 月均和频率
$stats['avg_per_month'] = $stats['total'] > 0 ? round($stats['total'] / 12, 1) : 0;
$stats['avg_days_between'] = $stats['total'] > 0 ? round(365 / $stats['total'], 1) : 0;
// 10. 关键词(基于当前用户电影的备注)
$stmt = $db->prepare("
SELECT m.remark
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
AND m.remark IS NOT NULL
AND m.remark != ''
");
$stmt->execute([$year, $self_person_id]);
$remarks = $stmt->fetchAll(PDO::FETCH_COLUMN);
$word_count = [];
foreach ($remarks as $text) {
$words = preg_split('/[\s,。.!?、;;:]+/', $text, -1, PREG_SPLIT_NO_EMPTY);
foreach ($words as $w) {
$w = trim($w);
if (mb_strlen($w) >= 2) {
if (!isset($word_count[$w])) $word_count[$w] = 0;
$word_count[$w]++;
}
}
}
arsort($word_count);
$stats['keywords'] = array_slice(array_keys($word_count), 0, 5);
// 11. 最近5部(当前用户参与的)
$stmt = $db->prepare("
SELECT DISTINCT m.title
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
ORDER BY m.watch_date DESC
LIMIT 5
");
$stmt->execute([$year, $self_person_id]);
$stats['recent_movies'] = $stmt->fetchAll(PDO::FETCH_COLUMN);
// 12. 我的观影记录(含海报)
$stmt = $db->prepare("
SELECT m.id, m.title, m.watch_date, m.poster_path
FROM movies m
JOIN viewers v ON m.id = v.movie_id
WHERE strftime('%Y', m.watch_date) = ?
AND v.person_id = ?
GROUP BY m.id
ORDER BY m.watch_date DESC
");
$stmt->execute([$year, $self_person_id]);
$movie_list = $stmt->fetchAll();
// 对每个电影查询一起观影的人(含头像)
foreach ($movie_list as &$movie) {
$stmt2 = $db->prepare("
SELECT p.id, p.name, p.avatar
FROM viewers v
LEFT JOIN persons p ON v.person_id = p.id
WHERE v.movie_id = ?
AND v.person_id != ?
");
$stmt2->execute([$movie['id'], $self_person_id]);
$movie['viewers'] = $stmt2->fetchAll();
}
$stats['my_movies'] = $movie_list;
return $stats;
}
function getYearsWithData() {
$db = getDB();
$stmt = $db->query("SELECT DISTINCT strftime('%Y', watch_date) as year FROM movies WHERE watch_date IS NOT NULL ORDER BY year DESC");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}