commit accf0760bb23da282b3ab342474eb2499ec62081 Author: Your Name Date: Sun Jul 6 19:46:42 2025 +0800 feat: 实现完整的应用商店系统 - 添加前端页面包括首页、应用详情页和版本历史页 - 实现管理员后台功能,包括应用管理、版本管理和登录验证 - 添加API接口用于获取应用列表和详情 - 实现文件上传和下载功能 - 添加Windows控制台和GUI客户端 - 完善数据库结构和初始化脚本 - 添加样式表和图片资源 diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..393d9da --- /dev/null +++ b/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^api(.*)$ api.php$1 [L] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f176b71 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# App Store 项目 + +这是一个基于 PHP 7.4 的 App Store 项目,使用 Bootstrap 实现 Fluent Design 风格界面,数据存储采用 MySQL 数据库。 + +## 项目结构 +``` +app2/ +├── config.php # 配置文件,包含数据库和管理员信息 +├── app_store.sql # 数据库初始化 SQL 文件 +├── index.php # 首页 +├── app.php # App 信息页 +├── admin.php # App 管理页 +├── api.php # API 接口文件 +├── styles.css # 自定义 CSS 文件 +├── images/ # 存储 App 预览图片和年龄分级 SVG +│ ├── age_3plus.svg +│ ├── age_7plus.svg +│ ├── age_12plus.svg +│ ├── age_17plus.svg +├── files/ # 存储 App 文件 +``` + +## 环境要求 +- PHP 7.4 +- MySQL +- Web 服务器(如 Apache 或 Nginx) + +## 安装步骤 +1. 创建项目目录并将代码复制到该目录下。 +2. 修改 `config.php` 文件,配置 MySQL 数据库信息和管理员账号。 +3. 执行 `app_store.sql` 文件,创建数据库和表结构。可以使用以下命令: + ```sql + mysql -u your_username -p your_database < app_store.sql + ``` +4. 创建 `files` 和 `images` 目录,并确保 Web 服务器对这些目录有写入权限。 + +## 功能说明 +- **首页**:展示最新 App 列表,包含基本信息和评分。 +- **App 信息页**:显示 App 详细信息、版本历史、预览图片和用户评价,支持用户评分。 +- **管理页**:管理员可以添加、删除 App,上传 App 文件和预览图片。 +- **API 接口**:提供 `/api` 获取 App 列表,`/api/app/<编号>` 获取单个 App 详细信息。 + +## 管理员登录 +默认管理员账号信息在 `config.php` 中配置,登录后可访问管理页面。 + +## 注意事项 +- 请确保 `files` 和 `images` 目录有足够的写入权限。 +- 生产环境中建议修改管理员密码和数据库信息,保证系统安全。 \ No newline at end of file diff --git a/admin/addapp.php b/admin/addapp.php new file mode 100644 index 0000000..e07fe1d --- /dev/null +++ b/admin/addapp.php @@ -0,0 +1,313 @@ +prepare("INSERT INTO apps (name, description, age_rating, age_rating_description, platforms) VALUES (?, ?, ?, ?, ?)"); + if (!$stmt) { + $error = "Database error: " . $conn->error; + } + if ($stmt) { + $stmt->bind_param("sssss", $name, $description, $ageRating, $_POST['age_rating_description'], $platforms); + if ($stmt->execute() === TRUE) { + $appId = $stmt->insert_id; + + // 处理上传的预览图片 + if (!empty($_FILES['images']['name'][0])) { + $uploadDir = '../images/'; + foreach ($_FILES['images']['tmp_name'] as $key => $tmpName) { + $fileName = basename($_FILES['images']['name'][$key]); + $targetPath = $uploadDir . $fileName; + if (move_uploaded_file($tmpName, $targetPath)) { + $insertImageSql = "INSERT INTO app_images (app_id, image_path) VALUES (?, ?)"; + $imgStmt = $conn->prepare($insertImageSql); + $imgStmt->bind_param("is", $appId, $targetPath); + $imgStmt->execute(); + } + } + } + + // 处理上传的App文件 + if (!empty($_FILES['app_file']['name'])) { + $uploadDir = '../files/'; + $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); + $verStmt->bind_param("isss", $appId, $version, $changelog, $targetPath); + $verStmt->execute(); + } + } + + header('Location: index.php?success=App 添加成功'); + exit; + } else { + $error = 'App 添加失败: '. $conn->error; + } +} +} +?> + + + + + + 添加App - <?php echo APP_STORE_NAME; ?> + + + + + + + + + +
+ +
+ + + + +
+ +
+ + +
+ + +

添加App

+
+
+ + +
+
+ + +
+
+ + +
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + 取消 +
+
+ + + + + + diff --git a/admin/deleteapp.php b/admin/deleteapp.php new file mode 100644 index 0000000..9d05f8b --- /dev/null +++ b/admin/deleteapp.php @@ -0,0 +1,41 @@ +prepare($deleteAppSql); +$stmt->bind_param("i", $appId); + +if ($stmt->execute() === TRUE) { + // 删除关联的图片 + $deleteImagesSql = "DELETE FROM app_images WHERE app_id = ?"; + $imgStmt = $conn->prepare($deleteImagesSql); + $imgStmt->bind_param("i", $appId); + $imgStmt->execute(); + + // 删除关联的版本 + $deleteVersionsSql = "DELETE FROM app_versions WHERE app_id = ?"; + $verStmt = $conn->prepare($deleteVersionsSql); + $verStmt->bind_param("i", $appId); + $verStmt->execute(); + + header('Location: index.php?success=App 删除成功'); +} else { + header('Location: index.php?error=App 删除失败: '. $conn->error); +} +exit; +?> \ No newline at end of file diff --git a/admin/editapp.php b/admin/editapp.php new file mode 100644 index 0000000..043b7e8 --- /dev/null +++ b/admin/editapp.php @@ -0,0 +1,347 @@ +prepare($getAppSql); +$stmt->bind_param("i", $appId); +$stmt->execute(); +$result = $stmt->get_result(); +if ($result->num_rows === 0) { + header('Location: index.php?error=App不存在'); + exit; +} +$app = $result->fetch_assoc(); +$platforms = json_decode($app['platforms'], true); + +$success = ''; +$error = ''; +// 处理编辑App请求 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_app'])) { + $name = $_POST['name']; + $description = $_POST['description']; + $ageRating = $_POST['age_rating']; + $newPlatforms = json_encode($_POST['platforms'] ?? []); + + // 处理表单提交 + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // 验证必填字段 + $required = ['name', 'description', 'age_rating', 'platforms']; + $errors = []; + foreach ($required as $field) { + if (empty($_POST[$field])) { + $errors[] = ucfirst($field) . ' 不能为空'; + } + } + + // 年龄分级验证 + if (($_POST['age_rating'] === '12+' || $_POST['age_rating'] === '17+') && empty($_POST['age_rating_description'])) { + $errors[] = '年龄分级为12+或以上时,年龄分级说明不能为空'; + } + + // 处理应用图标上传(如果有新上传) + if (!empty($_FILES['images']['name'][0])) { + $uploadDir = '../images/'; + foreach ($_FILES['images']['tmp_name'] as $key => $tmpName) { + $fileName = basename($_FILES['images']['name'][$key]); + $targetPath = $uploadDir . $fileName; + if (move_uploaded_file($tmpName, $targetPath)) { + $insertImageSql = "INSERT INTO app_images (app_id, image_path) VALUES (?, ?)"; + $imgStmt = $conn->prepare($insertImageSql); + $imgStmt->bind_param("is", $appId, $targetPath); + $imgStmt->execute(); + } + } + } + + // 处理新上传的App文件 + if (!empty($_FILES['app_file']['name'])) { + $uploadDir = '../files/'; + $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); + $verStmt->bind_param("isss", $appId, $version, $changelog, $targetPath); + $verStmt->execute(); + } + } + + header('Location: index.php?success=App 更新成功'); + exit; + } else { + $error = 'App 更新失败: '. $conn->error; + } +} +?> + + + + + + 编辑App - <?php echo APP_STORE_NAME; ?> + + + + + + + + + + + +
+ +
+ + +
+ + +

编辑App:

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ > + +
+
+ > + +
+ +
+ > + +
+
+
+ > + +
+
+ > + +
+
+
+ > + +
+ +
+ > + +
+
+
+ > + +
+
+ > + +
+
+ > + +
+
+
+
+ + +
仅在上传新安装包时填写
+
+
+ + +
+
+ + +
+
+ + +
+ + 取消 +
+
+ + + + + + + + prepare("UPDATE apps SET name=?, description=?, age_rating=?, age_rating_description=?, platforms=?, updated_at=NOW() WHERE id=?"); +$stmt->bind_param("sssssi", $name, $description, $age_rating, $_POST['age_rating_description'], $platformsJson, $appId); + +// ... existing code ... +?> \ No newline at end of file diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..1abefe3 --- /dev/null +++ b/admin/index.php @@ -0,0 +1,123 @@ +query($sqlApps); + +if (!$resultApps) { + error_log("Database query failed: " . $conn->error); + echo '
获取App列表失败,请联系管理员。
'; +} else { +?> + + + + + + App管理 - <?php echo APP_STORE_NAME; ?> + + + + + + + + + + + +
+ +
+ + +
+ + +

App列表

+ + + + + + + + + + + + fetch_assoc()): ?> + + + + + + + + + +
ID名称年龄分级创建时间操作
+ 编辑 + 版本管理 + 删除 +
+
+ + + + + + + \ No newline at end of file diff --git a/admin/login.php b/admin/login.php new file mode 100644 index 0000000..a0d4589 --- /dev/null +++ b/admin/login.php @@ -0,0 +1,70 @@ +'; + echo ''; + echo ''; + echo ' '; + echo ' '; + echo ' 管理员登录 - '. APP_STORE_NAME . ''; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ' '; + echo ''; + echo ''; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + echo '
管理员登录
'; + echo '
'; + if (isset($error)) { + echo '
'. $error . '
'; + } + echo '
'; + echo '
'; + echo ' '; + echo ' '; + echo '
'; + echo '
'; + echo ' '; + echo ' '; + echo '
'; + echo ' '; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + echo '
'; + echo ' '; + echo ' '; + echo ''; + echo ''; + exit; +} \ No newline at end of file diff --git a/admin/manage_versions.php b/admin/manage_versions.php new file mode 100644 index 0000000..876a13b --- /dev/null +++ b/admin/manage_versions.php @@ -0,0 +1,362 @@ +prepare($getAppSql); +$stmt->bind_param("i", $appId); +$stmt->execute(); +$result = $stmt->get_result(); +if ($result->num_rows === 0) { + header('Location: index.php?error=App不存在'); + exit; +} +$app = $result->fetch_assoc(); + +// 获取所有版本 +$versions = []; +$getVersionsSql = "SELECT * FROM app_versions WHERE app_id = ? ORDER BY created_at DESC"; +$stmt = $conn->prepare($getVersionsSql); +$stmt->bind_param("i", $appId); +$stmt->execute(); +$result = $stmt->get_result(); +while ($row = $result->fetch_assoc()) { + $versions[] = $row; +} + +$success = ''; +$error = ''; + +// 处理添加版本 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_version'])) { + $version = $_POST['version']; + $changelog = $_POST['changelog']; + + if (empty($version)) { + $error = '版本号不能为空'; + } elseif (empty($_FILES['app_file']['name'])) { + $error = '请上传App文件'; + } else { + $uploadDir = '../files/'; + $fileName = basename($_FILES['app_file']['name']); + $targetPath = $uploadDir . $fileName; + + if (move_uploaded_file($_FILES['app_file']['tmp_name'], $targetPath)) { + $insertVersionSql = "INSERT INTO app_versions (app_id, version, changelog, file_path, created_at) VALUES (?, ?, ?, ?, NOW())"; + $stmt = $conn->prepare($insertVersionSql); + $stmt->bind_param("isss", $appId, $version, $changelog, $targetPath); + + if ($stmt->execute() === TRUE) { + header('Location: manage_versions.php?app_id=' . $appId . '&success=版本添加成功'); + exit; + } else { + $error = '版本添加失败: ' . $conn->error; + unlink($targetPath); // 删除已上传的文件 + } + } else { + $error = '文件上传失败'; + } + } +} + +// 处理删除版本 +if (isset($_GET['delete_id']) && is_numeric($_GET['delete_id'])) { + $versionId = $_GET['delete_id']; + + // 获取版本信息 + $getVersionSql = "SELECT file_path FROM app_versions WHERE id = ? AND app_id = ?"; + $stmt = $conn->prepare($getVersionSql); + $stmt->bind_param("ii", $versionId, $appId); + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows === 1) { + $version = $result->fetch_assoc(); + + // 删除文件 + if (file_exists($version['file_path'])) { + unlink($version['file_path']); + } + + // 删除数据库记录 + $deleteVersionSql = "DELETE FROM app_versions WHERE id = ? AND app_id = ?"; + $stmt = $conn->prepare($deleteVersionSql); + $stmt->bind_param("ii", $versionId, $appId); + + if ($stmt->execute() === TRUE) { + header('Location: manage_versions.php?app_id=' . $appId . '&success=版本删除成功'); + exit; + } else { + $error = '版本删除失败: ' . $conn->error; + } + } else { + $error = '版本不存在'; + } +} + +// 处理编辑版本 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_version'])) { + $versionId = $_POST['version_id']; + $version = $_POST['version']; + $changelog = $_POST['changelog']; + + if (empty($version)) { + $error = '版本号不能为空'; + } else { + // 检查是否上传了新文件 + $fileUpdate = ''; + $params = ['ss', $version, $changelog, $versionId, $appId]; + + if (!empty($_FILES['new_app_file']['name'])) { + $uploadDir = '../files/'; + $fileName = basename($_FILES['new_app_file']['name']); + $targetPath = $uploadDir . $fileName; + + if (move_uploaded_file($_FILES['new_app_file']['tmp_name'], $targetPath)) { + // 获取旧文件路径 + $getOldFileSql = "SELECT file_path FROM app_versions WHERE id = ? AND app_id = ?"; + $stmt = $conn->prepare($getOldFileSql); + $stmt->bind_param("ii", $versionId, $appId); + $stmt->execute(); + $result = $stmt->get_result(); + $oldVersion = $result->fetch_assoc(); + + // 删除旧文件 + if (file_exists($oldVersion['file_path'])) { + unlink($oldVersion['file_path']); + } + + $fileUpdate = ", file_path = ?"; + $params[0] = 'sss'; + $params[] = $targetPath; + } else { + $error = '文件上传失败'; + } + } + + if (empty($error)) { + $updateVersionSql = "UPDATE app_versions SET version = ?, changelog = ?" . $fileUpdate . " WHERE id = ? AND app_id = ?"; + $stmt = $conn->prepare($updateVersionSql); + + // 动态绑定参数 + $stmt->bind_param(...$params); + + if ($stmt->execute() === TRUE) { + header('Location: manage_versions.php?app_id=' . $appId . '&success=版本更新成功'); + exit; + } else { + $error = '版本更新失败: ' . $conn->error; + } + } + } +} + +// 获取URL参数中的成功/错误消息 +if (isset($_GET['success'])) { + $success = $_GET['success']; +} elseif (isset($_GET['error'])) { + $error = $_GET['error']; +} +?> + + + + + + 管理版本 - <?php echo htmlspecialchars($app['name']); ?> + + + + + + + + + + +
+
+
+

管理版本:

+

管理该应用的所有版本

+
+
+ +
+
+ + +
+ + +
+ + + +
+ 暂无版本记录 +
+ +
+ +
+
+
+
版本
+
发布日期:
+

+
+ +
+
+ + + + +
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/api.php b/api.php new file mode 100644 index 0000000..e9b2dc7 --- /dev/null +++ b/api.php @@ -0,0 +1,388 @@ +prepare($countSql); + if ($paramTypes && count($stmtParams) > 2) { + // 排除最后两个分页参数 + $countParams = array_slice($stmtParams, 0, -2); + $countTypes = substr($paramTypes, 0, -2); + call_user_func_array([$countStmt, 'bind_param'], array_merge([$countTypes], $countParams)); + } + $countStmt->execute(); + $countResult = $countStmt->get_result(); + $total = $countResult->fetch_assoc()['total'] ?? 0; + $totalPages = ceil($total / $limit); + + // 执行主查询 + $stmt = $conn->prepare($sql); + call_user_func_array([$stmt, 'bind_param'], array_merge([$paramTypes], $stmtParams)); + $stmt->execute(); + $result = $stmt->get_result(); + + $apps = []; + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $apps[] = $row; + } + } + + // 返回带分页元数据的响应 + echo json_encode([ + 'data' => $apps, + 'pagination' => [ + 'total' => $total, + 'page' => $page, + 'limit' => $limit, + 'totalPages' => $totalPages + ] + ]); + exit; + } + + // 处理应用详情请求 + elseif ($action === 'app' && isset($_GET['id']) && is_numeric($_GET['id']) && $requestMethod === 'GET') { + $appId = $_GET['id']; + error_log("Requesting app details for ID: $appId"); + + $sqlApp = "SELECT apps.id, apps.name, apps.description, apps.age_rating, apps.platform, apps.created_at, AVG(reviews.rating) as avg_rating + FROM apps + LEFT JOIN reviews ON apps.id = reviews.app_id + WHERE apps.id = ? + GROUP BY apps.id, apps.name, apps.description, apps.age_rating, apps.platform, apps.created_at"; + $stmt = $conn->prepare($sqlApp); + $stmt->bind_param("i", $appId); + $stmt->execute(); + $resultApp = $stmt->get_result(); + error_log("Executing prepared statement for app details"); + + if (!$resultApp) { + error_log("Database error: " . $conn->error); + http_response_code(500); + echo json_encode(['error' => 'Database query failed']); + exit; + } + + $app = $resultApp->fetch_assoc(); + error_log("App found: " . ($app ? "Yes" : "No")); + + if ($app) { + // 获取版本信息 + $sqlVersions = "SELECT * FROM app_versions WHERE app_id = $appId ORDER BY created_at DESC"; + $resultVersions = $conn->query($sqlVersions); + $versions = []; + while ($version = $resultVersions->fetch_assoc()) { + $versions[] = $version; + } + $app['versions'] = $versions; + + // 获取图片信息 + $sqlImages = "SELECT * FROM app_images WHERE app_id = $appId"; + $resultImages = $conn->query($sqlImages); + $images = []; + while ($image = $resultImages->fetch_assoc()) { + $images[] = $image; + } + $app['images'] = $images; + + // 获取评价信息 + $sqlReviews = "SELECT * FROM reviews WHERE app_id = $appId ORDER BY created_at DESC"; + $resultReviews = $conn->query($sqlReviews); + $reviews = []; + while ($review = $resultReviews->fetch_assoc()) { + $reviews[] = $review; + } + $app['reviews'] = $reviews; + + echo json_encode($app); + } else { + http_response_code(404); + echo json_encode(['error' => "App with ID $appId not found", 'sql' => $sqlApp]); + } + exit; + } + + // 处理用户收藏应用 + elseif ($action === 'favorite' && isset($_GET['app_id']) && is_numeric($_GET['app_id']) && isset($_GET['user_id']) && is_numeric($_GET['user_id']) && $requestMethod === 'POST') { + $appId = $_GET['app_id']; + $userId = $_GET['user_id']; + + $stmt = $conn->prepare("INSERT IGNORE INTO user_favorites (user_id, app_id) VALUES (?, ?)"); + $stmt->bind_param("ii", $userId, $appId); + + if ($stmt->execute()) { + echo json_encode(['status' => 'success', 'message' => 'App added to favorites']); + } else { + http_response_code(500); + echo json_encode(['error' => 'Failed to add to favorites']); + } + $stmt->close(); + exit; + } + + // 获取用户收藏列表 + elseif ($action === 'favorites' && isset($_GET['user_id']) && is_numeric($_GET['user_id']) && $requestMethod === 'GET') { + $userId = $_GET['user_id']; + + $sql = "SELECT apps.* FROM user_favorites JOIN apps ON user_favorites.app_id = apps.id WHERE user_favorites.user_id = $userId"; + $result = $conn->query($sql); + + $favorites = []; + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $favorites[] = $row; + } + } + + echo json_encode($favorites); + exit; + } + + // 获取应用推荐列表 + elseif ($action === 'recommendations' && $requestMethod === 'GET') { + $sql = "SELECT apps.*, app_recommendations.reason FROM app_recommendations JOIN apps ON app_recommendations.app_id = apps.id"; + $result = $conn->query($sql); + + $recommendations = []; + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $recommendations[] = $row; + } + } + + echo json_encode($recommendations); + exit; + } + + // 获取热门应用排行榜 + elseif ($action === 'hot_apps' && $requestMethod === 'GET') { + $sql = "SELECT apps.*, SUM(app_versions.download_count) as total_downloads FROM apps JOIN app_versions ON apps.id = app_versions.app_id GROUP BY apps.id ORDER BY total_downloads DESC LIMIT 10"; + $result = $conn->query($sql); + + $hotApps = []; + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $hotApps[] = $row; + } + } + + echo json_encode($hotApps); + exit; + } + + // 提交用户反馈 + elseif ($action === 'feedback' && isset($_GET['user_id']) && is_numeric($_GET['user_id']) && $requestMethod === 'POST') { + $userId = $_GET['user_id']; + $appId = isset($_GET['app_id']) && is_numeric($_GET['app_id']) ? $_GET['app_id'] : null; + $content = $_POST['content'] ?? ''; + + if (empty($content)) { + http_response_code(400); + echo json_encode(['error' => 'Feedback content is required']); + exit; + } + + $stmt = $conn->prepare("INSERT INTO user_feedback (user_id, app_id, content) VALUES (?, ?, ?)"); + $stmt->bind_param("iis", $userId, $appId, $content); + + if ($stmt->execute()) { + echo json_encode(['status' => 'success', 'message' => 'Feedback submitted successfully']); + } else { + http_response_code(500); + echo json_encode(['error' => 'Failed to submit feedback']); + } + $stmt->close(); + exit; + } + + // 处理下载请求 + elseif ($action === 'download' && isset($_GET['version_id']) && is_numeric($_GET['version_id']) && $requestMethod === 'GET') { + $versionId = $_GET['version_id']; + + $stmt = $conn->prepare("SELECT * FROM app_versions WHERE id = ?"); + $stmt->bind_param("i", $versionId); + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows > 0) { + $version = $result->fetch_assoc(); + // 更新下载计数 + $updateStmt = $conn->prepare("UPDATE app_versions SET download_count = download_count + 1 WHERE id = ?"); + $updateStmt->bind_param("i", $versionId); + $updateStmt->execute(); + $updateStmt->close(); + $filePath = $version['file_path']; + + if (file_exists($filePath)) { + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename="' . basename($filePath) . '"'); + header('Content-Length: ' . filesize($filePath)); + readfile($filePath); + exit; + } else { + http_response_code(404); + echo json_encode(['error' => 'File not found']); + } + } else { + http_response_code(404); + echo json_encode(['error' => 'Version not found']); + } + $stmt->close(); + exit; + } + + // 无效操作 + else { + http_response_code(400); + echo json_encode(['error' => 'Invalid action or parameters']); + exit; + } +} + +// 保留原路径路由逻辑作为兼容 fallback +$requestUri = $_SERVER['REQUEST_URI']; +$path = parse_url($requestUri, PHP_URL_PATH); +$path = preg_replace('/\.php$/', '', $path); +$pathParts = explode('/', trim($path, '/')); +error_log("Path parts: " . print_r($pathParts, true)); + +if ($pathParts[0] === 'api') { + error_log("Processing API path request: " . print_r($pathParts, true)); + // 处理应用详情请求 /api/app/ + if (count($pathParts) >= 3 && $pathParts[1] === 'app' && is_numeric($pathParts[2])) { + $appId = $pathParts[2]; + error_log("Path-based app details request for ID: $appId"); + + $sqlApp = "SELECT apps.id, apps.name, apps.description, apps.age_rating, apps.platform, apps.created_at, AVG(reviews.rating) as avg_rating + FROM apps + LEFT JOIN reviews ON apps.id = reviews.app_id + WHERE apps.id = ? + GROUP BY apps.id, apps.name, apps.description, apps.age_rating, apps.platform, apps.created_at"; + $stmt = $conn->prepare($sqlApp); + $stmt->bind_param("i", $appId); + $stmt->execute(); + $resultApp = $stmt->get_result(); + error_log("Executing prepared statement for path-based app details"); + + if (!$resultApp) { + error_log("Database error: " . $conn->error); + http_response_code(500); + echo json_encode(['error' => 'Database query failed']); + exit; + } + + $app = $resultApp->fetch_assoc(); + error_log("App found via path: " . ($app ? "Yes" : "No")); + + if ($app) { + // 获取版本信息 + $sqlVersions = "SELECT * FROM app_versions WHERE app_id = $appId ORDER BY created_at DESC"; + $resultVersions = $conn->query($sqlVersions); + $versions = []; + while ($version = $resultVersions->fetch_assoc()) { + $versions[] = $version; + } + $app['versions'] = $versions; + + // 获取图片信息 + $sqlImages = "SELECT * FROM app_images WHERE app_id = $appId"; + $resultImages = $conn->query($sqlImages); + $images = []; + while ($image = $resultImages->fetch_assoc()) { + $images[] = $image; + } + $app['images'] = $images; + + // 获取评价信息 + $sqlReviews = "SELECT * FROM reviews WHERE app_id = $appId ORDER BY created_at DESC"; + $resultReviews = $conn->query($sqlReviews); + $reviews = []; + while ($review = $resultReviews->fetch_assoc()) { + $reviews[] = $review; + } + $app['reviews'] = $reviews; + + echo json_encode($app); + } else { + http_response_code(404); + echo json_encode([ + 'error' => 'Not found', + 'path' => $path, + 'path_parts' => $pathParts, + 'action' => isset($_GET['action']) ? $_GET['action'] : null + ]); + } + exit; + } + // 处理其他API路径请求... +} + +http_response_code(404); +echo json_encode([ + 'error' => 'Not found', + 'path' => $path, + 'path_parts' => $pathParts, + 'action' => isset($_GET['action']) ? $_GET['action'] : null +]); +?> \ No newline at end of file diff --git a/app.php b/app.php new file mode 100644 index 0000000..89713b2 --- /dev/null +++ b/app.php @@ -0,0 +1,193 @@ +query($sqlApp); +$app = $resultApp->fetch_assoc(); + +if (!$app) { + header('Location: index.php'); + exit; +} + +// 获取App版本信息 +$sqlVersions = "SELECT * FROM app_versions WHERE app_id = $appId ORDER BY created_at DESC"; +$resultVersions = $conn->query($sqlVersions); + +// 获取App预览图片 +$sqlImages = "SELECT * FROM app_images WHERE app_id = $appId"; +$resultImages = $conn->query($sqlImages); + +// 获取评价信息 +$sqlReviews = "SELECT * FROM reviews WHERE app_id = $appId ORDER BY created_at DESC"; +$resultReviews = $conn->query($sqlReviews); + +// 处理评价提交 +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['rating'])) { + $rating = $_POST['rating']; + $ipAddress = $_SERVER['REMOTE_ADDR']; + + $insertSql = "INSERT INTO reviews (app_id, ip_address, rating) VALUES ($appId, '$ipAddress', $rating)"; + if ($conn->query($insertSql) === TRUE) { + header('Location: app.php?id=$appId'); + exit; + } +} +?> + + + + + + <?php echo $app['name']; ?> - <?php echo APP_STORE_NAME; ?> + + + + + + + + + + + +
+
+
+

+

+

年龄分级:

+ +
+

年龄分级说明

+

+
+ +

适用平台:

+

评分: /5

+
+
+ +
+
+ +
+
+

版本历史

+ fetch_assoc()): ?> +
+ +
+ +
+
+ +
+
+

评价

+ fetch_assoc()): ?> +
+
+

评分: /5

+

评价时间:

+
+
+ +
+
+

提交评价

+
+
+ + +
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/app_store.sql b/app_store.sql new file mode 100644 index 0000000..aa71e41 --- /dev/null +++ b/app_store.sql @@ -0,0 +1,191 @@ +-- 创建数据库 +-- CREATE DATABASE IF NOT EXISTS app_store; +USE awa; + +-- 创建APP表 +CREATE TABLE IF NOT EXISTS apps ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT NOT NULL, + age_rating ENUM('3+', '7+', '12+', '17+') NOT NULL, + age_rating_description TEXT, + platforms JSON NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建APP版本表 +CREATE TABLE IF NOT EXISTS app_versions ( + id INT AUTO_INCREMENT PRIMARY KEY, + app_id INT NOT NULL, + version VARCHAR(50) NOT NULL, + changelog TEXT NOT NULL, + file_path VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE +); + +-- 创建APP预览图片表 +CREATE TABLE IF NOT EXISTS app_images ( + id INT AUTO_INCREMENT PRIMARY KEY, + app_id INT NOT NULL, + image_path VARCHAR(255) NOT NULL, + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE +); + +-- 创建评价表 +CREATE TABLE IF NOT EXISTS reviews ( + id INT AUTO_INCREMENT PRIMARY KEY, + app_id INT NOT NULL, + ip_address VARCHAR(45) NOT NULL, + rating TINYINT NOT NULL CHECK (rating BETWEEN 1 AND 5), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY unique_review (app_id, ip_address), + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE +); + +-- 创建管理员表 +CREATE TABLE IF NOT EXISTS admins ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL +); + +-- 插入默认管理员 +INSERT IGNORE INTO admins (username, password) VALUES ("admin", "your_admin_password_hash"); + +-- 创建用户表 +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_login TIMESTAMP NULL +); + +-- 创建应用分类表 +CREATE TABLE IF NOT EXISTS categories ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + description TEXT +); + +-- 应用与分类的多对多关系表 +CREATE TABLE IF NOT EXISTS app_categories ( + app_id INT NOT NULL, + category_id INT NOT NULL, + PRIMARY KEY (app_id, category_id), + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE, + FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE +); + +-- 修改评价表,支持文字评论并关联用户 +SET @exist_ip_address = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'awa' AND TABLE_NAME = 'reviews' AND COLUMN_NAME = 'ip_address'); +SET @sql = IF(@exist_ip_address > 0, 'ALTER TABLE reviews DROP COLUMN ip_address', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 检查并删除unique_review索引(如果存在) +SET @exist_unique_review = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'awa' AND TABLE_NAME = 'reviews' AND INDEX_NAME = 'unique_review'); +SET @sql = IF(@exist_unique_review > 0, 'ALTER TABLE reviews DROP INDEX unique_review', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 检查并添加user_id列(如果不存在) +SET @exist_user_id = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'awa' AND TABLE_NAME = 'reviews' AND COLUMN_NAME = 'user_id'); +SET @sql = IF(@exist_user_id = 0, 'ALTER TABLE reviews ADD COLUMN user_id INT', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 检查并添加comment列(如果不存在) +SET @exist_comment = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'awa' AND TABLE_NAME = 'reviews' AND COLUMN_NAME = 'comment'); +SET @sql = IF(@exist_comment = 0, 'ALTER TABLE reviews ADD COLUMN comment TEXT', '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 = 'awa' AND TABLE_NAME = 'reviews' AND COLUMN_NAME = 'user_id' AND CONSTRAINT_NAME = 'fk_reviews_users'); +SET @sql = IF(@exist_fk = 0, 'ALTER TABLE reviews ADD CONSTRAINT fk_reviews_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +-- 检查并添加唯一索引(如果不存在) +SET @exist_unique_index = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'awa' AND TABLE_NAME = 'reviews' AND INDEX_NAME = 'unique_user_app_review'); +SET @sql = IF(@exist_unique_index = 0, 'ALTER TABLE reviews ADD UNIQUE KEY unique_user_app_review (user_id, app_id)', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 添加应用下载统计 +SET @exist_download_count = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'awa' AND TABLE_NAME = 'app_versions' AND COLUMN_NAME = 'download_count'); +SET @sql = IF(@exist_download_count = 0, 'ALTER TABLE app_versions ADD COLUMN download_count INT DEFAULT 0', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 创建下载历史表 +CREATE TABLE IF NOT EXISTS download_history ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT, + version_id INT NOT NULL, + download_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (version_id) REFERENCES app_versions(id) ON DELETE CASCADE +); + +-- 创建用户收藏表 +CREATE TABLE IF NOT EXISTS user_favorites ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + app_id INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY unique_favorite (user_id, app_id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE +); + +-- 创建应用推荐表 +CREATE TABLE IF NOT EXISTS app_recommendations ( + id INT AUTO_INCREMENT PRIMARY KEY, + app_id INT NOT NULL, + reason TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE +); + +-- 创建应用更新通知表 +CREATE TABLE IF NOT EXISTS app_update_notifications ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + app_id INT NOT NULL, + version_id INT NOT NULL, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE, + FOREIGN KEY (version_id) REFERENCES app_versions(id) ON DELETE CASCADE +); + +-- 创建用户反馈表 +CREATE TABLE IF NOT EXISTS user_feedback ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + app_id INT, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE SET NULL +); + +-- 修改app_versions表,添加最后更新时间戳用于热门排行 +SET @exist_last_updated = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'app_versions' AND COLUMN_NAME = 'last_updated'); +SET @sql = IF(@exist_last_updated = 0, 'ALTER TABLE app_versions ADD COLUMN last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + diff --git a/config.php b/config.php new file mode 100644 index 0000000..7b08d1f --- /dev/null +++ b/config.php @@ -0,0 +1,24 @@ +connect_error) { + die('数据库连接失败: ' . $conn->connect_error); +} +$conn->set_charset('utf8mb4'); + +// 设置时区 +date_default_timezone_set('Asia/Shanghai'); +?> \ No newline at end of file diff --git a/download.php b/download.php new file mode 100644 index 0000000..15abb51 --- /dev/null +++ b/download.php @@ -0,0 +1,54 @@ +prepare($getVersionSql); +$stmt->bind_param("i", $versionId); +$stmt->execute(); +$result = $stmt->get_result(); +if ($result->num_rows !== 1) { + http_response_code(404); + exit('版本不存在'); +} +$version = $result->fetch_assoc(); + +// 获取绝对文件路径 +$filePath = realpath(__DIR__ . '/' . $version['file_path']); + +// 验证文件存在性 +if (!$filePath || !file_exists($filePath)) { + http_response_code(404); + exit('文件不存在'); +} + +// 设置下载响应头 +$fileName = basename($filePath); +$fileSize = filesize($filePath); + +// 清除输出缓冲区并发送 headers +ob_end_clean(); +header('Content-Type: application/octet-stream'); +header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($fileName)); +header('Content-Length: ' . $fileSize); +header('Cache-Control: no-cache, must-revalidate'); +header('Expires: 0'); +header('Pragma: public'); + +// 输出文件内容 +if (!readfile($filePath)) { + http_response_code(500); + exit('无法读取文件'); +} + +exit; +?> \ No newline at end of file diff --git a/files/.gitkeep b/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/files/LeonChat_1.2.apk b/files/LeonChat_1.2.apk new file mode 100644 index 0000000..1af1eae Binary files /dev/null and b/files/LeonChat_1.2.apk differ diff --git a/images/age_12plus.svg b/images/age_12plus.svg new file mode 100644 index 0000000..2a36790 --- /dev/null +++ b/images/age_12plus.svg @@ -0,0 +1,4 @@ + + + 12+ + \ No newline at end of file diff --git a/images/age_17plus.svg b/images/age_17plus.svg new file mode 100644 index 0000000..d5c56cd --- /dev/null +++ b/images/age_17plus.svg @@ -0,0 +1,4 @@ + + + 17+ + \ No newline at end of file diff --git a/images/age_3plus.svg b/images/age_3plus.svg new file mode 100644 index 0000000..00573d3 --- /dev/null +++ b/images/age_3plus.svg @@ -0,0 +1,4 @@ + + + 3+ + \ No newline at end of file diff --git a/images/age_7plus.svg b/images/age_7plus.svg new file mode 100644 index 0000000..e2f9e42 --- /dev/null +++ b/images/age_7plus.svg @@ -0,0 +1,4 @@ + + + 7+ + \ No newline at end of file diff --git a/images/下载.png b/images/下载.png new file mode 100644 index 0000000..da0a694 Binary files /dev/null and b/images/下载.png differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..5687c8a --- /dev/null +++ b/index.php @@ -0,0 +1,113 @@ + + + + + + + <?php echo APP_STORE_NAME; ?> + + + + + + + + + + + +
+
+
+ + +
+
+

最新应用

+
+ + prepare($sql); + $searchTerm = "%$search%"; + $stmt->bind_param("ss", $searchTerm, $searchTerm); + $stmt->execute(); + $result = $stmt->get_result(); + } else { + $result = $conn->query($sql); + } + + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + echo '
'; + echo '
'; + echo ''. $row['name'] . ''; + echo '
'; + echo '
'. $row['name'] . '
'; + echo '

'. substr($row['description'], 0, 100) . '...

'; + echo '

评分: '. round($row['avg_rating'], 1) . '/5

'; + echo '查看详情'; + echo '
'; + echo '
'; + echo '
'; + } + } + ?> +
+
+ + + + + + \ No newline at end of file diff --git a/router.php b/router.php new file mode 100644 index 0000000..44ed783 --- /dev/null +++ b/router.php @@ -0,0 +1,10 @@ +prepare($getAppSql); +$stmt->bind_param("i", $appId); +$stmt->execute(); +$result = $stmt->get_result(); +if ($result->num_rows === 0) { + header('Location: index.php?error=App不存在'); + exit; +} +$app = $result->fetch_assoc(); + +// 获取所有版本 +$versions = []; +$getVersionsSql = "SELECT * FROM app_versions WHERE app_id = ? ORDER BY created_at DESC"; +$stmt = $conn->prepare($getVersionsSql); +$stmt->bind_param("i", $appId); +$stmt->execute(); +$result = $stmt->get_result(); +while ($row = $result->fetch_assoc()) { + $versions[] = $row; +} +?> + + + + + + <?php echo htmlspecialchars($app['name']); ?> - 版本历史 + + + + + + + + + + +
+
+
+

- 版本历史

+

查看和下载该应用的所有历史版本

+
+
+ + +
+ 暂无版本记录 +
+ +
+ +
+
+
+
版本
+
发布日期:
+

+
+ +
+
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/windows/console_app.py b/windows/console_app.py new file mode 100644 index 0000000..7ec5078 --- /dev/null +++ b/windows/console_app.py @@ -0,0 +1,108 @@ +import requests +import json + +def fetch_apps(search_term=None): + url = 'http://localhost:3232/api.php?action=list' + if search_term: + url += f'&search={search_term}' + try: + response = requests.get(url) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print(f"获取应用列表失败: {e}") + return [] + +def get_app_details(app_id): + url = f'http://localhost:3232/api.php?action=app&id={app_id}' + try: + response = requests.get(url) + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError as e: + if response.status_code == 404: + try: + error_data = response.json() + print(f"获取应用详情失败: {error_data.get('error')}") + print(f"执行的SQL: {error_data.get('sql')}") + except ValueError: + print(f"获取应用详情失败: {e}") + else: + print(f"获取应用详情失败: {e}") + return None + except requests.exceptions.RequestException as e: + print(f"获取应用详情失败: {e}") + return None + +def download_app(version_id): + url = f'http://localhost:3232/api.php?action=download&version_id={version_id}' + try: + response = requests.get(f'http://localhost:3232/api/download/{version_id}', stream=True) + response.raise_for_status() + + filename = response.headers.get('Content-Disposition', '').split('filename=')[-1].strip('"') + if not filename: + filename = f'app_version_{version_id}.apk' + + with open(filename, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + print(f'下载成功: {filename}') + return True + except requests.exceptions.RequestException as e: + print(f'下载失败: {e}') + return False + +def display_apps(apps): + if not apps: + print('没有找到应用程序') + return + + print('=== 应用商店 ===') + for i, app in enumerate(apps, 1): + print(f'[{i}] {app.get("name")}') + print(f' 描述: {app.get("description", "无")}') + print(f' 评分: {app.get("avg_rating", "暂无")}/5') + print(f' 适用平台: {", ".join(app.get("platforms", []))}') + +if __name__ == "__main__": + while True: + print("\n=== 应用商店控制台 ===") + print("1. 浏览所有应用") + print("2. 搜索应用") + print("3. 查看应用详情") + print("4. 下载应用") + print("5. 退出") + + choice = input("请选择操作 (1-5): ") + + if choice == "1": + apps = fetch_apps() + display_apps(apps) + elif choice == "2": + search_term = input("请输入搜索关键词: ") + apps = fetch_apps(search_term) + display_apps(apps) + elif choice == "3": + app_id = input("请输入应用ID: ") + app = get_app_details(app_id) + if app: + print("\n=== 应用详情 ===") + print(f"名称: {app.get('name')}") + print(f"描述: {app.get('description', '无')}") + print(f"评分: {app.get('avg_rating', '暂无')}/5") + print(f"适用平台: {', '.join(app.get('platforms', []))}") + print("版本:") + for version in app.get('versions', []): + print(f" - {version.get('version_name')} (ID: {version.get('id')})") + else: + print("应用不存在或获取失败") + elif choice == "4": + version_id = input("请输入版本ID: ") + download_app(version_id) + elif choice == "5": + print("谢谢使用,再见!") + break + else: + print("无效的选择,请重试") \ No newline at end of file diff --git a/windows/gui_app.py b/windows/gui_app.py new file mode 100644 index 0000000..71145a2 --- /dev/null +++ b/windows/gui_app.py @@ -0,0 +1,209 @@ +import tkinter as tk +from tkinter import ttk, messagebox, filedialog +import requests +import json +import threading + +class AppStoreGUI: + def __init__(self, root): + self.root = root + self.root.title("应用商店") + self.root.geometry("800x600") + self.root.resizable(True, True) + + # 创建搜索框架 + self.search_frame = ttk.Frame(root) + self.search_frame.pack(fill=tk.X, padx=10, pady=5) + + ttk.Label(self.search_frame, text="搜索应用: ").pack(side=tk.LEFT, padx=5) + self.search_entry = ttk.Entry(self.search_frame, width=50) + self.search_entry.pack(side=tk.LEFT, padx=5) + ttk.Button(self.search_frame, text="搜索", command=self.on_search).pack(side=tk.LEFT, padx=5) + + # 创建主框架 + self.main_frame = ttk.Frame(root) + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) + + # 创建滚动条 + self.canvas = tk.Canvas(self.main_frame) + self.scrollbar = ttk.Scrollbar(self.main_frame, orient="vertical", command=self.canvas.yview) + self.scrollable_frame = ttk.Frame(self.canvas) + + self.scrollable_frame.bind( + "", + lambda e: self.canvas.configure( + scrollregion=self.canvas.bbox("all") + ) + ) + + self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") + self.canvas.configure(yscrollcommand=self.scrollbar.set) + + self.canvas.pack(side="left", fill="both", expand=True) + self.scrollbar.pack(side="right", fill="y") + + # 加载应用列表 + self.load_apps() + + def fetch_apps(self, search_term=None): + url = 'http://localhost:3232/api.php?action=list' + if search_term: + url += f'&search={search_term}' + try: + response = requests.get(url) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + messagebox.showerror('错误', f'获取应用列表失败: {str(e)}') + return [] + + def fetch_app_details(self, app_id): + url = f'http://localhost:3232/api.php?action=app&id={app_id}' + try: + response = requests.get(url) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + messagebox.showerror('错误', f'获取应用详情失败: {str(e)}') + return None + + def download_version(self, version_id): + url = f'http://localhost:3232/api.php?action=download&version_id={version_id}' + try: + response = requests.get(f'http://localhost:3232/api/app/{app_id}') + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + messagebox.showerror("错误", f"获取应用详情失败: {str(e)}") + return None + + def on_search(self): + # 清空现有应用卡片 + for widget in self.scrollable_frame.winfo_children(): + widget.destroy() + # 加载搜索结果 + search_term = self.search_entry.get().strip() + self.load_apps(search_term) + + def load_apps(self, search_term=None): + apps = self.fetch_apps(search_term) + if not apps: + ttk.Label(self.scrollable_frame, text="没有找到应用程序").pack(pady=20) + return + + # 创建应用卡片网格 + for i, app in enumerate(apps): + frame = ttk.LabelFrame(self.scrollable_frame, text=app.get("name")) + frame.grid(row=i//2, column=i%2, padx=10, pady=10, sticky="nsew") + + ttk.Label(frame, text=f"描述: {app.get('description', '无')}").pack(anchor="w", padx=5, pady=2) + ttk.Label(frame, text=f"评分: {app.get('avg_rating', '暂无')}/5").pack(anchor="w", padx=5, pady=2) + ttk.Label(frame, text=f"适用平台: {','.join(app.get('platforms', []))}").pack(anchor="w", padx=5, pady=2) + ttk.Button(frame, text="查看详情", command=lambda a=app: self.show_details(a)).pack(pady=5) + + # 配置网格权重使卡片自适应 + self.scrollable_frame.grid_columnconfigure(0, weight=1) + self.scrollable_frame.grid_columnconfigure(1, weight=1) + + def show_details(self, app): + # 获取完整应用详情 + app_details = self.fetch_app_details(app['id']) + if not app_details: + return + + detail_window = tk.Toplevel(self.root) + detail_window.title(app_details.get("name")) + detail_window.geometry("600x400") + + # 创建滚动区域 + canvas = tk.Canvas(detail_window) + scrollbar = ttk.Scrollbar(detail_window, orient="vertical", command=canvas.yview) + scrollable_frame = ttk.Frame(canvas) + + scrollable_frame.bind( + "", + lambda e: canvas.configure( + scrollregion=canvas.bbox("all") + ) + ) + + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + # 应用基本信息 + ttk.Label(scrollable_frame, text=f"名称: {app_details.get('name')}", font=('Arial', 12, 'bold')).pack(anchor="w", padx=10, pady=5) + ttk.Label(scrollable_frame, text=f"描述: {app_details.get('description', '无')}").pack(anchor="w", padx=10, pady=5) + ttk.Label(scrollable_frame, text=f"评分: {app_details.get('avg_rating', '暂无')}/5").pack(anchor="w", padx=10, pady=5) + ttk.Label(scrollable_frame, text=f"适用年龄: {app_details.get('age_rating', '未知')}").pack(anchor="w", padx=10, pady=5) + ttk.Label(scrollable_frame, text=f"适用平台: {','.join(app_details.get('platforms', []))}").pack(anchor="w", padx=10, pady=5) + + # 版本信息 + versions = app_details.get('versions', []) + if versions: + ttk.Label(scrollable_frame, text="\n=== 版本列表 ===", font=('Arial', 10, 'bold')).pack(anchor="w", padx=10, pady=10) + for version in versions: + version_frame = ttk.Frame(scrollable_frame) + version_frame.pack(anchor="w", padx=15, pady=5, fill=tk.X) + + ttk.Label(version_frame, text=f"版本 {version.get('version_name', '未知')}", font=('Arial', 9, 'bold')).pack(anchor="w") + ttk.Label(version_frame, text=f"发布日期: {version.get('created_at', '未知')}").pack(anchor="w") + ttk.Label(version_frame, text=f"文件大小: {version.get('file_size', '未知')}").pack(anchor="w") + ttk.Button(version_frame, text="下载", command=lambda v=version: self.download_version(v)).pack(anchor="e", pady=5) + + ttk.Button(scrollable_frame, text="关闭", command=detail_window.destroy).pack(pady=20) + + def download_version(self, version): + def download_thread(): + try: + # 询问保存路径 + save_path = filedialog.asksaveasfilename( + defaultextension=".apk", + filetypes=[("APK files", "*.apk"), ("All files", "*")], + initialfile=version.get('file_name', f"app_{version['id']}.apk") + ) + if not save_path: + return + + # 下载文件 + response = requests.get(f'http://localhost:3232/api/download/{version["id"]}', stream=True) + response.raise_for_status() + + total_size = int(response.headers.get('content-length', 0)) + block_size = 1024 # 1 KB + progress = 0 + + with open(save_path, 'wb') as file: + for data in response.iter_content(block_size): + progress += len(data) + file.write(data) + # 更新进度条 + progress_var.set((progress / total_size) * 100) + root.update_idletasks() + + messagebox.showinfo("成功", f"文件已下载至:\n{save_path}") + except requests.exceptions.RequestException as e: + messagebox.showerror("错误", f"下载失败: {str(e)}") + finally: + progress_window.destroy() + + # 创建进度窗口 + progress_window = tk.Toplevel(self.root) + progress_window.title(f"下载版本 {version.get('version_name', '未知')}") + progress_window.geometry("300x100") + progress_window.resizable(False, False) + + ttk.Label(progress_window, text="下载中...").pack(pady=10) + progress_var = tk.DoubleVar() + progress_bar = ttk.Progressbar(progress_window, variable=progress_var, maximum=100) + progress_bar.pack(fill=tk.X, padx=20, pady=10) + + # 启动下载线程 + threading.Thread(target=download_thread).start() + +if __name__ == "__main__": + root = tk.Tk() + app = AppStoreGUI(root) + root.mainloop() \ No newline at end of file diff --git a/windows/requirements.txt b/windows/requirements.txt new file mode 100644 index 0000000..95cbef7 --- /dev/null +++ b/windows/requirements.txt @@ -0,0 +1,2 @@ +requests==2.31.0 +pyinstaller==6.8.0 \ No newline at end of file