Init 1.0 codes

This commit is contained in:
2025-09-20 22:20:08 +08:00
commit 49922cc006
2294 changed files with 426627 additions and 0 deletions

283
developer/dashboard.php Normal file
View File

@@ -0,0 +1,283 @@
<?php
// 引入配置文件
require_once '../config.php';
session_start();
// 检查开发者是否已登录
if (!isset($_SESSION['developer_id'])) {
header('Location: login.php');
exit;
}
$developerId = $_SESSION['developer_id'];
$developerUsername = $_SESSION['developer_username'];
// 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
} else {
// 获取开发者的应用列表
$apps = [];
$stmt = $conn->prepare('SELECT id, name, status, rejection_reason FROM apps WHERE developer_id = ?');
if (!$stmt) {
log_error('获取应用列表查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '获取应用列表时发生错误,请稍后再试';
} else {
$stmt->bind_param('i', $developerId);
if (!$stmt->execute()) {
log_error('获取应用列表查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '获取应用列表时发生错误,请稍后再试';
} else {
$result = $stmt->get_result();
$apps = $result->fetch_all(MYSQLI_ASSOC);
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>开发者仪表盘 - <?php echo APP_STORE_NAME; ?></title>
<!-- Bootstrap CSS -->
<link href="../css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<!-- 自定义CSS -->
<link rel="stylesheet" href="../styles.css">
<!-- SweetAlert2 -->
<script src="/js/sweetalert.js"></script>
<style>
.blur-bg {
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.5);
}
.app-card {
margin-bottom: 1rem;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
.page-transition {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dashboard-container {
max-width: 1200px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
}
.app-list {
margin-top: 20px;
}
.app-item {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
.status-pending {
color: orange;
}
.status-approved {
color: green;
}
.status-rejected {
color: red;
}
.action-buttons {
margin-top: 10px;
}
.action-buttons a {
display: inline-block;
padding: 5px 10px;
background-color: #007BFF;
color: #fff;
text-decoration: none;
border-radius: 3px;
margin-right: 10px;
}
.action-buttons a:hover {
background-color: #0056b3;
}
.add-app {
margin-bottom: 20px;
}
.add-app a {
display: inline-block;
padding: 10px 20px;
background-color: #28a745;
color: #fff;
text-decoration: none;
border-radius: 3px;
}
.add-app a:hover {
background-color: #218838;
}
.logout {
text-align: right;
}
.logout a {
color: #dc3545;
text-decoration: none;
}
.logout a:hover {
text-decoration: underline;
}
</style>
</head>
<body class="page-transition">
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light blur-bg">
<div class="container">
<a href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
<a class="navbar-brand" href="../index.php"><?php echo APP_STORE_NAME; ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="dashboard.php">应用仪表盘</a>
</li>
<li class="nav-item">
<a class="nav-link" href="upload_app.php">上传应用</a>
</li>
<li class="nav-item">
<a class="nav-link" href="profile.php">更改信息</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">退出登录</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="dashboard-container mt-4">
<?php
$rejectedApps = array_filter($apps, function($app) {
return $app['status'] === 'rejected';
});
if (!empty($rejectedApps)):
?>
<div class="alert alert-danger">
<strong>提醒:</strong> 您有 <?php echo count($rejectedApps); ?> 个应用未通过审核,请查看详情。
</div>
<?php endif; ?>
<h1>欢迎,<?php echo htmlspecialchars($developerUsername); ?></h1>
<div class="add-app">
<a href="upload_app.php"><i class="fas fa-upload me-1"></i>上传新应用</a>
</div>
<?php if (isset($error)): ?>
<div style="color: red;"><?php echo $error; ?></div>
<?php endif; ?>
<div class="app-list">
<h2>我的应用</h2>
<?php if (empty($apps)): ?>
<p>您还没有上传任何应用。</p>
<?php else: ?>
<?php foreach ($apps as $app): ?>
<div class="card app-card">
<div class="card-body">
<h5 class="card-title"><?php echo htmlspecialchars($app['name']); ?></h5>
<p class="card-text">
状态:
<?php if ($app['status'] === 'approved'): ?>
<span class="badge bg-success">已通过</span>
<?php elseif ($app['status'] === 'rejected'): ?>
<span class="badge bg-danger">未通过</span>
<div class="alert alert-warning mt-2">
拒绝原因: <?php echo htmlspecialchars($app['rejection_reason']); ?>
</div>
<?php else: ?>
<span class="badge bg-warning">待审核</span>
<?php endif; ?>
</p>
<div class="action-buttons">
<a href="edit_app.php?id=<?php echo $app['id']; ?>" class="btn btn-primary"><i class="fas fa-edit me-1"></i>编辑</a>
<a href="version_control.php?id=<?php echo $app['id']; ?>" class="btn btn-secondary"><i class="fas fa-history me-1"></i>版本控制</a>
<a href="#" class="delete-btn btn btn-danger" data-app-id="<?php echo $app['id']; ?>"><i class="fas fa-trash-alt me-1"></i>删除</a>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<!-- Bootstrap JS and Popper -->
<script src="../js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('page-transition');
// 删除按钮事件处理
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
const appId = this.getAttribute('data-app-id');
const appName = this.closest('.card').querySelector('.card-title').textContent;
Swal.fire({
title: '确认删除',
text: `您确定要删除应用"${appName}"吗?此操作不可撤销。`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: '删除',
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
fetch('delete_app.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `app_id=${encodeURIComponent(appId)}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire('删除成功', data.message, 'success').then(() => {
window.location.reload();
});
} else {
Swal.fire('删除失败', data.message, 'error');
}
})
.catch(error => {
Swal.fire('错误', '删除过程中发生错误', 'error');
});
}
});
});
});
});
</script>
</body>
</html>

81
developer/delete_app.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
// 引入配置文件
require_once '../config.php';
session_start();
// 检查开发者是否已登录
if (!isset($_SESSION['developer_id'])) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => '未授权访问']);
exit;
}
// 验证请求方法和参数
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['app_id'])) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => '无效的请求参数']);
exit;
}
$appId = intval($_POST['app_id']);
$developerId = $_SESSION['developer_id'];
// 检查数据库连接
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
http_response_code(500);
echo json_encode(['success' => false, 'message' => '数据库连接错误']);
exit;
}
// 验证应用所有权
$stmt = $conn->prepare('SELECT id FROM apps WHERE id = ? AND developer_id = ?');
if (!$stmt) {
log_error('验证应用所有权查询准备失败: ' . $conn->error, __FILE__, __LINE__);
http_response_code(500);
echo json_encode(['success' => false, 'message' => '服务器错误']);
exit;
}
$stmt->bind_param('ii', $appId, $developerId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$stmt->close();
http_response_code(403);
echo json_encode(['success' => false, 'message' => '您没有权限删除此应用']);
exit;
}
$stmt->close();
// 开始事务
$conn->begin_transaction();
try {
// 删除应用的版本记录
$stmt = $conn->prepare('DELETE FROM app_versions WHERE app_id = ?');
if (!$stmt) throw new Exception('删除版本记录准备失败: ' . $conn->error);
$stmt->bind_param('i', $appId);
if (!$stmt->execute()) throw new Exception('删除版本记录执行失败: ' . $stmt->error);
$stmt->close();
// 删除应用记录
$stmt = $conn->prepare('DELETE FROM apps WHERE id = ? AND developer_id = ?');
if (!$stmt) throw new Exception('删除应用记录准备失败: ' . $conn->error);
$stmt->bind_param('ii', $appId, $developerId);
if (!$stmt->execute()) throw new Exception('删除应用记录执行失败: ' . $stmt->error);
$stmt->close();
// 提交事务
$conn->commit();
echo json_encode(['success' => true, 'message' => '应用已成功删除']);
} catch (Exception $e) {
// 回滚事务
$conn->rollback();
log_error('删除应用失败: ' . $e->getMessage(), __FILE__, __LINE__);
http_response_code(500);
echo json_encode(['success' => false, 'message' => '删除应用失败: ' . $e->getMessage()]);
}
?>

479
developer/edit_app.php Normal file
View File

@@ -0,0 +1,479 @@
<?php
// 引入配置文件
require_once '../config.php';
session_start();
// 检查开发者是否已登录
if (!isset($_SESSION['developer_id'])) {
header('Location: login.php');
exit;
}
$developerId = $_SESSION['developer_id'];
$error = '';
$success = '';
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header('Location: dashboard.php');
exit;
}
$appId = $_GET['id'];
$app = [];
// 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
header('Location: dashboard.php');
exit;
}
// 获取所有标签
$tags = [];
$tagStmt = $conn->query('SELECT id, name FROM tags');
while ($tag = $tagStmt->fetch_assoc()) {
$tags[] = $tag;
}
$tagStmt->close();
// 获取应用现有标签
$appTags = [];
$appTagStmt = $conn->prepare('SELECT tag_id FROM app_tags WHERE app_id = ?');
$appTagStmt->bind_param('i', $appId);
$appTagStmt->execute();
$appTagResult = $appTagStmt->get_result();
while ($tag = $appTagResult->fetch_assoc()) {
$appTags[] = $tag['tag_id'];
}
$appTagStmt->close();
// 获取应用信息并验证开发者权限
$stmt = $conn->prepare('SELECT id, name, description, version, changelog, age_rating, age_rating_description, platforms, file_path FROM apps WHERE id = ? AND developer_id = ?');
if (!$stmt) {
log_error('获取应用信息查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '获取应用信息时发生错误,请稍后再试';
header('Location: dashboard.php');
exit;
}
$stmt->bind_param('ii', $appId, $developerId);
if (!$stmt->execute()) {
log_error('获取应用信息查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '获取应用信息时发生错误,请稍后再试';
header('Location: dashboard.php');
exit;
}
$result = $stmt->get_result();
$app = $result->fetch_assoc();
if (!$app) {
header('Location: dashboard.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$appName = trim($_POST['name']);
$appDescription = trim($_POST['description']);
$version = trim($_POST['version']);
$changelog = trim($_POST['changelog']);
$ageRating = $_POST['age_rating'];
$ageRatingDescription = trim($_POST['age_rating_description']);
$platforms = $_POST['platforms'] ?? [];
$platforms_json = json_encode($platforms);
$appFilePath = $app['file_path']; // 默认使用现有文件路径
// 获取选中的标签
$selectedTags = $_POST['tags'] ?? [];
// 处理应用文件上传
if (!empty($_FILES['app_file']['tmp_name'])) {
$uploadDir = '../uploads/apps/';
$fileExtension = pathinfo($_FILES['app_file']['name'], PATHINFO_EXTENSION);
$newFileName = uniqid() . '.' . $fileExtension;
$targetPath = $uploadDir . $newFileName;
// 验证文件类型和大小
$allowedTypes = ['apk', 'exe', 'jar', 'crx', 'ini'];
if (!in_array($fileExtension, $allowedTypes)) {
$error = '不支持的文件类型,请上传 ' . implode(', ', $allowedTypes) . ' 格式的文件';
} elseif ($_FILES['app_file']['size'] > 50 * 1024 * 1024) { // 50MB
$error = '文件大小不能超过50MB';
} elseif (!move_uploaded_file($_FILES['app_file']['tmp_name'], $targetPath)) {
$error = '文件上传失败,请检查服务器权限';
} else {
// 删除旧文件
if (file_exists($appFilePath)) {
unlink($appFilePath);
}
$appFilePath = $targetPath;
}
}
// 处理图片删除
if (!empty($_POST['removed_images'])) {
$removedImageIds = explode(',', $_POST['removed_images']);
foreach ($removedImageIds as $imgId) {
if (is_numeric($imgId)) {
// 获取图片路径
$stmt = $conn->prepare("SELECT image_path FROM app_images WHERE id = ?");
$stmt->bind_param("i", $imgId);
$stmt->execute();
$imgResult = $stmt->get_result();
if ($img = $imgResult->fetch_assoc()) {
// 删除文件
if (file_exists($img['image_path'])) {
unlink($img['image_path']);
}
// 删除数据库记录
$deleteStmt = $conn->prepare("DELETE FROM app_images WHERE id = ?");
$deleteStmt->bind_param("i", $imgId);
$deleteStmt->execute();
$deleteStmt->close();
}
$stmt->close();
}
}
}
// 更新应用标签
// 删除现有标签关联
$deleteTagStmt = $conn->prepare('DELETE FROM app_tags WHERE app_id = ?');
$deleteTagStmt->bind_param('i', $appId);
$deleteTagStmt->execute();
$deleteTagStmt->close();
// 添加新标签关联
foreach ($selectedTags as $tagId) {
if (is_numeric($tagId)) {
$tagStmt = $conn->prepare('INSERT INTO app_tags (app_id, tag_id) VALUES (?, ?)');
$tagStmt->bind_param('ii', $appId, $tagId);
$tagStmt->execute();
$tagStmt->close();
}
}
// 处理新图片上传
$imageUploadDir = '../uploads/images/';
$allowedImageTypes = ['jpg', 'jpeg', 'png'];
$maxImages = 5;
$currentImageCount = count($existingImages) - count($removedImageIds ?? []);
if (!empty($_FILES['images']['name'][0]) && empty($error)) {
$newImages = $_FILES['images'];
for ($i = 0; $i < count($newImages['name']); $i++) {
if ($newImages['error'][$i] !== UPLOAD_ERR_OK) continue;
$fileName = $newImages['name'][$i];
$fileTmp = $newImages['tmp_name'][$i];
$fileSize = $newImages['size'][$i];
$fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (!in_array($fileExt, $allowedImageTypes)) {
$error = "图片 {$fileName} 格式不支持仅允许jpg、png";
break;
}
if ($fileSize > 2 * 1024 * 1024) { // 2MB
$error = "图片 {$fileName} 大小超过2MB";
break;
}
if ($currentImageCount >= $maxImages) {
$error = "最多只能上传5张图片";
break;
}
$newFileName = uniqid() . '.' . $fileExt;
$targetPath = $imageUploadDir . $newFileName;
if (move_uploaded_file($fileTmp, $targetPath)) {
// 插入数据库
$stmt = $conn->prepare("INSERT INTO app_images (app_id, image_path) VALUES (?, ?)");
$stmt->bind_param("is", $appId, $targetPath);
$stmt->execute();
$stmt->close();
$currentImageCount++;
} else {
$error = "图片 {$fileName} 上传失败";
break;
}
}
}
// 验证标签选择
if (empty($selectedTags)) {
$error = '至少需要选择一个应用标签';
}
if (empty($appName) || empty($appDescription) || empty($version) || empty($changelog) || empty($ageRating) || empty($ageRatingDescription)) {
$error = '应用名称和描述不能为空';
} else {
// 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
} else {
$platforms = $_POST['platforms'] ?? [];
$platforms_json = json_encode($platforms);
$stmt = $conn->prepare('UPDATE apps SET name = ?, description = ?, version = ?, changelog = ?, age_rating = ?, age_rating_description = ?, platforms = ?, file_path = ?, status = \'pending\' WHERE id = ? AND developer_id = ?');
if (!$stmt) {
log_error('更新应用信息查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '更新应用信息时发生错误,请稍后再试';
} else {
$stmt->bind_param('ssssssssii', $appName, $appDescription, $version, $changelog, $ageRating, $ageRatingDescription, $platforms_json, $appFilePath, $appId, $developerId);
if (!$stmt->execute()) {
log_error('更新应用信息查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '更新应用信息时发生错误,请稍后再试';
} else {
$success = '应用信息更新成功,请等待管理员重新审核';
header('Location: dashboard.php?success=' . urlencode($success));
exit;
$app['name'] = $appName;
$app['description'] = $appDescription;
}
}
}
}
}
?>
<!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 href="../css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
</head>
<body>
<div class="container mt-4">
<h2 class="text-center mb-4">编辑应用</h2>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div class="alert alert-success"><?php echo $success; ?></div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="name" class="form-label">应用名称</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($app['name']); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">应用描述</label>
<textarea class="form-control" id="description" name="description" rows="5" required><?php echo htmlspecialchars($app['description']); ?></textarea>
</div>
<div class="mb-3">
<label for="version" class="form-label">版本号</label>
<input type="text" class="form-control" id="version" name="version" value="<?php echo htmlspecialchars($app['version']); ?>" required>
</div>
<div class="mb-3">
<label for="changelog" class="form-label">更新日志</label>
<textarea class="form-control" id="changelog" name="changelog" rows="3" required><?php echo htmlspecialchars($app['changelog']); ?></textarea>
</div>
<div class="mb-3">
<label for="age_rating" class="form-label">年龄分级</label>
<select class="form-select" id="age_rating" name="age_rating" required>
<option value="3+" <?php echo $app['age_rating'] === '3+' ? 'selected' : ''; ?>>3+</option>
<option value="7+" <?php echo $app['age_rating'] === '7+' ? 'selected' : ''; ?>>7+</option>
<option value="12+" <?php echo $app['age_rating'] === '12+' ? 'selected' : ''; ?>>12+</option>
<option value="17+" <?php echo $app['age_rating'] === '17+' ? 'selected' : ''; ?>>17+</option>
</select>
</div>
<div class="mb-3">
<label for="age_rating_description" class="form-label">年龄分级说明</label>
<input type="text" class="form-control" id="age_rating_description" name="age_rating_description" value="<?php echo htmlspecialchars($app['age_rating_description']); ?>" required>
</div>
<div class="mb-3">
<label class="form-label">适用平台</label>
<?php $platforms = json_decode($app['platforms'], true) ?? [];
// 解析平台值,提取主平台和子选项
$mainPlatforms = [];
$subOptions = [];
foreach($platforms as $platform){
if(strpos($platform, 'windows_') === 0){
$mainPlatforms[] = 'windows';
$subOptions['windows'] = $platform;
} elseif(strpos($platform, 'linux_') === 0){
$mainPlatforms[] = 'linux';
$subOptions['linux'] = $platform;
} else{
$mainPlatforms[] = $platform;
}
}
$mainPlatforms = array_unique($mainPlatforms);
?>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="windows" id="windows" name="platforms[]" <?php echo in_array('windows', $mainPlatforms) ? 'checked' : ''; ?>>
<label class="form-check-label" for="windows">Windows</label>
</div>
<div id="windows_suboptions" class="ms-4 mt-2" style="display: <?php echo in_array('windows', $mainPlatforms) ? 'block' : 'none'; ?>">
<div class="form-check">
<input class="form-check-input" type="radio" name="windows_version" id="windows_xp" value="windows_xp" <?php echo isset($subOptions['windows']) && $subOptions['windows'] === 'windows_xp' ? 'checked' : ''; ?>>
<label class="form-check-label" for="windows_xp">XP以前</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="windows_version" id="windows_win7" value="windows_win7" <?php echo isset($subOptions['windows']) && $subOptions['windows'] === 'windows_win7' ? 'checked' : ''; ?>>
<label class="form-check-label" for="windows_win7">Win7以后</label>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="macos" id="macos" name="platforms[]" <?php echo in_array('macos', $mainPlatforms) ? 'checked' : ''; ?>>
<label class="form-check-label" for="macos">macOS</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="linux" id="linux" name="platforms[]" <?php echo in_array('linux', $mainPlatforms) ? 'checked' : ''; ?>>
<label class="form-check-label" for="linux">Linux</label>
</div>
<div id="linux_suboptions" class="ms-4 mt-2" style="display: <?php echo in_array('linux', $mainPlatforms) ? 'block' : 'none'; ?>">
<div class="form-check">
<input class="form-check-input" type="radio" name="linux_distribution" id="linux_ubuntu" value="linux_ubuntu" <?php echo isset($subOptions['linux']) && $subOptions['linux'] === 'linux_ubuntu' ? 'checked' : ''; ?>>
<label class="form-check-label" for="linux_ubuntu">Ubuntu</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="linux_distribution" id="linux_arch" value="linux_arch" <?php echo isset($subOptions['linux']) && $subOptions['linux'] === 'linux_arch' ? 'checked' : ''; ?>>
<label class="form-check-label" for="linux_arch">Arch Linux</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="linux_distribution" id="linux_centos" value="linux_centos" <?php echo isset($subOptions['linux']) && $subOptions['linux'] === 'linux_centos' ? 'checked' : ''; ?>>
<label class="form-check-label" for="linux_centos">CentOS</label>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="platform_android" name="platforms[]" value="Android" <?php echo in_array('Android', $mainPlatforms) ? 'checked' : ''; ?>>
<label class="form-check-label" for="platform_android">Android</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="platform_ios" name="platforms[]" value="iOS" <?php echo in_array('iOS', $mainPlatforms) ? 'checked' : ''; ?>>
<label class="form-check-label" for="platform_ios">iOS</label>
</div>
</div>
<div class="mb-3">
<label for="tags" class="form-label">应用标签 (至少选择1个)</label>
<select id="tags" name="tags[]" multiple class="form-control">
<?php foreach ($tags as $tag): ?>
<option value="<?php echo $tag['id']; ?>" <?php echo in_array($tag['id'], $appTags) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($tag['name']); ?>
</option>
<?php endforeach; ?>
</select>
<small class="form-text text-muted">按住Ctrl键可选择多个标签</small>
</div>
<div class="mb-3">
<label for="app_file" class="form-label">更新应用文件</label>
<input class="form-control" type="file" id="app_file" name="app_file">
<div class="form-text">当前文件: <?php echo basename($app['file_path']); ?></div>
</div>
<div class="mb-3">
<label class="form-label">应用图片 (最多5张)</label>
<?php
// 获取现有图片
$existingImages = [];
$stmt = $conn->prepare("SELECT id, image_path FROM app_images WHERE app_id = ?");
$stmt->bind_param("i", $appId);
$stmt->execute();
$imgResult = $stmt->get_result();
while ($img = $imgResult->fetch_assoc()) {
$existingImages[] = $img;
}
$stmt->close();
?>
<!-- 现有图片 -->
<?php if (!empty($existingImages)): ?>
<div class="mb-3">
<label>现有图片:</label>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($existingImages as $img): ?>
<div class="position-relative">
<img src="<?php echo htmlspecialchars($img['image_path']); ?>" alt="应用图片" style="width: 100px; height: 100px; object-fit: cover; border-radius: 4px;">
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0" onclick="removeImage(<?php echo $img['id']; ?>)">×</button>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- 新图片上传 -->
<input type="file" name="images[]" multiple accept="image/*" class="form-control">
<small>支持jpg、png格式最多上传5张图片</small>
</div>
<input type="hidden" name="removed_images" id="removed_images" value="">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-save me-1"></i>保存更改</button>
</form>
<div class="text-center mt-3">
<a href="dashboard.php" class="btn btn-secondary">返回仪表盘</a>
</div>
</div>
<script>
function removeImage(imageId) {
const removedInput = document.getElementById('removed_images');
const currentValues = removedInput.value ? removedInput.value.split(',') : [];
if (!currentValues.includes(imageId.toString())) {
currentValues.push(imageId);
removedInput.value = currentValues.join(',');
}
// 从DOM中移除图片元素
event.target.closest('.position-relative').remove();
}
// 平台子选项显示控制
document.getElementById('windows').addEventListener('change', function() {
const suboptions = document.getElementById('windows_suboptions');
suboptions.style.display = this.checked ? 'block' : 'none';
if (!this.checked) {
document.querySelectorAll('input["windows_version"]').forEach(radio => radio.checked = false);
}
});
document.getElementById('linux').addEventListener('change', function() {
const suboptions = document.getElementById('linux_suboptions');
suboptions.style.display = this.checked ? 'block' : 'none';
if (!this.checked) {
document.querySelectorAll('input[name="linux_distribution"]').forEach(radio => radio.checked = false);
}
});
// 表单提交验证
document.querySelector('form').addEventListener('submit', function(e) {
// 验证Windows子选项
if (document.getElementById('windows').checked && !document.querySelector('input[name="windows_version"]:checked')) {
e.preventDefault();
Swal.fire({
title: '提示',
text: '请选择Windows版本XP以前或Win7以后',
icon: 'warning',
confirmButtonText: '确定'
});
return;
}
// 验证Linux子选项
if (document.getElementById('linux').checked && !document.querySelector('input[name="linux_distribution"]:checked')) {
e.preventDefault();
Swal.fire({
title: '提示',
text: '请选择Linux发行版Ubuntu、Arch Linux或CentOS',
icon: 'warning',
confirmButtonText: '确定'
});
return;
}
// 更新平台值包含子选项信息
const platforms = [];
if (document.getElementById('android').checked) platforms.push('Android');
if (document.getElementById('ios').checked) platforms.push('iOS');
if (document.getElementById('macos').checked) platforms.push('macos');
if (document.getElementById('windows').checked) {
platforms.push(document.querySelector('input[name="windows_version"]:checked').value);
}
if (document.getElementById('linux').checked) {
platforms.push(document.querySelector('input[name="linux_distribution"]:checked').value);
}
// 设置隐藏字段值
document.getElementById('platforms_hidden').value = JSON.stringify(platforms);
});
</script>
</body>
</html>

166
developer/login.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
// 引入配置文件
require_once '../config.php';
// 顶栏样式
echo '<style>
.navbar.scrolled {
background-color: rgba(255, 255, 255, 0.95) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>';
// 导航栏
echo '<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container">
<a href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
<a class="navbar-brand" href="../index.php">'. APP_STORE_NAME . '</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="../index.php">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="register.php">开发者注册</a>
</li>
</ul>
</div>
</div>
</nav>';
// 为内容添加顶部内边距
echo '<div style="padding-top: 70px;">';
session_start();
$error = '';
if (isset($_GET['register_success']) && $_GET['register_success'] == 1) {
$success = '注册成功,请登录';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$loginId = trim($_POST['login_id']);
$password = $_POST['password'];
if (empty($loginId) || empty($password)) {
$error = '邮箱/用户名和密码不能为空';
} else {
// 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
} else {
$stmt = $conn->prepare('SELECT id, username, password FROM developers WHERE email = ? OR username = ?');
if (!$stmt) {
log_error('登录查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '登录时发生错误,请稍后再试';
} else {
$stmt->bind_param('ss', $loginId, $loginId);
if (!$stmt->execute()) {
log_error('登录查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '登录时发生错误,请稍后再试';
} else {
$result = $stmt->get_result();
$developer = $result->fetch_assoc();
if ($developer && password_verify($password, $developer['password'])) {
$_SESSION['developer_id'] = $developer['id'];
$_SESSION['developer_username'] = $developer['username'];
// 处理自动登录
if (isset($_POST['remember_me']) && $_POST['remember_me'] === 'on') {
$cookie_lifetime = 30 * 24 * 60 * 60; // 30天
$cookie_params = session_get_cookie_params();
setcookie(
session_name(),
session_id(),
time() + $cookie_lifetime,
$cookie_params['path'],
$cookie_params['domain'],
$cookie_params['secure'],
$cookie_params['httponly']
);
ini_set('session.gc_maxlifetime', $cookie_lifetime);
}
header('Location: dashboard.php');
exit;
} else {
$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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<style>
body {
background-color: #f4f4f4;
padding: 20px 0;
}
.page-transition {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="page-transition">
<div class="container mt-5 col-md-4">
<h2>开发者登录</h2>
<?php if (isset($success)): ?>
<div class="alert alert-success" role="alert"><?php echo $success; ?></div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger" role="alert"><?php echo $error; ?></div>
<?php endif; ?>
<form method="post">
<div class="form-floating mb-3">
<input type="text" id="login_id" name="login_id" class="form-control" placeholder="请输入邮箱或用户名" required>
<label for="login_id">邮箱/用户名</label>
</div>
<div class="form-floating mb-3">
<input type="password" id="password" name="password" class="form-control" placeholder="请输入密码" required>
<label for="password">密码</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="remember_me" id="remember_me">
<label class="form-check-label" for="remember_me">
自动登录
</label>
</div>
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-user me-1"></i>登录</button>
</form>
<div class="text-center mt-3">
还没有账号?<a href="register.php" class="text-decoration-none">注册</a>
</div>
</div>
<script src="/js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('page-transition');
});
</script>
</body>
</html>

10
developer/logout.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
session_start();
// 销毁会话数据
session_unset();
session_destroy();
// 重定向到登录页面
header('Location: login.php');
exit;

207
developer/profile.php Normal file
View File

@@ -0,0 +1,207 @@
<?php
// 引入配置文件
require_once '../config.php';
session_start();
// 检查开发者是否已登录
if (!isset($_SESSION['developer_id'])) {
header('Location: login.php');
exit;
}
$developerId = $_SESSION['developer_id'];
$error = '';
$success = '';
// 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
} else {
// 获取开发者信息
$stmt = $conn->prepare('SELECT username, email, social_links FROM developers WHERE id = ?');
if (!$stmt) {
log_error('获取开发者信息查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '获取开发者信息时发生错误,请稍后再试';
} else {
$stmt->bind_param('i', $developerId);
if (!$stmt->execute()) {
log_error('获取开发者信息查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '获取开发者信息时发生错误,请稍后再试';
} else {
$result = $stmt->get_result();
$developer = $result->fetch_assoc();
}
}
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newUsername = trim($_POST['username']);
$newEmail = trim($_POST['email']);
$newPassword = $_POST['password'];
$newSocialLinks = trim($_POST['social_links']);
// 更新用户名和邮箱
$stmt = $conn->prepare('UPDATE developers SET username = ?, email = ?, social_links = ? WHERE id = ?');
if (!$stmt) {
log_error('更新开发者信息查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '更新信息时发生错误,请稍后再试';
} else {
$stmt->bind_param('sssi', $newUsername, $newEmail, $newSocialLinks, $developerId);
if (!$stmt->execute()) {
log_error('更新开发者信息查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '更新信息时发生错误,请稍后再试';
} else {
// 更新密码
if (!empty($newPassword)) {
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $conn->prepare('UPDATE developers SET password = ? WHERE id = ?');
if (!$stmt) {
log_error('更新密码查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '更新密码时发生错误,请稍后再试';
} else {
$stmt->bind_param('si', $hashedPassword, $developerId);
if (!$stmt->execute()) {
log_error('更新密码查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '更新密码时发生错误,请稍后再试';
}
}
}
if (empty($error)) {
$success = '信息更新成功';
$_SESSION['developer_username'] = $newUsername;
// 重新获取开发者信息
$stmt = $conn->prepare('SELECT username, email, social_links FROM developers WHERE id = ?');
if ($stmt) {
$stmt->bind_param('i', $developerId);
if ($stmt->execute()) {
$result = $stmt->get_result();
$developer = $result->fetch_assoc();
}
}
}
}
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>开发者信息 - <?php echo APP_STORE_NAME; ?></title>
<!-- Bootstrap CSS -->
<link href="../css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<!-- 自定义CSS -->
<link rel="stylesheet" href="../styles.css">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
.page-transition {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.profile-container {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
}
.form-group {
margin-bottom: 1rem;
}
.error {
color: red;
}
.success {
color: green;
}
</style>
</head>
<body class="page-transition">
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light blur-bg">
<div class="container">
<a class="navbar-brand" href="../index.php"><?php echo APP_STORE_NAME; ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">应用仪表盘</a>
</li>
<li class="nav-item">
<a class="nav-link" href="upload_app.php">上传应用</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">退出登录</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="profile.php">开发者信息</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="profile-container mt-4">
<?php if (!empty($error)): ?>
<div class="error"><?php echo $error; ?></div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div class="success"><?php echo $success; ?></div>
<?php endif; ?>
<h1>开发者信息</h1>
<form method="post">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($developer['username']); ?>" placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="email">邮箱</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($developer['email']); ?>" required>
</div>
<div class="form-group">
<label for="password">新密码 (留空则不修改)</label>
<input type="password" class="form-control" id="password" name="password">
</div>
<div class="form-group">
<label for="social_links">社交媒体链接 (多个链接用逗号分隔)</label>
<input type="text" class="form-control" id="social_links" name="social_links" value="<?php echo htmlspecialchars($developer['social_links']); ?>" placeholder="请输入社交媒体链接,多个链接用逗号分隔">
</div>
<button type="submit" class="btn btn-primary"><i class="fas fa-save me-1"></i>保存更改</button>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('page-transition');
});
</script>
</body>
</html>

232
developer/register.php Normal file
View File

@@ -0,0 +1,232 @@
<?php
// 引入配置文件
// 检查配置文件是否存在并加载
$configFile = '/www/wwwroot/leonmmcoset.jjxmm.win_8010/config.php';
if (!file_exists($configFile)) {
die('配置文件缺失: ' . $configFile . ',无法继续执行');
}
require_once $configFile;
// 引入日志工具
require_once '/www/wwwroot/leonmmcoset.jjxmm.win_8010/includes/logger.php';
// 配置文件加载后日志记录和常量检查
log_error('配置文件已成功加载: ' . $configFile);
// 验证关键常量是否定义
log_error('配置加载后常量检查 - SMTP_HOST: ' . (defined('SMTP_HOST') ? SMTP_HOST : '未定义'));
log_error('配置加载后常量检查 - SMTP_USERNAME: ' . (defined('SMTP_USERNAME') ? SMTP_USERNAME : '未定义'));
log_error('配置加载后常量检查 - SMTP_PASSWORD: ' . (defined('SMTP_PASSWORD') ? '已设置' : '未定义'));
log_error('配置加载后常量检查 - SMTP_PORT: ' . (defined('SMTP_PORT') ? SMTP_PORT : '未定义'));
log_error('配置文件加载后 - SMTP_USERNAME: ' . (defined('SMTP_USERNAME') ? SMTP_USERNAME : '未定义') . ', SMTP_PORT: ' . (defined('SMTP_PORT') ? SMTP_PORT : '未定义'));
// 引入PHPMailer命名空间
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
// 引入Composer自动加载器
require_once '../vendor/autoload.php';
// 顶栏样式
echo '<style>
.navbar.scrolled {
background-color: rgba(255, 255, 255, 0.95) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>';
// 导航栏
echo '<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container">
<a href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
<a class="navbar-brand" href="../index.php">'. APP_STORE_NAME . '</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="../index.php">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="login.php">开发者登录</a>
</li>
</ul>
</div>
</div>
</nav>';
// 为内容添加顶部内边距
echo '<div style="padding-top: 70px;">';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$password = $_POST['password'];
if (empty($username) || empty($email) || empty($password)) {
$error = '用户名、邮箱和密码不能为空';
} elseif (empty($_POST['agree'])) {
$error = '必须同意 APP 审核标准才能注册';
} elseif (empty($_POST['agree_privacy'])) {
$error = '必须同意隐私政策才能注册';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = '请输入有效的邮箱地址';
} else {
// 检查数据库连接是否为 PDO 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
} else {
try {
$stmt = $conn->prepare('SELECT id FROM developers WHERE username = ? OR email = ?');
$stmt->bind_param('ss', $username, $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows > 0) {
$error = '用户名或邮箱已被注册';
} else {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// 生成验证令牌
$verificationToken = bin2hex(random_bytes(32));
$insertStmt = $conn->prepare('INSERT INTO developers (username, email, password, verification_token) VALUES (?, ?, ?, ?)');
$insertStmt->bind_param('ssss', $username, $email, $hashedPassword, $verificationToken);
if (!$insertStmt->execute()) {
log_error('插入执行失败: ' . $insertStmt->error, __FILE__, __LINE__);
$error = '系统错误,请稍后再试';
} else {
// 生成验证链接
$verificationLink = 'http://' . $_SERVER['HTTP_HOST'] . '/developer/verify_email.php?token=' . urlencode($verificationToken);
// 加载邮件模板
$templatePath = __DIR__ . '/../mail/verification_template.php';
if (file_exists($templatePath)) {
$templateContent = file_get_contents($templatePath);
$templateContent = str_replace('{username}', htmlspecialchars($username), $templateContent);
$templateContent = str_replace('{verification_link}', $verificationLink, $templateContent);
// 调试日志测试
$testLogDir = 'c:\\web\\app2\\logs';
$testLogFile = $testLogDir . '\\test.log';
if (!is_dir($testLogDir)) {
mkdir($testLogDir, 0755, true);
}
file_put_contents($testLogFile, date('[Y-m-d H:i:s] ') . '邮件发送代码开始执行' . PHP_EOL, FILE_APPEND);
// 添加SMTP配置调试日志
log_error('SMTP配置参数 - HOST: ' . (defined('SMTP_HOST') ? SMTP_HOST : '未定义') . ', PORT: ' . (defined('SMTP_PORT') ? SMTP_PORT : '未定义') . ', USERNAME: ' . (defined('SMTP_USERNAME') ? SMTP_USERNAME : '未定义') . ', ENCRYPTION: ' . (defined('SMTP_ENCRYPTION') ? SMTP_ENCRYPTION : '未定义'), __FILE__, __LINE__);
log_error('开始执行邮件发送流程', __FILE__, __LINE__);
// 配置SMTP邮件发送
require_once '../vendor/phpmailer/phpmailer/src/PHPMailer.php';
require_once '../vendor/phpmailer/phpmailer/src/SMTP.php';
/** @var \PHPMailer\PHPMailer\PHPMailer $mail */
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
try {
$mail->isSMTP();
$mail->SMTPDebug = 4;
// 输出当前SMTP配置参数用于调试
log_error('SMTP配置参数: HOST=' . SMTP_HOST . ', PORT=' . SMTP_PORT . ', USERNAME=' . SMTP_USERNAME . ', ENCRYPTION=' . SMTP_ENCRYPTION);
// 检查openssl扩展是否启用
log_error('OpenSSL扩展状态: ' . (extension_loaded('openssl') ? '已启用' : '未启用')); // 启用详细调试
$mail->Debugoutput = function($str, $level) {
$logDir = 'c:\\web\\app2\\logs';
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
file_put_contents($logDir . '\\smtp_debug.log', date('[Y-m-d H:i:s] ') . $str . PHP_EOL, FILE_APPEND);
};
$mail->Host = defined('SMTP_HOST') ? SMTP_HOST : 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = defined('SMTP_USERNAME') ? SMTP_USERNAME : ''; // Ensure SMTP_USERNAME is defined in config.php
$mail->Password = defined('SMTP_PASSWORD') ? SMTP_PASSWORD : '';
$mail->SMTPSecure = defined('SMTP_ENCRYPTION') ? SMTP_ENCRYPTION : 'tls'; // Ensure SMTP_ENCRYPTION is defined in config.php
$mail->AuthType = 'PLAIN'; // 尝试使用PLAIN认证方式
$mail->Port = defined('SMTP_PORT') ? SMTP_PORT : 587;
$mail->CharSet = 'UTF-8';
$mail->setFrom(defined('SMTP_FROM_EMAIL') ? SMTP_FROM_EMAIL : 'noreply@example.com', defined('SMTP_FROM_NAME') ? SMTP_FROM_NAME : 'App Store'); // Ensure SMTP_FROM_EMAIL is defined in config.php
$mail->addAddress($email, $username);
$mail->isHTML(true);
$mail->Subject = '邮箱验证 - ' . (defined('APP_STORE_NAME') ? APP_STORE_NAME : 'App Store');
$mail->Body = $templateContent;
$mail->send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
log_error('邮件发送失败: ' . $mail->ErrorInfo, __FILE__, __LINE__);
}
} else {
log_error('验证邮件模板不存在: ' . $templatePath, __FILE__, __LINE__);
}
header('Location: login.php?register_success=1&verify_email_sent=1');
exit;
}
}
} catch (PDOException $e) {
$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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<style>
body {
background-color: #f4f4f4;
padding: 20px 0;
}
</style>
</head>
<body>
<div class="container mt-5 col-md-4">
<h2>开发者注册</h2>
<?php if (!empty($error)): ?>
<div class="alert alert-danger" role="alert"><?php echo $error; ?></div>
<?php endif; ?>
<form method="post">
<div class="form-floating mb-3">
<input type="text" class="form-control" id="username" name="username" placeholder="用户名" required>
<label for="username">用户名</label>
</div>
<div class="form-floating mb-3">
<input type="email" class="form-control" id="email" name="email" placeholder="邮箱" required>
<label for="email">邮箱</label>
</div>
<div class="form-floating mb-3">
<input type="password" class="form-control" id="password" name="password" placeholder="密码" required>
<label for="password">密码</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="agree" name="agree" required>
<label class="form-check-label" for="agree">我已阅读并同意 <a href="/docs/app_review_standards.php" target="_blank">APP 审核标准</a></label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="privacy_policy" name="agree_privacy">
<label class="form-check-label" for="privacy_policy">
我已阅读并同意 <a href="../docs/privacy_policy.php" target="_blank">隐私政策</a>
</label>
</div>
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-user-plus me-1"></i>注册</button>
</form>
</div>
<script src="/js/bootstrap.bundle.js"></script>
</body>
</html>

589
developer/upload_app.php Normal file
View File

@@ -0,0 +1,589 @@
<?php
require_once('../includes/logger.php');
set_time_limit(0); // 添加此行取消脚本超时限制
// 引入配置文件
require_once '../config.php';
session_start();
// 检查开发者是否已登录
if (!isset($_SESSION['developer_id']) || !is_numeric($_SESSION['developer_id'])) {
log_error('开发者会话ID不存在或无效', __FILE__, __LINE__);
header('Location: login.php');
exit;
}
$developerId = (int)$_SESSION['developer_id'];
log_info("上传应用的开发者ID: $developerId", __FILE__, __LINE__);
log_info("上传应用的开发者ID: $developerId", __FILE__, __LINE__);
$error = '';
$success = '';
// 检查开发者邮箱是否已验证
$stmt = $conn->prepare('SELECT is_verified FROM developers WHERE id = ?');
if (!$stmt) {
log_error('准备验证状态查询失败: ' . $conn->error, __FILE__, __LINE__);
$error = '系统错误,请稍后再试';
} else {
$stmt->bind_param('i', $developerId);
$stmt->execute();
$result = $stmt->get_result();
$developer = $result->fetch_assoc();
$stmt->close();
log_info("开发者验证状态: " . ($developer ? ($developer['is_verified'] ? "已验证" : "未验证") : "开发者不存在"), __FILE__, __LINE__);
if (!$developer) {
$error = '开发者账号不存在,请重新登录。';
} elseif (!$developer['is_verified']) {
$error = '您的邮箱尚未验证,请先验证邮箱后再上传应用。';
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 创建上传目录(如果不存在)
$uploadDirs = ['../uploads/apps', '../uploads/images'];
foreach ($uploadDirs as $dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
// 获取表单数据
$appName = trim($_POST['name']);
$appDescription = trim($_POST['description']);
$tags = $_POST['tags'] ?? [];
$ageRating = $_POST['age_rating'] ?? '';
$ageRatingDescription = $_POST['age_rating_description'] ?? '';
$platforms = isset($_POST['platforms']) ? $_POST['platforms'] : [];
$version = trim($_POST['version']);
$changelog = trim($_POST['changelog']);
// 验证表单数据
if (empty($appName) || empty($appDescription)) {
$error = '应用名称和描述不能为空';
} elseif (empty($changelog)) {
$error = '更新日志不能为空';
} elseif (empty($platforms)) {
$error = '请至少选择一个适用平台';
} elseif (in_array($ageRating, ['12+', '17+']) && empty($ageRatingDescription)) {
$error = '年龄分级为12+或以上时,必须提供年龄分级说明';
} else {
// 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置';
} else {
// 处理应用文件上传
// 获取选中的平台
$selectedPlatforms = $_POST['platforms'] ?? [];
// 处理应用文件上传
$appFile = $_FILES['app_file'] ?? null;
$appFilePath = '';
if ($appFile && $appFile['error'] === UPLOAD_ERR_OK) {
// 验证文件大小 (100MB)
if ($appFile['size'] > 500 * 1024 * 1024) {
log_error('应用文件过大: ' . number_format($appFile['size'] / 1024 / 1024, 2) . 'MB', __FILE__, __LINE__);
$error = '应用文件大小不能超过500MB';
}
$appExtension = pathinfo($appFile['name'], PATHINFO_EXTENSION);
$appFileName = uniqid() . '.' . $appExtension;
$appRelativePath = 'uploads/apps/' . $appFileName;
$appFilePath = __DIR__ . '/../' . $appRelativePath;
if (!move_uploaded_file($appFile['tmp_name'], $appFilePath)) {
log_error('应用文件移动失败', __FILE__, __LINE__);
$error = '应用文件上传失败';
}
} else {
// 验证标签ID是否存在
if (!empty($tags)) {
$tagIds = implode(',', array_map('intval', $tags));
$tagCheckStmt = $conn->prepare("SELECT id FROM tags WHERE id IN ($tagIds)");
if (!$tagCheckStmt) {
log_error('标签验证查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '系统错误,请稍后再试';
} else {
$tagCheckStmt->execute();
$tagResult = $tagCheckStmt->get_result();
$validTagIds = [];
while ($tag = $tagResult->fetch_assoc()) {
$validTagIds[] = $tag['id'];
}
$tagCheckStmt->close();
$invalidTags = array_diff($tags, $validTagIds);
if (!empty($invalidTags)) {
log_error('无效的标签ID: ' . implode(',', $invalidTags), __FILE__, __LINE__);
$error = '选择了无效的标签,请刷新页面重试';
}
}
}
$error = '应用文件上传错误: ' . ($appFile ? $appFile['error'] : '未找到文件');
}
// 处理图片上传
$imagePaths = [];
$images = $_FILES['images'] ?? null;
if ($images && is_array($images['tmp_name'])) {
foreach ($images['tmp_name'] as $key => $tmpName) {
if ($images['error'][$key] === UPLOAD_ERR_OK) {
// 验证图片大小 (10MB)
if ($images['size'][$key] > 10 * 1024 * 1024) {
log_error('图片过大: ' . $images['name'][$key] . ' (' . number_format($images['size'][$key] / 1024 / 1024, 2) . 'MB)', __FILE__, __LINE__);
$error = '图片 ' . $images['name'][$key] . ' 大小不能超过10MB';
}
$imageRelativePath = 'uploads/images/' . uniqid() . '.' . pathinfo($images['name'][$key], PATHINFO_EXTENSION);
$imagePath = __DIR__ . '/../' . $imageRelativePath;
$target_dir = dirname($imagePath);
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
if (move_uploaded_file($tmpName, $imagePath)) {
$imagePaths[] = $imageRelativePath;
} else {
log_error('图片文件移动失败: ' . $images['name'][$key], __FILE__, __LINE__);
}
}
}
}
if (empty($error)) {
// 开始数据库事务
$conn->begin_transaction();
try {
// 确保必要变量存在,防止空值导致 SQL 错误
if (!isset($appName) || !isset($appDescription) || !isset($developerId) || !isset($version) || !isset($changelog) || !isset($ageRating) || !isset($ageRatingDescription)) {
throw new Exception('缺少必要的上传参数');
}
// 获取开发者邮箱
$userStmt = $conn->prepare('SELECT email FROM developers WHERE id = ?');
$userStmt->bind_param('i', $developerId);
$userStmt->execute();
$userResult = $userStmt->get_result();
$user = $userResult->fetch_assoc();
$developerEmail = $user['email'] ?? '';
$userStmt->close();
if (empty($developerEmail)) {
throw new Exception('无法获取开发者邮箱信息');
}
// 插入应用基本信息
$stmt = $conn->prepare('INSERT INTO apps (name, description, platforms, status, age_rating, age_rating_description, version, changelog, file_path, developer_id, developer_email, created_at) VALUES (?, ?, ?, \'pending\', ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)');
if (!$stmt) {
throw new Exception('应用基本信息查询准备失败: ' . $conn->error);
}
// 确保平台数据正确编码
$platforms = $_POST['platforms'] ?? [];
$platforms_json = json_encode($platforms);
// 此处需确认预处理语句占位符数量,确保与 bind_param 参数数量一致,示例仅示意,实际需根据表结构调整
// 修正参数绑定添加file_path参数以匹配SQL占位符数量
// 修正参数类型字符串长度确保与10个参数匹配
// 修正类型字符串长度10个参数对应10个类型字符
// 最终修正10个参数对应10个类型字符
// 根据参数实际类型修正类型字符串整数用i字符串用s
// 移除多余的$status参数匹配SQL中9个占位符
// 修正age_rating_description类型为字符串并确保9个参数与占位符匹配
// 修复变量名错误:使用已验证的$appFilePath替换未定义的$file_path
$stmt->bind_param('ssssssssis', $appName, $appDescription, $platforms_json, $ageRating, $ageRatingDescription, $version, $changelog, $appRelativePath, $developerId, $developerEmail);
if (!$stmt->execute()) {
throw new Exception('应用基本信息查询执行失败: ' . $stmt->error);
}
$appId = $stmt->insert_id;
log_info("应用已插入数据库: ID=$appId, 状态=pending", __FILE__, __LINE__);
$stmt->close();
// 发送审核邮件给管理员
require_once '../vendor/autoload.php';
$subject = '新应用待审核';
$body = "开发者提交了新应用应用ID: $appId名称: $appName请及时审核。";
$mail = new PHPMailer\PHPMailer\PHPMailer();
$mail->isSMTP();
$mail->Host = SMTP_HOST;
$mail->SMTPAuth = true;
$mail->Username = SMTP_USERNAME;
$mail->Password = SMTP_PASSWORD;
$mail->SMTPSecure = SMTP_ENCRYPTION;
$mail->Port = SMTP_PORT;
$mail->setFrom(SMTP_FROM_EMAIL, SMTP_FROM_NAME);
$mail->addAddress(ADMIN_EMAIL);
$mail->isHTML(false);
$mail->CharSet = 'utf-8';
$mail->Subject = $subject;
$mail->Body = $body;
if(!$mail->send()) {
log_error('发送审核邮件失败: ' . $mail->ErrorInfo, __FILE__, __LINE__);
} else {
log_info('已成功发送审核邮件给管理员', __FILE__, __LINE__);
}
log_info("开始处理应用关联数据: ID=$appId", __FILE__, __LINE__);
// 插入应用标签关联
foreach ($tags as $tagId) {
$tagStmt = $conn->prepare('INSERT INTO app_tags (app_id, tag_id) VALUES (?, ?)');
if (!$tagStmt) {
throw new Exception('标签关联查询准备失败: ' . $conn->error);
}
$tagStmt->bind_param('ii', $appId, $tagId);
if (!$tagStmt->execute()) {
throw new Exception('标签关联查询执行失败: ' . $tagStmt->error);
}
$tagStmt->close();
}
// 插入应用图片
foreach ($imagePaths as $imageRelativePath) {
$imageStmt = $conn->prepare('INSERT INTO app_images (app_id, image_path) VALUES (?, ?)');
if (!$imageStmt) {
throw new Exception('图片关联查询准备失败: ' . $conn->error);
}
$imageStmt->bind_param('is', $appId, $imageRelativePath);
if (!$imageStmt->execute()) {
throw new Exception('图片关联查询执行失败: ' . $imageStmt->error);
}
$imageStmt->close();
}
// 插入应用版本信息
$versionStmt = $conn->prepare('INSERT INTO app_versions (app_id, version, changelog, file_path, created_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)');
if (!$versionStmt) {
throw new Exception('版本信息查询准备失败: ' . $conn->error);
}
$versionStmt->bind_param('isss', $appId, $version, $changelog, $appRelativePath);
if (!$versionStmt->execute()) {
throw new Exception('版本信息查询执行失败: ' . $versionStmt->error);
}
$versionStmt->close();
log_info("所有关联数据处理完成,准备提交事务: ID=$appId", __FILE__, __LINE__);
// 提交事务
$conn->commit();
log_info("应用上传成功: ID=$appId, 状态=pending", __FILE__, __LINE__);
$success = '应用上传成功,请等待管理员审核';
} catch (Exception $e) {
// 回滚事务
$conn->rollback();
log_error('应用上传事务失败(ID=$appId): ' . $e->getMessage(), __FILE__, __LINE__);
$error = '上传应用时发生错误,请稍后再试';
}
}
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传应用 - <?php echo APP_STORE_NAME; ?></title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<!-- 自定义CSS -->
<link rel="stylesheet" href="../styles.css">
<script src="/js/sweetalert.js"></script>
<!-- Fluent Design 模糊效果 -->
<style>
.blur-bg {
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.5);
}
.form-group {
margin-bottom: 1rem;
}
.btn-primary {
background-color: #007BFF;
border-color: #007BFF;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.back-link {
text-align: center;
margin-top: 1rem;
}
#ageRatingDescriptionGroup {
display: none;
}
</style>
<!-- Bootstrap JS with Popper -->
<script src="/js/bootstrap.bundle.js"></script>
</head>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light blur-bg">
<div class="container">
<a href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
<a class="navbar-brand" href="../index.php"><?php echo APP_STORE_NAME; ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">应用仪表盘</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="upload_app.php">上传应用</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">退出登录</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<style>
.form-group {
margin-bottom: 1rem;
}
.btn-primary {
background-color: #007BFF;
border-color: #007BFF;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.back-link {
text-align: center;
margin-top: 1rem;
}
#ageRatingDescriptionGroup {
display: none;
}
</style>
<script>
// 年龄分级说明显示控制
document.addEventListener('DOMContentLoaded', function() {
const ageRating = document.getElementById('age_rating');
const ageDescGroup = document.getElementById('ageRatingDescriptionGroup');
const ageDescInput = document.getElementById('age_rating_description');
function toggleAgeDescription() {
if (['12+', '17+'].includes(ageRating.value)) {
ageDescGroup.style.display = 'block';
ageDescInput.required = true;
} else {
ageDescGroup.style.display = 'none';
ageDescInput.required = false;
}
}
ageRating.addEventListener('change', toggleAgeDescription);
toggleAgeDescription(); // 初始状态检查
// 文件类型验证
const appFileInput = document.getElementById('app_file');
const imageInput = document.getElementById('images');
const allowedAppTypes = { 'android': ['apk'], 'ios': ['ipa'] };
const allowedImageTypes = ['jpg', 'jpeg', 'png', 'gif'];
appFileInput.addEventListener('change', function(e) {
if (this.files.length > 0) {
const file = this.files[0];
const ext = file.name.split('.').pop().toLowerCase();
if (file.size > 500 * 1024 * 1024) { // 100MB限制
Swal.fire({
title: '提示',
text: '文件大小不能超过500MB',
icon: 'warning',
confirmButtonText: '确定'
});
this.value = '';
}
}
});
imageInput.addEventListener('change', function(e) {
if (this.files.length > 0) {
for (let i = 0; i < this.files.length; i++) {
const file = this.files[i];
if (file.size > 10 * 1024 * 1024) { // 10MB限制
Swal.fire({
title: '提示',
text: `图片 ${file.name} 大小不能超过10MB`,
icon: 'warning',
confirmButtonText: '确定'
});
this.value = '';
return;
}
}
}
});
// 平台子选项显示控制
document.getElementById('windows').addEventListener('change', function() {
const suboptions = document.getElementById('windows_suboptions');
suboptions.style.display = this.checked ? 'block' : 'none';
if (!this.checked) {
document.querySelectorAll('input[name="windows_version"]').forEach(radio => radio.checked = false);
}
});
document.getElementById('linux').addEventListener('change', function() {
const suboptions = document.getElementById('linux_suboptions');
suboptions.style.display = this.checked ? 'block' : 'none';
if (!this.checked) {
document.querySelectorAll('input[name="linux_distribution"]').forEach(radio => radio.checked = false);
}
});
});
</script>
</head>
<body>
<div class="container mt-5 mb-5 col-md-8 col-lg-6">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h2 class="h4 mb-0">上传应用</h2>
</div>
<div class="card-body p-4">
<?php if (!empty($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?php echo $success; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<div class="alert alert-warning mb-3">
<strong>警告:</strong>如果该应用非您本人开发,请务必添加"转载"标签。
</div>
<div class="form-group mb-3">
<label for="name" class="form-label">应用名称</label>
<input type="text" id="name" name="name" class="form-control" required>
</div>
<div class="form-group mb-3">
<label for="tags" class="form-label">标签</label>
<select id="tags" name="tags[]" multiple class="form-select" size="3">
<?php
$tagResult = $conn->query("SELECT id, name FROM tags");
while ($tag = $tagResult->fetch_assoc()):
?>
<option value="<?php echo $tag['id']; ?>"><?php echo htmlspecialchars($tag['name']); ?></option>
<?php endwhile; ?>
</select>
<small>按住Ctrl键可选择多个标签</small>
</div>
<div class="form-group mb-3">
<label for="description" class="form-label">应用描述支持Markdown</label>
<textarea id="description" name="description" rows="5" class="form-control" required></textarea>
</div>
<div class="form-group mb-3">
<label for="age_rating" class="form-label">年龄分级</label>
<select class="form-select" id="age_rating" name="age_rating" required>
<option value="3+">3+</option>
<option value="7+">7+</option>
<option value="12+">12+</option>
<option value="17+">17+</option>
</select>
</div>
<div class="form-group mb-3" id="ageRatingDescriptionGroup">
<label for="age_rating_description" class="form-label">年龄分级说明</label>
<textarea class="form-control" id="age_rating_description" name="age_rating_description" rows="3" placeholder="请说明为何需要此年龄分级"></textarea>
<small>当年龄分级为12+或以上时,此项为必填</small>
</div>
<div class="alert alert-info mb-3">
<strong>信息:</strong>我们尝试了开发多选子平台的功能但是就会导致以前的app会全部显示“未知平台”如果你是Windows的话选择Win7以上选项如果你是Linux的话选择APP支持平台中的任意一个即可。
</div>
<div class="form-group mb-3">
<label class="form-label">适用平台</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="android" id="android" name="platforms[]">
<label class="form-check-label" for="android">Android</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="ios" id="ios" name="platforms[]">
<label class="form-check-label" for="ios">iOS</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="windows" id="windows" name="platforms[]">
<label class="form-check-label" for="windows">Windows</label>
</div>
<div id="windows_suboptions" class="ms-4 mt-2" style="display: none;">
<div class="form-check">
<input class="form-check-input" type="radio" name="windows_version" id="windows_xp" value="windows_xp">
<label class="form-check-label" for="windows_xp">XP以前</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="windows_version" id="windows_win7" value="windows_win7">
<label class="form-check-label" for="windows_win7">Win7以后</label>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="macos" id="macos" name="platforms[]">
<label class="form-check-label" for="macos">macOS</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="linux" id="linux" name="platforms[]">
<label class="form-check-label" for="linux">Linux</label>
</div>
<div id="linux_suboptions" class="ms-4 mt-2" style="display: none;">
<div class="form-check">
<input class="form-check-input" type="radio" name="linux_distribution" id="linux_ubuntu" value="linux_ubuntu">
<label class="form-check-label" for="linux_ubuntu">Ubuntu</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="linux_distribution" id="linux_arch" value="linux_arch">
<label class="form-check-label" for="linux_arch">Arch Linux</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="linux_distribution" id="linux_centos" value="linux_centos">
<label class="form-check-label" for="linux_centos">CentOS</label>
</div>
</div>
</div>
<div class="form-group mb-3">
<label for="app_file" class="form-label">应用文件</label>
<input type="file" id="app_file" name="app_file" class="form-control" required>
</div>
<div class="form-group mb-3">
<label for="version" class="form-label">版本号</label>
<input type="text" id="version" name="version" class="form-control" required placeholder="例如: 1.0.0">
</div>
<div class="form-group mb-3">
<label for="changelog" class="form-label">更新日志</label>
<textarea id="changelog" name="changelog" rows="3" class="form-control" required></textarea>
</div>
<div class="form-group mb-4">
<label for="images" class="form-label">预览图片</label>
<input type="file" id="images" name="images[]" multiple accept="image/*" class="form-control">
<small>可选择多张图片</small>
</div>
<div class="form-group mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="age_doc_confirm" name="age_doc_confirm" required>
<label class="form-check-label" for="age_doc_confirm">
我已阅读并理解<a href="../docs/age_rating.php" target="_blank">年龄说明文档</a>
</label>
</div>
</div>
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-upload me-1"></i>上传应用</button>
</form>
<div class="back-link mt-4">
<a href="dashboard.php" class="btn btn-outline-secondary w-100">返回仪表盘</a>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,93 @@
<?php
// 引入配置文件
require_once '../config.php';
session_start();
$error = '';
$success = '';
// 检查是否提供了验证令牌
if (!isset($_GET['token']) || empty($_GET['token'])) {
$error = '无效的验证链接';
} else {
$token = trim($_GET['token']);
// 验证数据库连接
if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请稍后再试';
} else {
try {
// 查询具有该令牌的未验证开发者
$stmt = $conn->prepare('SELECT id FROM developers WHERE verification_token = ? AND is_verified = FALSE');
$stmt->bind_param('s', $token);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows === 1) {
// 更新验证状态
$updateStmt = $conn->prepare('UPDATE developers SET is_verified = TRUE, verified_at = NOW(), verification_token = NULL WHERE verification_token = ?');
$updateStmt->bind_param('s', $token);
$updateStmt->execute();
$updateStmt->close();
$success = '邮箱验证成功!现在您可以登录并创建应用了。';
} else {
$error = '验证链接无效或已过期';
}
$stmt->close();
} catch (Exception $e) {
log_error('邮箱验证失败: ' . $e->getMessage(), __FILE__, __LINE__);
$error = '验证过程中发生错误,请稍后再试';
}
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮箱验证 - <?= APP_STORE_NAME ?></title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<style>
body { background-color: #f4f4f4; padding: 70px 0; }
.container { max-width: 500px; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
.alert { margin-bottom: 20px; }
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container">
<a href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
<a class="navbar-brand" href="../index.php"><?= APP_STORE_NAME ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" href="../index.php">首页</a></li>
<li class="nav-item"><a class="nav-link" href="login.php">开发者登录</a></li>
</ul>
</div>
</div>
</nav>
<div class="container">
<h2 class="mb-4">邮箱验证</h2>
<?php if (!empty($success)): ?>
<div class="alert alert-success" role="alert"><?= $success ?></div>
<a href="login.php" class="btn btn-primary"><i class="fas fa-sign-in-alt me-1"></i>前往登录</a>
<?php else: ?>
<div class="alert alert-danger" role="alert"><?= $error ?></div>
<a href="register.php" class="btn btn-secondary"><i class="fas fa-user-plus me-1"></i>重新注册</a>
<?php endif; ?>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,421 @@
<?php
require_once '../config.php';
require_once '../includes/logger.php';
session_start();
// 检查开发者登录状态
if (!isset($_SESSION['developer_id'])) {
header('Location: login.php');
exit;
}
$developerId = $_SESSION['developer_id'];
// 验证App ID
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header('Location: dashboard.php?error=无效的App ID');
exit;
}
$appId = $_GET['id'];
// 获取App信息并验证所有权
$app = null;
$getAppSql = "SELECT * FROM apps WHERE id = ? AND developer_id = ?";
$stmt = $conn->prepare($getAppSql);
if (!$stmt) {
log_error("应用所有权验证查询准备失败: " . $conn->error, __FILE__, __LINE__);
header('Location: dashboard.php?error=验证应用所有权失败');
exit;
}
$stmt->bind_param("ii", $appId, $developerId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
header('Location: dashboard.php?error=App不存在或无权访问');
exit;
}
$app = $result->fetch_assoc();
$platforms = json_decode($app['platforms'], true);
$success = '';
$error = '';
// 处理版本上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
// 验证版本信息
if (empty($_POST['version']) || empty($_FILES['app_file']['name'])) {
$error = '版本号和安装包不能为空';
} else {
// 处理App文件上传
$uploadDir = '../files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$fileName = basename($_FILES['app_file']['name']);
$targetPath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['app_file']['tmp_name'], $targetPath)) {
$version = $_POST['version'];
$changelog = $_POST['changelog'] ?? '';
// 插入新版本记录
$insertVersionSql = "INSERT INTO app_versions (app_id, version, changelog, file_path) VALUES (?, ?, ?, ?)";
$verStmt = $conn->prepare($insertVersionSql);
if (!$verStmt) {
log_error("版本插入准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本保存失败,请稍后再试';
unlink($targetPath); // 清理已上传文件
} else {
$verStmt->bind_param("isss", $appId, $version, $changelog, $targetPath);
if ($verStmt->execute()) {
// 更新应用表中的最新版本
// 更新应用表中的最新版本
$updateAppSql = "UPDATE apps SET version = ? WHERE id = ?";
$updStmt = $conn->prepare($updateAppSql);
if (!$updStmt) {
log_error("应用版本更新准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '更新应用版本失败,请稍后再试';
unlink($targetPath); // 数据库更新失败,删除文件
} else {
$updStmt->bind_param("si", $version, $appId);
$updStmt->execute();
$success = '版本上传成功';
}
} else {
$error = '版本保存失败: '. $conn->error;
unlink($targetPath); // 数据库更新失败,删除文件
}
}
} else {
$error = '文件上传失败';
}
}
}
// 处理版本修改请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['version_id'])) {
$versionId = $_POST['version_id'];
$version = $_POST['version'];
$changelog = $_POST['changelog'] ?? '';
// 检查是否有新文件上传
if (!empty($_FILES['new_app_file']['name'])) {
$uploadDir = '../files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$fileName = basename($_FILES['new_app_file']['name']);
$newFilePath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['new_app_file']['tmp_name'], $newFilePath)) {
// 获取旧文件路径并删除
$getOldPathSql = "SELECT file_path FROM app_versions WHERE id = ?";
$getOldPathStmt = $conn->prepare($getOldPathSql);
if (!$getOldPathStmt) {
log_error("获取旧文件路径查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本修改失败,请稍后再试';
unlink($newFilePath);
} else {
$getOldPathStmt->bind_param("i", $versionId);
$getOldPathStmt->execute();
$oldPathResult = $getOldPathStmt->get_result();
if ($oldPathResult->num_rows > 0) {
$oldPathRow = $oldPathResult->fetch_assoc();
$oldFilePath = $oldPathRow['file_path'];
if (file_exists($oldFilePath)) {
unlink($oldFilePath);
}
}
// 更新版本信息
$updateVersionSql = "UPDATE app_versions SET version = ?, changelog = ?, file_path = ? WHERE id = ?";
$updateVersionStmt = $conn->prepare($updateVersionSql);
if (!$updateVersionStmt) {
log_error("版本更新查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本修改失败,请稍后再试';
unlink($newFilePath);
} else {
$updateVersionStmt->bind_param("sssi", $version, $changelog, $newFilePath, $versionId);
if ($updateVersionStmt->execute()) {
$success = '版本修改成功';
} else {
$error = '版本修改失败: ' . $conn->error;
unlink($newFilePath);
}
}
}
} else {
$error = '文件上传失败';
}
} else {
// 仅更新版本号和更新日志
$updateVersionSql = "UPDATE app_versions SET version = ?, changelog = ? WHERE id = ?";
$updateVersionStmt = $conn->prepare($updateVersionSql);
if (!$updateVersionStmt) {
log_error("版本更新查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本修改失败,请稍后再试';
} else {
$updateVersionStmt->bind_param("ssi", $version, $changelog, $versionId);
if ($updateVersionStmt->execute()) {
$success = '版本修改成功';
} else {
$error = '版本修改失败: ' . $conn->error;
}
}
}
}
// 处理版本删除请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_version'])) {
$versionId = $_POST['version_id'];
$filePath = $_POST['file_path'];
// 删除文件
if (file_exists($filePath)) {
if (!unlink($filePath)) {
log_error("文件删除失败: " . $filePath, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
}
}
// 从数据库删除版本记录
$deleteVersionSql = "DELETE FROM app_versions WHERE id = ?";
$deleteVersionStmt = $conn->prepare($deleteVersionSql);
if (!$deleteVersionStmt) {
log_error("版本删除查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
} else {
$deleteVersionStmt->bind_param("i", $versionId);
if ($deleteVersionStmt->execute()) {
$success = '版本删除成功';
} else {
$error = '版本删除失败: ' . $conn->error;
}
}
if (!empty($success)) {
echo $success;
} else {
echo $error;
}
exit;
}
// 获取现有版本列表
$versions = [];
$getVersionsSql = "SELECT * FROM app_versions WHERE app_id = ? ORDER BY id DESC";
$verStmt = $conn->prepare($getVersionsSql);
if (!$verStmt) {
log_error("版本查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '获取版本列表失败,请稍后再试';
} else {
$verStmt->bind_param("i", $appId);
$verStmt->execute();
$versionsResult = $verStmt->get_result();
while ($ver = $versionsResult->fetch_assoc()) {
$versions[] = $ver;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>版本控制 - <?php echo htmlspecialchars($app['name']); ?></title>
<link href="../css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="/css/all.min.css">
<link rel="stylesheet" href="../styles.css">
<script src="/js/sweetalert.js"></script>
<style>
.blur-bg {
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.5);
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light blur-bg">
<div class="container">
<a href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
<a class="navbar-brand" href="../index.php"><?php echo APP_STORE_NAME; ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">我的应用</a>
</li>
<li class="nav-item">
<a class="nav-link" href="upload_app.php">上传新应用</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="version_control.php?id=<?php echo $appId; ?>">版本控制</a>
</li>
<li class="nav-item">
<a class="nav-link" href="profile.php">个人资料</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">退出登录</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<?php if (!empty($success)): ?>
<!-- <script>Swal.fire('成功', '<?php echo addslashes($success); ?>', 'success');</script> -->
<?php endif; ?>
<?php if (!empty($error)): ?>
<!-- <script>Swal.fire('错误', '<?php echo addslashes($error); ?>', 'error');</script> -->
<?php endif; ?>
<div class="card blur-bg mb-4">
<div class="card-header">
<h2>应用版本控制: <?php echo htmlspecialchars($app['name']); ?></h2>
</div>
<div class="card-body">
<h4>上传新版本</h4>
<form method="post" enctype="multipart/form-data" class="mb-4">
<div class="row g-3">
<div class="col-md-6">
<div class="form-floating">
<input type="text" class="form-control" id="version" name="version" placeholder="版本号" required>
<label for="version">版本号 (如: 1.0.0)</label>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="app_file" class="form-label">安装包文件</label>
<input class="form-control" type="file" id="app_file" name="app_file" required>
</div>
</div>
</div>
<div class="form-floating mb-3">
<textarea class="form-control" id="changelog" name="changelog" rows="3" placeholder="更新日志"></textarea>
<label for="changelog">更新日志支持Markdown</label>
</div>
<button type="submit" class="btn btn-primary" name="upload_version"><i class="fas fa-cloud-upload-alt me-1"></i>上传新版本</button>
<a href="dashboard.php" class="btn btn-secondary ms-2"><i class="fas fa-arrow-left me-1"></i>返回</a>
</form>
<hr>
<h4>版本历史</h4>
<?php if (empty($versions)): ?>
<div class="alert alert-info">暂无版本记录</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>版本号</th>
<th>上传时间</th>
<th>更新日志</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($versions as $ver): ?>
<tr>
<td><?php echo htmlspecialchars($ver['version']); ?></td>
<td><?php echo htmlspecialchars($ver['upload_time']); ?></td>
<td><?php echo nl2br(htmlspecialchars($ver['changelog'] ?: '无')); ?></td>
<td>
<a href="../download.php?id=<?php echo $ver['id']; ?>&type=version" class="btn btn-sm btn-outline-primary"><i class="fas fa-download me-1"></i>下载</a>
<a href="#" class="btn btn-sm btn-outline-warning ms-2" onclick="openEditModal(<?php echo $ver['id']; ?>, '<?php echo htmlspecialchars($ver['version']); ?>', '<?php echo htmlspecialchars($ver['changelog']); ?>')"><i class="fas fa-edit me-1"></i>修改</a>
<a href="#" class="btn btn-sm btn-outline-danger ms-2" onclick="confirmDelete(<?php echo $ver['id']; ?>, '<?php echo htmlspecialchars($ver['file_path']); ?>')"><i class="fas fa-trash-alt me-1"></i>删除</a>
<?php if ($ver['is_current'] == 1): ?>
<span class="badge bg-success">当前版本</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script src="../js/bootstrap.bundle.js"></script>
<script>
function openEditModal(versionId, version, changelog) {
const modal = `
<div class="modal fade" id="editVersionModal" tabindex="-1" aria-labelledby="editVersionModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editVersionModalLabel">修改版本</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="editVersionForm" method="post" enctype="multipart/form-data">
<div class="modal-body">
<input type="hidden" name="version_id" value="${versionId}">
<div class="form-floating mb-3">
<input type="text" class="form-control" id="editVersion" name="version" value="${version}" required>
<label for="editVersion">版本号</label>
</div>
<div class="form-floating mb-3">
<textarea class="form-control" id="editChangelog" name="changelog" rows="3">${changelog}</textarea>
<label for="editChangelog">更新日志</label>
</div>
<div class="mb-3">
<label for="new_app_file" class="form-label">更新App文件 (可选)</label>
<input class="form-control" type="file" id="new_app_file" name="new_app_file">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><i class="fas fa-times me-1"></i>取消</button>
<button type="submit" class="btn btn-primary"><i class="fas fa-save me-1"></i>保存修改</button>
</div>
</form>
</div>
</div>
</div>`;
document.body.insertAdjacentHTML('beforeend', modal);
const editModal = new bootstrap.Modal(document.getElementById('editVersionModal'));
editModal.show();
document.getElementById('editVersionForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('version_control.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
editModal.hide();
window.location.reload();
})
.catch(error => {
editModal.hide();
window.location.reload();
});
});
}
function confirmDelete(versionId, filePath) {
const formData = new FormData();
formData.append('delete_version', 'true');
formData.append('version_id', versionId);
formData.append('file_path', filePath);
fetch('version_control.php', { method: 'POST', body: formData })
.then(response => response.text())
.then(data => {
window.location.reload();
})
.catch(error => {
window.location.reload();
});
}
</script>
</body>
</html>