Files
SunShineMusic/upload.php
2025-09-24 14:15:48 +00:00

443 lines
24 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
// 音乐上传功能(已登录用户自动识别)
require_once 'db_config.php';
$message = '';
$message_type = '';
// 启动会话以检查登录状态
session_start();
// 音乐分类映射
$categories = [
'cantonese' => '粤语歌曲',
'mandarin' => '国语歌曲',
'waiyu' => '外语歌曲',
'classic' => '经典老歌',
'other' => '其他音乐'
];
// 检查用户是否登录
$isLoggedIn = isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === true;
$uploaderName = $isLoggedIn ? $_SESSION['user_info']['username'] : '匿名用户';
// 处理上传
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_music'])) {
// 验证表单数据
$title = trim($_POST['title'] ?? '');
$artist = trim($_POST['artist'] ?? '');
// 如果用户已登录,强制使用会话中的用户名,忽略表单提交的值
$uploader = $isLoggedIn ? $_SESSION['user_info']['username'] : trim($_POST['uploader'] ?? '匿名用户');
$category = $_POST['category'] ?? 'other';
$description = trim($_POST['description'] ?? '');
$bvid = trim($_POST['bvid'] ?? '');
$duration = trim($_POST['duration'] ?? '');
// 验证时长格式
if (!empty($duration) && !preg_match('/^\d+:\d{2}$/', $duration)) {
$message = "时长格式不正确,请使用 MM:SS 格式(例如 3:45 表示3分45秒";
$message_type = "error";
} elseif (empty($title) || empty($artist)) {
$message = "歌曲名和歌手名不能为空";
$message_type = "error";
} elseif (!isset($_FILES['audio_file']) || $_FILES['audio_file']['error'] !== UPLOAD_ERR_OK) {
$message = "请选择要上传的音频文件";
$message_type = "error";
} else {
// 验证BV号格式
if (!empty($bvid) && !preg_match('/^BV[0-9A-Za-z]+$/', $bvid)) {
$message = "BV号格式不正确应为以BV开头的字符串如BV1Va4y1n7HN";
$message_type = "error";
} else {
// 处理文件上传
$upload_dir = 'uploads/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
$file_ext = pathinfo($_FILES['audio_file']['name'], PATHINFO_EXTENSION);
$allowed_ext = ['mp3', 'wav', 'flac', 'm4a'];
if (!in_array(strtolower($file_ext), $allowed_ext)) {
$message = "不支持的文件格式仅支持mp3, wav, flac, m4a";
$message_type = "error";
} else {
// 检查文件大小限制30MB
$max_file_size = 30 * 1024 * 1024; // 30MB
if ($_FILES['audio_file']['size'] > $max_file_size) {
$message = "文件大小超过限制30MB";
$message_type = "error";
} else {
// 生成唯一文件名
$file_name = uniqid('music_') . '.' . $file_ext;
$target_path = $upload_dir . $file_name;
if (move_uploaded_file($_FILES['audio_file']['tmp_name'], $target_path)) {
// 使用用户输入的时长,如果为空则使用默认值
$final_duration = !empty($duration) ? $duration : "0:00";
// 保存到待审核表
try {
$stmt = $conn->prepare("INSERT INTO pending_music
(uploader_name, title, artist, category, description, file_path, duration, upload_time, bvid)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), ?)");
$stmt->execute([$uploader, $title, $artist, $category, $description, $target_path, $final_duration, $bvid]);
$message = "上传成功,已进入待审核队列,感谢您的分享!";
$message_type = "success";
// 清空表单
$_POST = [];
} catch (PDOException $e) {
$message = "数据库错误: " . $e->getMessage();
$message_type = "error";
unlink($target_path); // 数据库错误时删除已上传文件
}
} else {
$message = "文件上传失败";
$message_type = "error";
}
}
}
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传音乐 - 音乐分享平台</title>
<link rel="icon" href="./static/icon/icon.png" type="image/png">
<link rel="alternate icon" href="./static/icon/icon.ico" type="image/x-icon">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@font-face {
font-family: "MyCustomFont";
src: url("./static/font/bbc.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
/* === 全局变量定义 === */
:root {
--primary-color: #2c3e50;
--primary-hover: #34495e;
--accent-color: #e74c3c;
--bg-color: #f7f9fc;
--text-color: #333;
--muted-color: #7f8c8d;
--card-bg: #ffffff;
--card-shadow: 0 4px 12px rgba(0,0,0,0.08);
--border-color: #dcdfe6;
--main-font: "MyCustomFont", sans-serif; /* 自定义字体变量 */
}
body {
font-family: var(--main-font);
margin: 0;
padding: 0;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
h1, h2, h3 { color: var(--primary-color); }
h1 { font-size: 1.8rem; text-align: center; margin: 1rem 0; }
h2 { font-size: 1.4rem; margin-bottom: 0.5rem; }
.desktop .app-container { display: flex; min-height: 100vh; }
.desktop .main-wrapper { flex: 1; display: flex; flex-direction: column; margin-left: 280px; }
.desktop .main-content { flex: 1; max-width: 1200px; width: 100%; margin: 0 auto; padding: 1rem; box-sizing: border-box; }
.mobile .app-container { display: block; min-height: 100vh; }
.mobile .main-wrapper { margin-left: 0; }
.mobile .main-content { width: 100%; padding: 0.5rem; box-sizing: border-box; }
.top-bar {
display: flex; justify-content: space-between; align-items: center;
padding: 1rem 1.5rem; background-color: var(--card-bg);
box-shadow: 0 2px 8px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 100;
}
.site-title { margin: 0; font-size: 1.4rem; color: var(--primary-color); }
.theme-toggle, .user-menu-btn {
background: none; border: 1px solid var(--border-color); color: var(--text-color);
padding: 0.5rem 0.8rem; border-radius: 20px; cursor: pointer;
display: flex; align-items: center; gap: 0.5rem; transition: all 0.3s ease; text-decoration: none;
}
.theme-toggle:hover, .user-menu-btn:hover { background-color: var(--primary-color); color: white; border-color: var(--primary-color); }
.form-group { margin-bottom: 1.2rem; text-align: left; }
.form-group label { display: block; margin-bottom: 0.5rem; color: var(--primary-color); font-weight: 500; }
.form-group.required label::after { content: " *"; color: var(--accent-color); }
.form-control {
width: 100%; padding: 0.8rem; border: 1px solid var(--border-color);
border-radius: 8px; font-size: 1rem; box-sizing: border-box;
background-color: var(--bg-color); color: var(--text-color);
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.form-control:read-only { background-color: #e9ecef; cursor: not-allowed; color: #495057; }
.form-control:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(44, 62, 80, 0.1); }
.error-input { border-color: var(--accent-color) !important; box-shadow: 0 0 0 2px rgba(231, 76, 60, 0.1) !important; }
.submit-btn {
background-color: var(--primary-color); color: white; border: none;
padding: 0.8rem 2rem; border-radius: 25px; font-size: 1rem; cursor: pointer;
transition: background-color 0.3s ease, transform 0.1s ease; width: 100%;
box-sizing: border-box; display: flex; align-items: center; justify-content: center; gap: 0.5rem;
}
.submit-btn:hover { background-color: var(--primary-hover); }
.submit-btn:active { transform: translateY(2px); }
.file-info { margin-top: 0.5rem; font-size: 0.85rem; color: var(--muted-color); }
.field-hint { margin-top: 0.3rem; font-size: 0.8rem; color: var(--muted-color); font-style: italic; }
.message { padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center; font-size: 0.95rem; transition: opacity 0.3s ease, transform 0.3s ease; }
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
.music-item { background-color: var(--card-bg); border-radius: 12px; padding: 1.2rem; box-shadow: var(--card-shadow); margin-bottom: 1.2rem; }
.artist-info { color: var(--muted-color); font-size: 0.9rem; margin-bottom: 1rem; }
.back-to-top {
position: fixed; bottom: 30px; right: 30px; width: 50px; height: 50px;
background-color: var(--primary-color); color: white; border-radius: 50%;
display: flex; align-items: center; justify-content: center; font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.2); cursor: pointer;
opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 999;
}
.back-to-top.show { opacity: 1; visibility: visible; }
.back-to-top:hover { background-color: var(--primary-hover); transform: translateY(-5px); }
</style>
</head>
<body>
<div class="app-container">
<div class="main-wrapper">
<header class="top-bar">
<h1 class="site-title">
<img src="./static/icon/icon.png" alt="音乐图标" style="width: 24px; height: 24px; vertical-align: middle; margin-right: 8px;">
<a href="./index.php" style="text-decoration: none; color: inherit;">音乐分享平台</a>
</h1>
<?php if ($isLoggedIn): ?>
<a href="user_center.php" class="user-menu-btn"><i class="fas fa-user"></i> <?php echo htmlspecialchars($uploaderName); ?></a>
<?php else: ?>
<a href="login.php" class="user-menu-btn"><i class="fas fa-sign-in-alt"></i> 登录</a>
<?php endif; ?>
</header>
<main class="main-content">
<div class="music-item">
<h2><i class="fas fa-cloud-upload-alt"></i> 上传音乐</h2>
<p class="artist-info">
<?php if ($isLoggedIn): ?>
您好,<strong><?php echo htmlspecialchars($uploaderName); ?></strong>。您已登录,上传将自动记录您的昵称。
<?php else: ?>
您当前未登录,上传后将以“匿名用户”身份显示。<a href="login.php" style="color: var(--primary-color);">登录</a>后可获得更完整的服务。
<?php endif; ?>
</p>
<?php if ($message): ?>
<div class="message <?php echo $message_type; ?>"><?php echo htmlspecialchars($message); ?></div>
<?php endif; ?>
<form id="upload-form" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="uploader">您的昵称</label>
<input type="text" id="uploader" name="uploader" class="form-control"
value="<?php echo htmlspecialchars($isLoggedIn ? $uploaderName : ($_POST['uploader'] ?? '匿名用户')); ?>"
<?php echo $isLoggedIn ? 'readonly' : ''; ?>>
<?php if ($isLoggedIn): ?>
<div class="field-hint">已为您自动填充昵称,该字段不可修改。</div>
<?php else: ?>
<div class="field-hint">未登录状态下,默认为“匿名用户”。</div>
<?php endif; ?>
</div>
<div class="form-group required">
<label for="title">歌曲名称</label>
<input type="text" id="title" name="title" class="form-control" value="<?php echo htmlspecialchars($_POST['title'] ?? ''); ?>" required>
</div>
<div class="form-group required">
<label for="artist">歌手/乐队</label>
<input type="text" id="artist" name="artist" class="form-control" value="<?php echo htmlspecialchars($_POST['artist'] ?? ''); ?>" required>
</div>
<div class="form-group required">
<label for="category">音乐分类</label>
<select id="category" name="category" class="form-control" required>
<?php foreach ($categories as $key => $name): ?>
<option value="<?php echo $key; ?>" <?php echo (($_POST['category'] ?? '') == $key) ? 'selected' : ''; ?>>
<?php echo $name; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group required">
<label for="duration">音频时长</label>
<input type="text" id="duration" name="duration" class="form-control"
value="<?php echo htmlspecialchars($_POST['duration'] ?? ''); ?>"
placeholder="请输入时长,格式为 MM:SS例如 3:45 表示3分45秒" required>
<div class="field-hint">请按照实际时长填写,格式为 分钟:秒数(例如 4:30 表示4分30秒</div>
</div>
<div class="form-group">
<label for="bvid">B站视频BV号 (可选)</label>
<input type="text" id="bvid" name="bvid" class="form-control"
value="<?php echo htmlspecialchars($_POST['bvid'] ?? ''); ?>"
placeholder="例如: BV1Va4y1n7HN">
<div class="field-hint">若该音乐来自B站视频可填写对应的BV号以BV开头方便用户查看视频来源</div>
</div>
<div class="form-group">
<label for="description">歌曲描述 (可选)</label>
<textarea id="description" name="description" class="form-control" rows="3"><?php echo htmlspecialchars($_POST['description'] ?? ''); ?></textarea>
</div>
<div class="form-group required">
<label for="audio_file">音频文件</label>
<input type="file" id="audio_file" name="audio_file" class="form-control" accept="audio/*" required>
<small class="file-info">支持格式mp3, wav, flac, m4a文件大小不超过30MB</small>
</div>
<button type="submit" name="upload_music" class="submit-btn">
<i class="fas fa-upload"></i> 上传音乐
</button>
</form>
</div>
</main>
</div>
</div>
<script>
// 检测设备类型并添加相应类名
document.addEventListener('DOMContentLoaded', function() {
const isMobile = window.innerWidth < 768;
document.body.classList.add(isMobile ? 'mobile' : 'desktop');
// 表单验证增强
const uploadForm = document.getElementById('upload-form');
if (uploadForm) {
uploadForm.addEventListener('submit', function(e) {
let isValid = true;
const titleInput = document.getElementById('title');
const artistInput = document.getElementById('artist');
const durationInput = document.getElementById('duration');
const bvidInput = document.getElementById('bvid');
// 清除之前的错误状态
titleInput.classList.remove('error-input');
artistInput.classList.remove('error-input');
durationInput.classList.remove('error-input');
bvidInput.classList.remove('error-input');
// 验证必填字段
if (!titleInput.value.trim()) {
titleInput.classList.add('error-input');
isValid = false;
}
if (!artistInput.value.trim()) {
artistInput.classList.add('error-input');
isValid = false;
}
// 验证时长格式
if (durationInput.value.trim() && !/^\d+:\d{2}$/.test(durationInput.value.trim())) {
durationInput.classList.add('error-input');
isValid = false;
}
// 验证BV号格式如果填写了
if (bvidInput.value.trim() && !/^BV[0-9A-Za-z]+$/.test(bvidInput.value.trim())) {
bvidInput.classList.add('error-input');
isValid = false;
}
if (!isValid) {
e.preventDefault();
// 清除已有的错误消息
const existingErrors = document.querySelectorAll('.message.error');
existingErrors.forEach(el => el.remove());
// 显示错误消息
const errorMsg = document.createElement('div');
errorMsg.className = 'message error';
if (durationInput.classList.contains('error-input')) {
errorMsg.textContent = '时长格式不正确,请使用 MM:SS 格式(例如 3:45 表示3分45秒';
} else if (bvidInput.classList.contains('error-input')) {
errorMsg.textContent = 'BV号格式不正确应为以BV开头的字符串如BV1Va4y1n7HN';
} else {
errorMsg.textContent = '请填写所有必填字段';
}
errorMsg.style.marginBottom = '1rem';
uploadForm.parentNode.insertBefore(errorMsg, uploadForm);
// 3秒后自动移除错误消息
setTimeout(() => {
errorMsg.style.opacity = '0';
setTimeout(() => errorMsg.remove(), 300);
}, 3000);
}
});
}
// 文件选择预览(大小和名称)
const fileInput = document.getElementById('audio_file');
if (fileInput) {
fileInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
const fileInfo = document.querySelector('.file-info') || document.createElement('div');
fileInfo.className = 'file-info';
// 格式化文件大小
const fileSize = this.files[0].size;
let sizeText;
if (fileSize < 1024 * 1024) {
sizeText = (fileSize / 1024).toFixed(2) + ' KB';
} else {
sizeText = (fileSize / (1024 * 1024)).toFixed(2) + ' MB';
}
fileInfo.innerHTML = `已选择: ${this.files[0].name} (${sizeText})`;
if (!document.querySelector('.file-info')) {
this.parentNode.appendChild(fileInfo);
}
}
});
}
// 回到顶部按钮功能
const backToTopBtn = document.createElement('button');
backToTopBtn.className = 'back-to-top';
backToTopBtn.innerHTML = '<i class="fas fa-chevron-up"></i>';
backToTopBtn.setAttribute('aria-label', '回到顶部');
document.body.appendChild(backToTopBtn);
window.addEventListener('scroll', () => {
if (window.scrollY > 300) {
backToTopBtn.classList.add('show');
} else {
backToTopBtn.classList.remove('show');
}
});
backToTopBtn.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
// 显示消息动画
const messageEl = document.querySelector('.message');
if (messageEl) {
setTimeout(() => {
messageEl.style.opacity = 1;
}, 100);
// 成功消息5秒后自动消失
if (messageEl.classList.contains('success')) {
setTimeout(() => {
messageEl.style.opacity = 0;
setTimeout(() => messageEl.remove(), 300);
}, 5000);
}
}
});
</script>
</body>
</html>