Files
laoyang 40e1bc5e56 feat: 完善电影记录系统源码与项目文档
功能模块:
- 观影记录:列表(瀑布流)/ 添加 / 编辑 / 详情 / 删除,删除时同步清理图片文件
- 首页快捷搜索 + 高级搜索(片名、影院、观影人、日期区间组合筛选)
- 影院管理:三段式信息 + 50 种预设配色,删除已引用影院时二次确认
- 观影人管理:头像裁剪上传 + 专属配色,已引用者禁止删除
- 图片处理:Cropper.js 裁剪票根/海报/观影照片/头像,多图上传,首字母 SVG 头像兜底
- OMDb 自动获取影片信息(fetch_movie.php 代理,返回 JSON)
- CSV 批量导入:UTF-8/GBK 自动识别,影院去重,事务保护,逐行错误报告
- 统计仪表板:总览 + 按影院/观影人/年份/近 12 个月排行
- 年度报告:按账号关联的观影人视角生成,含 Chart.js 月度与星期分布图
- 多用户:账号增删改、账号与观影人绑定、修改自己的用户名与密码
- 登录鉴权:Session + password_hash,全站页面登录校验

文档:
- 重写 README:功能特性、技术栈、目录结构、数据库结构、部署与使用指南、接口说明、CSV 格式、安全注意事项、已知限制
- 新增 .gitignore,排除 db/ 与 uploads/ 等运行时数据
2026-09-21 08:20:56 +08:00

740 lines
25 KiB
PHP
Raw Permalink 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
// ============================================
// 文件: config.php (完整版)
// 说明: 包含所有核心函数,年度报告支持海报和共同观影人
// ============================================
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start();
// 数据库路径
define('DB_PATH', __DIR__ . '/db/movies.db');
// 上传目录
define('UPLOAD_DIR', __DIR__ . '/uploads/');
define('POSTER_DIR', UPLOAD_DIR . 'posters/');
define('TICKET_DIR', UPLOAD_DIR . 'tickets/');
define('PHOTO_DIR', UPLOAD_DIR . 'photos/');
define('AVATAR_DIR', UPLOAD_DIR . 'avatars/');
// 创建上传目录
foreach ([POSTER_DIR, TICKET_DIR, PHOTO_DIR, AVATAR_DIR] as $dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
// 数据库连接
function getDB() {
try {
$db = new PDO('sqlite:' . DB_PATH);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $db;
} catch (PDOException $e) {
die('数据库连接失败: ' . $e->getMessage());
}
}
// 初始化数据库
function initDB() {
$db = getDB();
// 管理员表
$db->exec("
CREATE TABLE IF NOT EXISTS admins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
avatar TEXT
)
");
// 电影院表
$db->exec("
CREATE TABLE IF NOT EXISTS cinemas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
place TEXT NOT NULL,
name TEXT NOT NULL,
address TEXT,
color TEXT DEFAULT '#888888'
)
");
// 电影表
$db->exec("
CREATE TABLE IF NOT EXISTS movies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
info TEXT,
remark TEXT,
poster_path TEXT,
ticket_path TEXT,
photo_path TEXT,
cinema_id INTEGER,
watch_date DATE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (cinema_id) REFERENCES cinemas(id) ON DELETE SET NULL
)
");
// 观影人表
$db->exec("
CREATE TABLE IF NOT EXISTS persons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#888888',
avatar TEXT
)
");
// 观影关联表
$db->exec("
CREATE TABLE IF NOT EXISTS viewers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER NOT NULL,
name TEXT,
person_id INTEGER,
FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE,
FOREIGN KEY (person_id) REFERENCES persons(id) ON DELETE SET NULL
)
");
// 多照片表
$db->exec("
CREATE TABLE IF NOT EXISTS movie_photos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER NOT NULL,
photo_path TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE
)
");
// ===== 迁移字段 =====
try { $db->exec("ALTER TABLE cinemas ADD COLUMN color TEXT DEFAULT '#888888'"); } catch (PDOException $e) {}
try { $db->exec("ALTER TABLE admins ADD COLUMN avatar TEXT"); } catch (PDOException $e) {}
try { $db->exec("ALTER TABLE persons ADD COLUMN avatar TEXT"); } catch (PDOException $e) {}
try { $db->exec("ALTER TABLE movies ADD COLUMN remark TEXT"); } catch (PDOException $e) {}
try { $db->exec("ALTER TABLE viewers ADD COLUMN person_id INTEGER REFERENCES persons(id) ON DELETE SET NULL"); } catch (PDOException $e) {}
// 新增:管理员关联观影人
try { $db->exec("ALTER TABLE admins ADD COLUMN person_id INTEGER REFERENCES persons(id) ON DELETE SET NULL"); } catch (PDOException $e) {}
// ===== 迁移旧数据 =====
// 将 viewers.name 迁移到 persons
$stmt = $db->query("SELECT DISTINCT name FROM viewers WHERE name != '' AND name IS NOT NULL");
$names = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($names as $name) {
$chk = $db->prepare("SELECT id FROM persons WHERE name = ?");
$chk->execute([$name]);
if (!$chk->fetch()) {
$ins = $db->prepare("INSERT INTO persons (name) VALUES (?)");
$ins->execute([$name]);
}
}
$update = $db->prepare("UPDATE viewers SET person_id = (SELECT id FROM persons WHERE persons.name = viewers.name) WHERE person_id IS NULL AND name IS NOT NULL");
$update->execute();
// 默认管理员
$stmt = $db->prepare("SELECT COUNT(*) FROM admins");
$stmt->execute();
if ($stmt->fetchColumn() == 0) {
$hashed = password_hash('123456', PASSWORD_DEFAULT);
$stmt = $db->prepare("INSERT INTO admins (username, password) VALUES (?, ?)");
$stmt->execute(['admin', $hashed]);
}
}
// 登录检查
function checkLogin() {
if (!isset($_SESSION['admin_id'])) {
header('Location: login.php');
exit;
}
}
// 获取当前管理员(含关联观影人信息)
function getCurrentAdmin() {
if (isset($_SESSION['admin_id'])) {
$db = getDB();
$stmt = $db->prepare("
SELECT a.*, p.id as person_id, p.name as person_name, p.color as person_color, p.avatar as person_avatar
FROM admins a
LEFT JOIN persons p ON a.person_id = p.id
WHERE a.id = ?
");
$stmt->execute([$_SESSION['admin_id']]);
return $stmt->fetch();
}
return null;
}
// 获取所有管理员(含关联观影人)
function getAllAdmins() {
$db = getDB();
$stmt = $db->query("
SELECT a.id, a.username, a.avatar, a.person_id, p.name as person_name
FROM admins a
LEFT JOIN persons p ON a.person_id = p.id
ORDER BY a.id
");
return $stmt->fetchAll();
}
// 添加管理员(支持关联观影人)
function addAdmin($username, $password, $avatar = null, $person_id = null) {
$db = getDB();
$stmt = $db->prepare("SELECT COUNT(*) FROM admins WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetchColumn() > 0) return false;
$hashed = password_hash($password, PASSWORD_DEFAULT);
$stmt = $db->prepare("INSERT INTO admins (username, password, avatar, person_id) VALUES (?, ?, ?, ?)");
return $stmt->execute([$username, $hashed, $avatar, $person_id]);
}
// 删除管理员
function deleteAdmin($id) {
$db = getDB();
$stmt = $db->query("SELECT COUNT(*) FROM admins");
if ($stmt->fetchColumn() <= 1) return false;
if ($id == $_SESSION['admin_id']) return false;
$stmt = $db->prepare("DELETE FROM admins WHERE id = ?");
return $stmt->execute([$id]);
}
// 更新管理员的观影人关联
function updateAdminPersonId($admin_id, $person_id) {
$db = getDB();
$stmt = $db->prepare("UPDATE admins SET person_id = ? WHERE id = ?");
return $stmt->execute([$person_id, $admin_id]);
}
// 生成唯一文件名
function uniqueFilename($original) {
$ext = strtolower(pathinfo($original, PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($ext, $allowed)) {
$ext = 'jpg';
}
return date('YmdHis') . '_' . uniqid() . '.' . $ext;
}
// 安全输出
function h($str) {
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
// 海报 URL
function posterUrl($path) {
if ($path && file_exists(__DIR__ . '/' . $path)) {
return '/' . $path;
}
return '/assets/placeholder-poster.jpg';
}
// 图片 URL
function imageUrl($path) {
if ($path && file_exists(__DIR__ . '/' . $path)) {
return '/' . $path;
}
return null;
}
// ===== 头像处理 =====
function getAvatarUrl($avatar_path, $name = '') {
if ($avatar_path && file_exists(__DIR__ . '/' . $avatar_path)) {
return '/' . $avatar_path;
}
if (!empty($name)) {
$letter = mb_substr($name, 0, 1, 'UTF-8');
$colors = ['#e74c3c','#e67e22','#f1c40f','#2ecc71','#3498db','#9b59b6','#1abc9c','#e84393','#00b894','#0984e3'];
$color = $colors[abs(crc32($name)) % count($colors)];
return "data:image/svg+xml," . urlencode("<svg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'><rect width='40' height='40' fill='{$color}'/><text x='20' y='28' text-anchor='middle' font-size='20' fill='#fff' font-family='sans-serif' font-weight='bold'>{$letter}</text></svg>");
}
return '/assets/default-avatar.png';
}
// ===== 保存 Base64 图片 =====
function saveBase64Image($base64_data, $save_dir, $prefix = 'avatar') {
if (strpos($base64_data, 'base64,') !== false) {
$base64_data = substr($base64_data, strpos($base64_data, 'base64,') + 7);
}
$binary = base64_decode($base64_data);
if ($binary === false) return false;
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_buffer($finfo, $binary);
finfo_close($finfo);
$ext_map = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp'
];
if (!isset($ext_map[$mime])) return false;
$ext = $ext_map[$mime];
$filename = date('YmdHis') . '_' . uniqid() . '_' . $prefix . '.' . $ext;
$filepath = $save_dir . $filename;
if (file_put_contents($filepath, $binary) === false) return false;
return str_replace(__DIR__ . '/', '', $filepath);
}
// ===== 电影列表 =====
function getMovies() {
$db = getDB();
$stmt = $db->query("
SELECT m.*, c.place, c.name as cinema_name, c.address as cinema_address, c.color as cinema_color
FROM movies m
LEFT JOIN cinemas c ON m.cinema_id = c.id
ORDER BY m.watch_date DESC, m.created_at DESC
");
$movies = $stmt->fetchAll();
attachViewersToMovies($movies);
return $movies;
}
function getMovie($id) {
$db = getDB();
$stmt = $db->prepare("
SELECT m.*, c.place, c.name as cinema_name, c.address as cinema_address, c.color as cinema_color
FROM movies m
LEFT JOIN cinemas c ON m.cinema_id = c.id
WHERE m.id = ?
");
$stmt->execute([$id]);
$movie = $stmt->fetch();
if ($movie) {
$stmt2 = $db->prepare("
SELECT v.id, v.movie_id, v.person_id,
COALESCE(p.name, v.name) as name,
p.color as person_color,
p.avatar as person_avatar
FROM viewers v
LEFT JOIN persons p ON v.person_id = p.id
WHERE v.movie_id = ?
ORDER BY v.id
");
$stmt2->execute([$id]);
$movie['viewers'] = $stmt2->fetchAll();
$stmt3 = $db->prepare("SELECT * FROM movie_photos WHERE movie_id = ? ORDER BY id");
$stmt3->execute([$id]);
$movie['photos'] = $stmt3->fetchAll();
}
return $movie;
}
function attachViewersToMovies(&$movies) {
if (empty($movies)) return;
$db = getDB();
$movie_ids = array_column($movies, 'id');
if (empty($movie_ids)) return;
$placeholders = implode(',', array_fill(0, count($movie_ids), '?'));
$stmt = $db->prepare("
SELECT v.movie_id, p.id as person_id, p.name, p.color, p.avatar
FROM viewers v
LEFT JOIN persons p ON v.person_id = p.id
WHERE v.movie_id IN ($placeholders)
ORDER BY v.id
");
$stmt->execute($movie_ids);
$rows = $stmt->fetchAll();
$viewers_map = [];
foreach ($rows as $row) {
$movie_id = $row['movie_id'];
if (!isset($viewers_map[$movie_id])) $viewers_map[$movie_id] = [];
$viewers_map[$movie_id][] = [
'person_id' => $row['person_id'],
'name' => $row['name'] ?? '未知',
'color' => $row['color'] ?? '#888888',
'avatar' => $row['avatar']
];
}
foreach ($movies as &$movie) {
$movie['viewers'] = $viewers_map[$movie['id']] ?? [];
}
}
// ===== 影院 =====
function getCinemas() {
$db = getDB();
$stmt = $db->query("SELECT * FROM cinemas ORDER BY place, name");
return $stmt->fetchAll();
}
// ===== 搜索 =====
function searchMovies($keyword) {
return advancedSearch(['title' => $keyword]);
}
function advancedSearch($params) {
$db = getDB();
$sql = "
SELECT DISTINCT m.*,
c.place, c.name as cinema_name, c.address as cinema_address, c.color as cinema_color
FROM movies m
LEFT JOIN cinemas c ON m.cinema_id = c.id
LEFT JOIN viewers v ON m.id = v.movie_id
LEFT JOIN persons p ON v.person_id = p.id
WHERE 1=1
";
$bind = [];
if (!empty($params['title'])) {
$sql .= " AND m.title LIKE :title";
$bind[':title'] = '%' . $params['title'] . '%';
}
if (!empty($params['cinema_id']) && is_numeric($params['cinema_id'])) {
$sql .= " AND m.cinema_id = :cinema_id";
$bind[':cinema_id'] = (int)$params['cinema_id'];
}
if (!empty($params['person_id']) && is_numeric($params['person_id'])) {
$sql .= " AND v.person_id = :person_id";
$bind[':person_id'] = (int)$params['person_id'];
}
if (!empty($params['date_from'])) {
$sql .= " AND m.watch_date >= :date_from";
$bind[':date_from'] = $params['date_from'];
}
if (!empty($params['date_to'])) {
$sql .= " AND m.watch_date <= :date_to";
$bind[':date_to'] = $params['date_to'];
}
$sql .= " ORDER BY m.watch_date DESC, m.created_at DESC";
$stmt = $db->prepare($sql);
$stmt->execute($bind);
$movies = $stmt->fetchAll();
attachViewersToMovies($movies);
return $movies;
}
// ===== 统计 =====
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);
}
// 初始化数据库
initDB();