feat: 添加开发者功能模块和审核系统
- 新增开发者注册、登录、仪表盘和登出功能 - 实现应用上传、编辑和管理功能 - 添加管理员审核应用和管理开发者功能 - 完善数据库结构支持开发者系统 - 增加错误日志记录功能 - 更新.gitignore忽略上传目录和系统文件
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# 忽略上传的应用文件
|
||||
uploads/
|
||||
|
||||
# 忽略文件目录
|
||||
files/
|
||||
|
||||
# 忽略图片目录
|
||||
images/
|
||||
|
||||
# 忽略日志文件
|
||||
logs/
|
||||
|
||||
# 忽略Windows系统文件
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
@@ -16,7 +16,7 @@ if (isset($_GET['logout'])) {
|
||||
}
|
||||
|
||||
// 获取App列表
|
||||
$sqlApps = "SELECT * FROM apps ORDER BY created_at DESC";
|
||||
$sqlApps = "SELECT * FROM apps WHERE status = 'approved' ORDER BY created_at DESC";
|
||||
$resultApps = $conn->query($sqlApps);
|
||||
|
||||
if (!$resultApps) {
|
||||
@@ -56,7 +56,13 @@ if (!$resultApps) {
|
||||
<a class="nav-link active" aria-current="page" href="index.php">App列表</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="addapp.php">添加App</a>
|
||||
<a class="nav-link" href="addapp.php">添加App</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="review_apps.php">审核APP</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="manage_developers.php">管理开发者</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="?logout=true">退出登录</a>
|
||||
|
||||
246
admin/manage_developers.php
Normal file
246
admin/manage_developers.php
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
require_once '../config.php';
|
||||
// 检查管理员权限
|
||||
// 设置会话cookie路径为根目录以确保跨目录访问
|
||||
session_set_cookie_params(0, '/');
|
||||
// 检查会话是否已启动,避免重复启动
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
if (!session_start()) {
|
||||
error_log('会话启动失败');
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
|
||||
error_log('会话启动失败');
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
// 从数据库验证用户角色,确保权限检查准确性
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
$userId = $_SESSION['user_id'];
|
||||
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ?");
|
||||
if (!$stmt) {
|
||||
error_log('Database prepare failed: ' . $conn->error);
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
$stmt->bind_param("i", $userId);
|
||||
if (!$stmt->execute()) {
|
||||
error_log('Query execution failed: ' . $stmt->error);
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
$result = $stmt->get_result();
|
||||
if (!$result) {
|
||||
error_log('Failed to get result: ' . $stmt->error);
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
$user = $result->fetch_assoc();
|
||||
|
||||
if (!$user || $user['role'] !== 'admin') {
|
||||
error_log('用户 ' . $userId . ' 不是管理员,拒绝访问');
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
error_log('未找到用户会话,重定向到登录页');
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除用户请求
|
||||
if (isset($_POST['delete_user'])) {
|
||||
$userId = $_POST['user_id'];
|
||||
$stmt = $conn->prepare("DELETE FROM users WHERE id = ? AND role = 'developer'");
|
||||
$stmt->bind_param("i", $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
header("Location: manage_developers.php?deleted=true");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 处理更新用户请求
|
||||
if (isset($_POST['update_user'])) {
|
||||
$userId = $_POST['user_id'];
|
||||
$username = $_POST['username'];
|
||||
$email = $_POST['email'];
|
||||
|
||||
$stmt = $conn->prepare("UPDATE users SET username = ?, email = ? WHERE id = ? AND role = 'developer'");
|
||||
$stmt->bind_param("ssi", $username, $email, $userId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
header("Location: manage_developers.php?updated=true");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 获取所有开发者用户
|
||||
$developers = [];
|
||||
$result = $conn->query("SELECT id, username, email, created_at FROM users WHERE role = 'developer' ORDER BY created_at DESC");
|
||||
if (!$result) {
|
||||
error_log('Failed to fetch developers: ' . $conn->error);
|
||||
die('获取开发者列表失败,请稍后重试');
|
||||
}
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$developers[] = $row;
|
||||
}
|
||||
|
||||
// 获取要编辑的用户信息
|
||||
$editUser = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$editUserId = $_GET['edit'];
|
||||
$stmt = $conn->prepare("SELECT id, username, email FROM users WHERE id = ? AND role = 'developer'");
|
||||
$stmt->bind_param("i", $editUserId);
|
||||
$stmt->execute();
|
||||
$editUser = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
}
|
||||
?>
|
||||
<!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="stylesheet" href="../styles.css">
|
||||
<style>
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.user-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.user-table th, .user-table td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.user-table th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
margin: 0 5px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edit-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
.delete-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
.edit-form {
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.submit-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.message {
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.success {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>管理开发者用户</h1>
|
||||
|
||||
<?php if (isset($_GET['deleted'])): ?>
|
||||
<div class="message success">用户已成功删除</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['updated'])): ?>
|
||||
<div class="message success">用户信息已成功更新</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($editUser): ?>
|
||||
<div class="edit-form">
|
||||
<h2>编辑开发者用户</h2>
|
||||
<form method="post" action="manage_developers.php">
|
||||
<input type="hidden" name="user_id" value="<?php echo $editUser['id']; ?>">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" value="<?php echo htmlspecialchars($editUser['username']); ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">邮箱</label>
|
||||
<input type="email" id="email" name="email" value="<?php echo htmlspecialchars($editUser['email']); ?>" required>
|
||||
</div>
|
||||
<button type="submit" name="update_user" class="submit-btn">更新用户</button>
|
||||
<a href="manage_developers.php" class="action-btn">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<table class="user-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($developers as $developer): ?>
|
||||
<tr>
|
||||
<td><?php echo $developer['id']; ?></td>
|
||||
<td><?php echo htmlspecialchars($developer['username']); ?></td>
|
||||
<td><?php echo htmlspecialchars($developer['email']); ?></td>
|
||||
<td><?php echo $developer['created_at']; ?></td>
|
||||
<td>
|
||||
<a href="manage_developers.php?edit=<?php echo $developer['id']; ?>" class="action-btn edit-btn">编辑</a>
|
||||
<form method="post" action="manage_developers.php" style="display: inline-block;" onsubmit="return confirm('确定要删除这个用户吗?');">
|
||||
<input type="hidden" name="user_id" value="<?php echo $developer['id']; ?>">
|
||||
<button type="submit" name="delete_user" class="action-btn delete-btn">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($developers)): ?>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center;">暂无开发者用户</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
208
admin/review_apps.php
Normal file
208
admin/review_apps.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
require_once '../config.php';
|
||||
|
||||
session_start();
|
||||
// 检查管理员登录状态
|
||||
if (!isset($_SESSION['admin'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$success = '';
|
||||
$error = '';
|
||||
|
||||
// 处理审核操作
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['review_action'])) {
|
||||
$appId = $_POST['app_id'];
|
||||
$action = $_POST['review_action'];
|
||||
$rejectionReason = $_POST['rejection_reason'] ?? '';
|
||||
|
||||
// 验证应用ID
|
||||
if (!is_numeric($appId)) {
|
||||
$error = '无效的应用ID';
|
||||
} else {
|
||||
// 检查数据库连接
|
||||
if (!($conn instanceof mysqli)) {
|
||||
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
|
||||
$error = '数据库连接错误,请检查配置';
|
||||
} else {
|
||||
// 更新应用状态
|
||||
$status = $action === 'approve' ? 'approved' : 'rejected';
|
||||
$stmt = $conn->prepare("UPDATE apps SET status = ?, rejection_reason = ? WHERE id = ?");
|
||||
if (!$stmt) {
|
||||
$error = "数据库错误: " . $conn->error;
|
||||
} else {
|
||||
$stmt->bind_param("ssi", $status, $rejectionReason, $appId);
|
||||
if ($stmt->execute()) {
|
||||
$success = '应用审核已更新';
|
||||
} else {
|
||||
$error = '更新审核状态失败: ' . $conn->error;
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取待审核应用列表
|
||||
$pendingApps = [];
|
||||
if (!($conn instanceof mysqli)) {
|
||||
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
|
||||
$error = '数据库连接错误,请检查配置';
|
||||
} else {
|
||||
$stmt = $conn->prepare("SELECT a.id, a.name, a.description, a.status, a.created_at
|
||||
FROM apps a
|
||||
WHERE a.status = 'pending'
|
||||
ORDER BY a.created_at DESC");
|
||||
if (!$stmt) {
|
||||
$error = "数据库错误: " . $conn->error;
|
||||
} else {
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$pendingApps = $result->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt->close();
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!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="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- 自定义CSS -->
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<!-- Fluent Design 模糊效果 -->
|
||||
<style>
|
||||
.blur-bg {
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.app-card {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.app-card:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<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="index.php">App列表</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="addapp.php">添加App</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="review_apps.php">应用审核</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="?logout=true">退出登录</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<?php if (!empty($success)): ?>
|
||||
<div class="alert alert-success"><?php echo $success; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($error)): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2>应用审核</h2>
|
||||
<p class="text-muted">待审核应用: <?php echo count($pendingApps); ?></p>
|
||||
|
||||
<?php if (empty($pendingApps)): ?>
|
||||
<div class="alert alert-info">没有待审核的应用</div>
|
||||
<?php else: ?>
|
||||
<div class="row">
|
||||
<?php foreach ($pendingApps as $app): ?>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card app-card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0"><?php echo htmlspecialchars($app['name']); ?></h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text"><strong>开发者:</strong> <?php echo htmlspecialchars($app['username']); ?></p>
|
||||
<p class="card-text"><strong>提交时间:</strong> <?php echo htmlspecialchars($app['created_at']); ?></p>
|
||||
<p class="card-text"><strong>描述:</strong> <?php echo nl2br(htmlspecialchars($app['description'])); ?></p>
|
||||
|
||||
<!-- 获取应用图片 -->
|
||||
<?php
|
||||
$images = [];
|
||||
$stmt = $conn->prepare("SELECT image_path FROM app_images WHERE app_id = ?");
|
||||
$stmt->bind_param("i", $app['id']);
|
||||
$stmt->execute();
|
||||
$imgResult = $stmt->get_result();
|
||||
while ($img = $imgResult->fetch_assoc()) {
|
||||
$images[] = $img['image_path'];
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
|
||||
<?php if (!empty($images)): ?>
|
||||
<div class="mb-3">
|
||||
<strong>预览图片:</strong><br>
|
||||
<img src="<?php echo htmlspecialchars($images[0]); ?>" alt="应用截图" class="img-thumbnail" style="max-width: 200px;">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" class="mt-3">
|
||||
<input type="hidden" name="app_id" value="<?php echo $app['id']; ?>">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" name="review_action" value="approve" class="btn btn-success flex-grow-1">通过</button>
|
||||
<button type="button" class="btn btn-danger flex-grow-1" data-bs-toggle="modal" data-bs-target="#rejectModal<?php echo $app['id']; ?>">拒绝</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 拒绝原因模态框 -->
|
||||
<div class="modal fade" id="rejectModal<?php echo $app['id']; ?>" tabindex="-1" aria-labelledby="rejectModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="rejectModalLabel">拒绝应用: <?php echo htmlspecialchars($app['name']); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="app_id" value="<?php echo $app['id']; ?>">
|
||||
<div class="mb-3">
|
||||
<label for="rejection_reason<?php echo $app['id']; ?>" class="form-label">拒绝原因</label>
|
||||
<textarea class="form-control" id="rejection_reason<?php echo $app['id']; ?>" name="rejection_reason" rows="3" required></textarea>
|
||||
<div class="form-text">请详细说明拒绝原因,帮助开发者改进应用</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="submit" name="review_action" value="reject" class="btn btn-danger">确认拒绝</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,7 +11,10 @@ CREATE TABLE IF NOT EXISTS apps (
|
||||
age_rating_description TEXT,
|
||||
platforms JSON NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
version VARCHAR(20) NOT NULL,
|
||||
changelog TEXT NOT NULL,
|
||||
file_path VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
-- 创建APP版本表
|
||||
@@ -165,6 +168,41 @@ CREATE TABLE IF NOT EXISTS user_favorites (
|
||||
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 创建开发者表
|
||||
CREATE TABLE IF NOT EXISTS developers (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 修改 apps 表,添加 developer_id 和 status 字段
|
||||
SET @exist_developer_id = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apps' AND COLUMN_NAME = 'developer_id');
|
||||
SET @sql = IF(@exist_developer_id = 0, 'ALTER TABLE apps ADD COLUMN developer_id INT', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist_status = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apps' AND COLUMN_NAME = 'status');
|
||||
SET @sql = IF(@exist_status = 0, 'ALTER TABLE apps ADD COLUMN status ENUM(''pending'', ''approved'', ''rejected'') DEFAULT ''pending''', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 添加应用拒绝原因字段
|
||||
SET @exist_rejection_reason = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apps' AND COLUMN_NAME = 'rejection_reason');
|
||||
SET @sql = IF(@exist_rejection_reason = 0, 'ALTER TABLE apps ADD COLUMN rejection_reason TEXT NULL', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist_fk = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'apps' AND COLUMN_NAME = 'developer_id' AND CONSTRAINT_NAME = 'fk_apps_developers');
|
||||
SET @sql = IF(@exist_fk = 0, 'ALTER TABLE apps ADD CONSTRAINT fk_apps_developers FOREIGN KEY (developer_id) REFERENCES developers(id) ON DELETE SET NULL', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 创建应用推荐表
|
||||
CREATE TABLE IF NOT EXISTS app_recommendations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
17
config.php
17
config.php
@@ -15,10 +15,25 @@ define('ADMIN_PASSWORD', '123456');
|
||||
// 数据库连接
|
||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
|
||||
if ($conn->connect_error) {
|
||||
die('数据库连接失败: ' . $conn->connect_error);
|
||||
$error_msg = '数据库连接失败: ' . $conn->connect_error;
|
||||
log_error($error_msg, __FILE__, __LINE__);
|
||||
die($error_msg);
|
||||
}
|
||||
$conn->set_charset('utf8mb4');
|
||||
|
||||
// 设置时区
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
// 错误日志记录函数
|
||||
function log_error($message, $file = '', $line = '') {
|
||||
$log_entry = date('[Y-m-d H:i:s]') . ' Error: ' . $message;
|
||||
if (!empty($file)) {
|
||||
$log_entry .= ' in ' . $file;
|
||||
}
|
||||
if (!empty($line)) {
|
||||
$log_entry .= ' on line ' . $line;
|
||||
}
|
||||
$log_entry .= "\n";
|
||||
file_put_contents('d:\\app2\\logs\\error.log', $log_entry, FILE_APPEND);
|
||||
}
|
||||
?>
|
||||
211
developer/dashboard.php
Normal file
211
developer/dashboard.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?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="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- 自定义CSS -->
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<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;
|
||||
}
|
||||
.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>
|
||||
<!-- 导航栏 -->
|
||||
<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 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="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">上传新应用</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">编辑</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap JS and Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
434
developer/edit_app.php
Normal file
434
developer/edit_app.php
Normal file
@@ -0,0 +1,434 @@
|
||||
<?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']; // 默认使用现有文件路径
|
||||
|
||||
// 处理应用文件上传
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证标签选择
|
||||
$selectedTags = $_POST['tags'] ?? [];
|
||||
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>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
width: 500px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
resize: vertical;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type="submit"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.success {
|
||||
color: green;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.back-link {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.back-link a {
|
||||
color: #007BFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>编辑应用</h2>
|
||||
<?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; ?>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="name">应用名称</label>
|
||||
<input type="text" id="name" name="name" value="<?php echo htmlspecialchars($app['name']); ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">应用描述</label>
|
||||
<textarea id="description" name="description" rows="5" required><?php echo htmlspecialchars($app['description']); ?></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="version">版本号</label>
|
||||
<input type="text" id="version" name="version" value="<?php echo htmlspecialchars($app['version']); ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="changelog">更新日志</label>
|
||||
<textarea id="changelog" name="changelog" rows="3" required><?php echo htmlspecialchars($app['changelog']); ?></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="age_rating">年龄分级</label>
|
||||
<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="form-group">
|
||||
<label for="age_rating_description">年龄分级说明</label>
|
||||
<input type="text" id="age_rating_description" name="age_rating_description" value="<?php echo htmlspecialchars($app['age_rating_description']); ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>适用平台</label>
|
||||
<?php $platforms = json_decode($app['platforms'], true) ?? []; ?>
|
||||
<div>
|
||||
<input type="checkbox" id="platform_android" name="platforms[]" value="Android" <?php echo in_array('Android', $platforms) ? 'checked' : ''; ?>>
|
||||
<label for="platform_android">Android</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="platform_ios" name="platforms[]" value="iOS" <?php echo in_array('iOS', $platforms) ? 'checked' : ''; ?>>
|
||||
<label for="platform_ios">iOS</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>应用标签 (至少选择1个)</label>
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<div>
|
||||
<input type="checkbox" id="tag_<?php echo $tag['id']; ?>" name="tags[]" value="<?php echo $tag['id']; ?>" <?php echo in_array($tag['id'], $appTags) ? 'checked' : ''; ?>>
|
||||
<label for="tag_<?php echo $tag['id']; ?>"><?php echo htmlspecialchars($tag['name']); ?></label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="app_file">更新应用文件</label>
|
||||
<input type="file" id="app_file" name="app_file">
|
||||
<small>当前文件: <?php echo basename($app['file_path']); ?></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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="">
|
||||
<input type="submit" value="保存更改">
|
||||
</form>
|
||||
<div class="back-link">
|
||||
<a href="dashboard.php">返回仪表盘</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();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
142
developer/login.php
Normal file
142
developer/login.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
// 引入配置文件
|
||||
require_once '../config.php';
|
||||
|
||||
session_start();
|
||||
$error = '';
|
||||
|
||||
if (isset($_GET['register_success']) && $_GET['register_success'] == 1) {
|
||||
$success = '注册成功,请登录';
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = trim($_POST['email']);
|
||||
$password = $_POST['password'];
|
||||
|
||||
if (empty($email) || 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 = ?');
|
||||
if (!$stmt) {
|
||||
log_error('登录查询准备失败: ' . $conn->error, __FILE__, __LINE__);
|
||||
$error = '登录时发生错误,请稍后再试';
|
||||
} else {
|
||||
$stmt->bind_param('s', $email);
|
||||
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'];
|
||||
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>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
width: 300px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type="submit"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.success {
|
||||
color: green;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.register-link {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>开发者登录</h2>
|
||||
<?php if (isset($success)): ?>
|
||||
<div class="success"><?php echo $success; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($error)): ?>
|
||||
<div class="error"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label for="email">邮箱</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<input type="submit" value="登录">
|
||||
</form>
|
||||
<div class="register-link">
|
||||
还没有账号?<a href="register.php">注册</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
10
developer/logout.php
Normal file
10
developer/logout.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// 销毁会话数据
|
||||
session_unset();
|
||||
session_destroy();
|
||||
|
||||
// 重定向到登录页面
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
136
developer/register.php
Normal file
136
developer/register.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
// 引入配置文件
|
||||
require_once '../config.php';
|
||||
|
||||
$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 (!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);
|
||||
$insertStmt = $conn->prepare('INSERT INTO developers (username, email, password) VALUES (?, ?, ?)');
|
||||
if (!$insertStmt) {
|
||||
log_error('插入准备失败: ' . $conn->error, __FILE__, __LINE__);
|
||||
$error = '系统错误,请稍后再试';
|
||||
} else {
|
||||
$insertStmt->bind_param('sss', $username, $email, $hashedPassword);
|
||||
if (!$insertStmt->execute()) {
|
||||
log_error('插入执行失败: ' . $insertStmt->error, __FILE__, __LINE__);
|
||||
$error = '系统错误,请稍后再试';
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: login.php?register_success=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>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
width: 300px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
input[type="submit"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>开发者注册</h2>
|
||||
<?php if (!empty($error)): ?>
|
||||
<div class="error"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">邮箱</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<input type="submit" value="注册">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
470
developer/upload_app.php
Normal file
470
developer/upload_app.php
Normal file
@@ -0,0 +1,470 @@
|
||||
<?php
|
||||
// 引入配置文件
|
||||
require_once '../config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
// 检查开发者是否已登录
|
||||
if (!isset($_SESSION['developer_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$developerId = $_SESSION['developer_id'];
|
||||
$error = '';
|
||||
$success = '';
|
||||
|
||||
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($version) || !preg_match('/^\d+\.\d+\.\d+$/', $version)) {
|
||||
$error = '版本号格式不正确,应为X.X.X格式';
|
||||
} 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'] > 100 * 1024 * 1024) {
|
||||
log_error('应用文件过大: ' . number_format($appFile['size'] / 1024 / 1024, 2) . 'MB', __FILE__, __LINE__);
|
||||
$error = '应用文件大小不能超过100MB';
|
||||
}
|
||||
$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 {
|
||||
$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[] = $imagePath;
|
||||
} 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('缺少必要的上传参数');
|
||||
}
|
||||
|
||||
// 插入应用基本信息
|
||||
$filePath = ''; // 初始化file_path为空字符串以满足数据库要求
|
||||
// 添加created_at字段并设置为当前时间戳
|
||||
$stmt = $conn->prepare('INSERT INTO apps (name, description, developer_id, platforms, status, age_rating, age_rating_description, version, changelog, file_path, 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('ssissssss', $appName, $appDescription, $developerId, $platforms_json, $ageRating, $ageRatingDescription, $version, $changelog, $filePath);
|
||||
if (!$stmt->execute()) {
|
||||
throw new Exception('应用基本信息查询执行失败: ' . $stmt->error);
|
||||
}
|
||||
$appId = $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
// 插入应用标签关联
|
||||
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 $imagePath) {
|
||||
$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();
|
||||
|
||||
// 提交事务
|
||||
$conn->commit();
|
||||
$success = '应用上传成功,请等待管理员审核';
|
||||
} catch (Exception $e) {
|
||||
// 回滚事务
|
||||
$conn->rollback();
|
||||
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>上传应用 - <?php echo APP_STORE_NAME; ?></title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- 自定义CSS -->
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
<!-- 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="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</head>
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<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 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 > 100 * 1024 * 1024) { // 100MB限制
|
||||
alert('文件大小不能超过100MB');
|
||||
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限制
|
||||
alert(`图片 ${file.name} 大小不能超过10MB`);
|
||||
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="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">应用描述</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="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>
|
||||
<input type="submit" value="上传" class="btn btn-primary w-100 py-2">
|
||||
</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>
|
||||
16
index.php
16
index.php
@@ -41,9 +41,22 @@ if (!isset($conn) || !$conn instanceof mysqli) {
|
||||
<a class="nav-link" href="/admin/">管理</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="tags.php">标签</a>
|
||||
</li>
|
||||
<?php if (isset($_SESSION['developer_id'])): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="developer/dashboard.php">进入面板</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="developer/register.php">开发者注册</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="developer/login.php">开发者登录</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,6 +147,9 @@ if (!isset($conn) || !$conn instanceof mysqli) {
|
||||
$paramTypes .= 'ss';
|
||||
}
|
||||
|
||||
// 只显示已审核通过的应用
|
||||
$conditions[] = "apps.status = 'approved'";
|
||||
|
||||
// 添加条件
|
||||
if (!empty($conditions)) {
|
||||
$sql .= "WHERE " . implode(" AND ", $conditions);
|
||||
|
||||
Reference in New Issue
Block a user