From c50d96490da5f004c82425bf79d3877e48c7169b Mon Sep 17 00:00:00 2001 From: ikmkj <1@qq,com> Date: Mon, 26 Jan 2026 09:48:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E6=B7=BB=E5=8A=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E7=AE=A1=E7=90=86=E3=80=81=E5=9B=9E=E6=94=B6=E7=AB=99?= =?UTF-8?q?=E5=92=8C=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 Grouping 模型和控制器中添加删除和更新方法 - 实现完整的图片上传、删除、预览和批量操作功能 - 添加图片清理功能用于删除冗余图片文件 - 实现 Markdown 文件按分组查询、搜索和标题更新功能 - 添加回收站功能支持软删除和恢复操作 - 实现系统设置和注册码生成功能 - 配置新的 API 路由包括图片、系统和回收站相关接口 - 更新前端开发服务器代理地址从 8084 到 80 端口 --- biji-php/config/routes.php | 47 +++- .../src/Controllers/GroupingController.php | 30 +++ .../Controllers/ImageCleanupController.php | 29 +++ biji-php/src/Controllers/ImageController.php | 212 ++++++++++++++++++ .../src/Controllers/MarkdownController.php | 55 +++++ biji-php/src/Controllers/SystemController.php | 58 +++++ biji-php/src/Controllers/TrashController.php | 80 +++++++ biji-php/src/Models/Grouping.php | 8 + biji-php/src/Models/Image.php | 59 +++++ biji-php/src/Models/ImageCleanup.php | 69 ++++++ biji-php/src/Models/MarkdownFile.php | 8 + biji-php/src/Models/Trash.php | 117 ++++++++++ biji-qianduan/vite.config.js | 2 +- 13 files changed, 772 insertions(+), 2 deletions(-) create mode 100644 biji-php/src/Controllers/ImageCleanupController.php create mode 100644 biji-php/src/Controllers/ImageController.php create mode 100644 biji-php/src/Controllers/SystemController.php create mode 100644 biji-php/src/Controllers/TrashController.php create mode 100644 biji-php/src/Models/Image.php create mode 100644 biji-php/src/Models/ImageCleanup.php create mode 100644 biji-php/src/Models/Trash.php diff --git a/biji-php/config/routes.php b/biji-php/config/routes.php index 64258f2..b5c5b51 100644 --- a/biji-php/config/routes.php +++ b/biji-php/config/routes.php @@ -4,6 +4,10 @@ use Slim\Routing\RouteCollectorProxy; use App\Controllers\UserController; use App\Controllers\MarkdownController; use App\Controllers\GroupingController; +use App\Controllers\ImageController; +use App\Controllers\SystemController; +use App\Controllers\TrashController; +use App\Controllers\ImageCleanupController; use App\Middleware\AuthMiddleware; // 根路径测试路由 @@ -37,15 +41,19 @@ $app->group('/api/user', function (RouteCollectorProxy $group) { $group->delete('/deleteUser', [UserController::class, 'deleteUser']); })->add(new AuthMiddleware()); -// Markdown 相关路由 +// Markdown 相关路由(无需认证 - 公开阅读) $app->group('/api/markdown', function (RouteCollectorProxy $group) { $group->get('/{id}', [MarkdownController::class, 'getById']); $group->get('', [MarkdownController::class, 'getAll']); + $group->get('/search', [MarkdownController::class, 'search']); + $group->get('/grouping/{groupingId}', [MarkdownController::class, 'getByGroupingId']); + $group->get('/recent', [MarkdownController::class, 'getRecent']); }); // Markdown 相关路由(需要认证) $app->group('/api/markdown', function (RouteCollectorProxy $group) { $group->post('/updateMarkdown', [MarkdownController::class, 'update']); + $group->post('/{id}/title', [MarkdownController::class, 'updateTitle']); $group->delete('/{id}', [MarkdownController::class, 'delete']); })->add(new AuthMiddleware()); @@ -60,3 +68,40 @@ $app->group('/api/groupings', function (RouteCollectorProxy $group) { $group->put('/{id}', [GroupingController::class, 'update']); $group->delete('/{id}', [GroupingController::class, 'delete']); })->add(new AuthMiddleware()); + +// 图片相关路由(无需认证 - 公开预览) +$app->group('/api/images', function (RouteCollectorProxy $group) { + $group->get('/preview/{url}', [ImageController::class, 'preview']); +}); + +// 图片相关路由(需要认证) +$app->group('/api/images', function (RouteCollectorProxy $group) { + $group->post('', [ImageController::class, 'upload']); + $group->post('/{id}', [ImageController::class, 'delete']); + $group->post('/deleteByUrl', [ImageController::class, 'deleteByUrl']); + $group->post('/batch', [ImageController::class, 'batchDelete']); +})->add(new AuthMiddleware()); + +// 系统设置相关路由(无需认证 - 公开查询注册状态) +$app->group('/api/system', function (RouteCollectorProxy $group) { + $group->get('/registration/status', [SystemController::class, 'getRegistrationStatus']); +}); + +// 系统设置相关路由(需要认证) +$app->group('/api/system', function (RouteCollectorProxy $group) { + $group->post('/registration/toggle', [SystemController::class, 'toggleRegistration']); + $group->post('/registration/generate-code', [SystemController::class, 'generateRegistrationCode']); +})->add(new AuthMiddleware()); + +// 回收站相关路由(需要认证) +$app->group('/api/trash', function (RouteCollectorProxy $group) { + $group->get('', [TrashController::class, 'getTrashItems']); + $group->post('/restore/{type}/{id}', [TrashController::class, 'restoreItem']); + $group->delete('/permanently/{type}/{id}', [TrashController::class, 'permanentlyDeleteItem']); + $group->delete('/clean', [TrashController::class, 'cleanTrash']); +})->add(new AuthMiddleware()); + +// 管理员相关路由(需要认证 - 图片清理) +$app->group('/api/admin', function (RouteCollectorProxy $group) { + $group->post('/cleanup-images', [ImageCleanupController::class, 'cleanupImages']); +})->add(new AuthMiddleware()); diff --git a/biji-php/src/Controllers/GroupingController.php b/biji-php/src/Controllers/GroupingController.php index a37b285..c8a29d3 100644 --- a/biji-php/src/Controllers/GroupingController.php +++ b/biji-php/src/Controllers/GroupingController.php @@ -39,4 +39,34 @@ class GroupingController $response->getBody()->write(json_encode(ApiResponse::success($created), JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); } + + public function update(Request $request, Response $response, $args) + { + $id = $args['id']; + $data = $request->getParsedBody(); + $grouping = $data['grouping'] ?? ''; + + if (empty($grouping)) { + $response->getBody()->write(json_encode(ApiResponse::fail('分组名称不能为空'), JSON_UNESCAPED_UNICODE)); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400); + } + + $model = new Grouping(); + $model->update($id, $grouping); + $updated = $model->findById($id); + + $response->getBody()->write(json_encode(ApiResponse::success($updated), JSON_UNESCAPED_UNICODE)); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + public function delete(Request $request, Response $response, $args) + { + $id = $args['id']; + + $model = new Grouping(); + $model->delete($id); + + $response->getBody()->write(json_encode(ApiResponse::success(null, '删除成功'), JSON_UNESCAPED_UNICODE)); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } } diff --git a/biji-php/src/Controllers/ImageCleanupController.php b/biji-php/src/Controllers/ImageCleanupController.php new file mode 100644 index 0000000..dfd9cb3 --- /dev/null +++ b/biji-php/src/Controllers/ImageCleanupController.php @@ -0,0 +1,29 @@ +cleanupRedundantImages(); + + $message = "成功清理 {$deletedCount} 个冗余图片"; + $response->getBody()->write(ApiResponse::json(ApiResponse::success($message))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('清理失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } +} diff --git a/biji-php/src/Controllers/ImageController.php b/biji-php/src/Controllers/ImageController.php new file mode 100644 index 0000000..ea3f0b6 --- /dev/null +++ b/biji-php/src/Controllers/ImageController.php @@ -0,0 +1,212 @@ +getUploadedFiles(); + $file = $uploadedFiles['file'] ?? null; + + if (!$file || $file->getError() !== UPLOAD_ERR_OK) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('文件上传失败'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400); + } + + $uploadDir = $_ENV['UPLOAD_DIR'] ?? '../uploads'; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + + $originalName = $file->getClientFilename(); + $extension = pathinfo($originalName, PATHINFO_EXTENSION); + $storedName = uniqid() . '.' . $extension; + $filePath = $uploadDir . '/' . $storedName; + + try { + $file->moveTo($filePath); + + $params = $request->getParsedBody(); + $markdownId = $params['markdownId'] ?? null; + + $model = new Image(); + $imageId = $model->create( + $originalName, + $storedName, + $storedName, + $file->getSize(), + $file->getClientMediaType(), + $markdownId + ); + + $image = $model->findById($imageId); + $response->getBody()->write(ApiResponse::json(ApiResponse::success($image))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('上传失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } + + /** + * 根据ID删除图片 + */ + public function delete(Request $request, Response $response, $args) + { + $id = $args['id']; + $uploadDir = $_ENV['UPLOAD_DIR'] ?? '../uploads'; + + try { + $model = new Image(); + $image = $model->findById($id); + + if (!$image) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('图片不存在'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(404); + } + + // 删除物理文件 + $filePath = $uploadDir . '/' . $image['stored_name']; + if (file_exists($filePath)) { + unlink($filePath); + } + + // 删除数据库记录 + $model->delete($id); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '删除成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('删除失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } + + /** + * 在线预览图片 + */ + public function preview(Request $request, Response $response, $args) + { + $url = $args['url']; + $uploadDir = $_ENV['UPLOAD_DIR'] ?? '../uploads'; + $filePath = $uploadDir . '/' . $url; + + if (empty($url) || !file_exists($filePath)) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('文件不存在'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(404); + } + + // 获取文件 MIME 类型 + $contentType = $this->getContentTypeFromFileExtension($url); + + // 读取文件内容 + $fileContent = file_get_contents($filePath); + $fileSize = filesize($filePath); + + $response->getBody()->write($fileContent); + return $response + ->withHeader('Content-Type', $contentType) + ->withHeader('Content-Length', (string)$fileSize); + } + + /** + * 根据URL删除图片 + */ + public function deleteByUrl(Request $request, Response $response) + { + $data = $request->getParsedBody(); + $url = $data['url'] ?? ''; + $uploadDir = $_ENV['UPLOAD_DIR'] ?? '../uploads'; + + if (empty($url)) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('URL不能为空'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400); + } + + try { + // 删除物理文件 + $filePath = $uploadDir . '/' . $url; + if (file_exists($filePath)) { + unlink($filePath); + } + + // 删除数据库记录 + $model = new Image(); + $model->deleteByUrl($url); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '删除成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('删除失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } + + /** + * 批量删除图片 + */ + public function batchDelete(Request $request, Response $response) + { + $data = $request->getParsedBody(); + $urls = $data['urls'] ?? []; + $uploadDir = $_ENV['UPLOAD_DIR'] ?? '../uploads'; + + if (empty($urls) || !is_array($urls)) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('URL列表不能为空'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400); + } + + try { + // 删除物理文件 + foreach ($urls as $url) { + $filePath = $uploadDir . '/' . $url; + if (file_exists($filePath)) { + unlink($filePath); + } + } + + // 删除数据库记录 + $model = new Image(); + $model->deleteByUrls($urls); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '批量删除成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('批量删除失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } + + /** + * 根据文件扩展名获取 MIME 类型 + */ + private function getContentTypeFromFileExtension($fileName) + { + if (empty($fileName) || strpos($fileName, '.') === false) { + return 'application/octet-stream'; + } + + $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + + $mimeTypes = [ + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'webp' => 'image/webp', + 'svg' => 'image/svg+xml' + ]; + + return $mimeTypes[$extension] ?? 'application/octet-stream'; + } +} diff --git a/biji-php/src/Controllers/MarkdownController.php b/biji-php/src/Controllers/MarkdownController.php index 92d1389..979b703 100644 --- a/biji-php/src/Controllers/MarkdownController.php +++ b/biji-php/src/Controllers/MarkdownController.php @@ -73,4 +73,59 @@ class MarkdownController $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '删除成功'))); return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); } + + public function getByGroupingId(Request $request, Response $response, $args) + { + $groupingId = $args['groupingId']; + $model = new MarkdownFile(); + $files = $model->getByGroupingId($groupingId); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($files))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + public function search(Request $request, Response $response) + { + $params = $request->getQueryParams(); + $keyword = $params['keyword'] ?? ''; + + $model = new MarkdownFile(); + $files = $model->searchByTitle($keyword); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($files))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + public function getRecent(Request $request, Response $response) + { + $model = new MarkdownFile(); + $files = $model->getRecent(12); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($files))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + public function updateTitle(Request $request, Response $response, $args) + { + $id = $args['id']; + $data = $request->getParsedBody(); + $title = $data['title'] ?? ''; + + if (empty($title)) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('标题不能为空'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400); + } + + $model = new MarkdownFile(); + $model->updateTitle($id, $title); + $file = $model->findById($id); + + if (!$file) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('文件未找到或更新失败'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(404); + } + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($file))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } } diff --git a/biji-php/src/Controllers/SystemController.php b/biji-php/src/Controllers/SystemController.php new file mode 100644 index 0000000..c823eb3 --- /dev/null +++ b/biji-php/src/Controllers/SystemController.php @@ -0,0 +1,58 @@ +isRegistrationEnabled(); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($enabled))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + /** + * 切换注册功能状态(需要认证) + */ + public function toggleRegistration(Request $request, Response $response) + { + $data = $request->getParsedBody(); + $enabled = $data['enabled'] ?? false; + + $model = new SystemSetting(); + $model->setRegistrationEnabled($enabled); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '设置成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + /** + * 生成注册码(需要认证) + */ + public function generateRegistrationCode(Request $request, Response $response) + { + $userId = $request->getAttribute('userId'); + + if (!$userId) { + $response->getBody()->write(ApiResponse::json(ApiResponse::fail('未授权'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(401); + } + + $model = new RegistrationCode(); + $code = $model->generateCode($userId); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($code))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } +} diff --git a/biji-php/src/Controllers/TrashController.php b/biji-php/src/Controllers/TrashController.php new file mode 100644 index 0000000..e0b0c37 --- /dev/null +++ b/biji-php/src/Controllers/TrashController.php @@ -0,0 +1,80 @@ +getTrashItems(); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success($items))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + /** + * 恢复项目 + */ + public function restoreItem(Request $request, Response $response, $args) + { + $type = $args['type']; + $id = $args['id']; + + try { + $model = new Trash(); + $model->restoreItem($id, $type); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '恢复成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('恢复失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } + + /** + * 永久删除项目 + */ + public function permanentlyDeleteItem(Request $request, Response $response, $args) + { + $type = $args['type']; + $id = $args['id']; + + try { + $model = new Trash(); + $model->permanentlyDeleteItem($id, $type); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '永久删除成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('删除失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } + + /** + * 清空回收站 + */ + public function cleanTrash(Request $request, Response $response) + { + try { + $model = new Trash(); + $model->cleanTrash(); + + $response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '清空成功'))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } catch (\Exception $e) { + $response->getBody()->write(ApiResponse::json(ApiResponse::error('清空失败: ' . $e->getMessage()))); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(500); + } + } +} diff --git a/biji-php/src/Models/Grouping.php b/biji-php/src/Models/Grouping.php index c3b36f4..80ab530 100644 --- a/biji-php/src/Models/Grouping.php +++ b/biji-php/src/Models/Grouping.php @@ -47,6 +47,14 @@ class Grouping return $stmt->execute([$grouping, $id]); } + public function delete($id) + { + $stmt = $this->db->prepare( + "UPDATE `grouping` SET is_deleted = 1, deleted_at = NOW() WHERE id = ?" + ); + return $stmt->execute([$id]); + } + public function softDelete($id, $userId) { $stmt = $this->db->prepare( diff --git a/biji-php/src/Models/Image.php b/biji-php/src/Models/Image.php new file mode 100644 index 0000000..ca7679f --- /dev/null +++ b/biji-php/src/Models/Image.php @@ -0,0 +1,59 @@ +db = Database::getInstance()->getConnection(); + } + + public function create($originalName, $storedName, $url, $size, $contentType, $markdownId = null) + { + $stmt = $this->db->prepare( + "INSERT INTO image (original_name, stored_name, url, size, content_type, markdown_id, created_at) + VALUES (?, ?, ?, ?, ?, ?, NOW())" + ); + $stmt->execute([$originalName, $storedName, $url, $size, $contentType, $markdownId]); + return $this->db->lastInsertId(); + } + + public function findById($id) + { + $stmt = $this->db->prepare("SELECT * FROM image WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch(); + } + + public function findByUrl($url) + { + $stmt = $this->db->prepare("SELECT * FROM image WHERE url = ?"); + $stmt->execute([$url]); + return $stmt->fetch(); + } + + public function delete($id) + { + $stmt = $this->db->prepare("DELETE FROM image WHERE id = ?"); + return $stmt->execute([$id]); + } + + public function deleteByUrl($url) + { + $stmt = $this->db->prepare("DELETE FROM image WHERE url = ?"); + return $stmt->execute([$url]); + } + + public function deleteByUrls($urls) + { + $placeholders = str_repeat('?,', count($urls) - 1) . '?'; + $stmt = $this->db->prepare("DELETE FROM image WHERE url IN ($placeholders)"); + return $stmt->execute($urls); + } +} diff --git a/biji-php/src/Models/ImageCleanup.php b/biji-php/src/Models/ImageCleanup.php new file mode 100644 index 0000000..e232d61 --- /dev/null +++ b/biji-php/src/Models/ImageCleanup.php @@ -0,0 +1,69 @@ +db = Database::getInstance()->getConnection(); + } + + /** + * 清理冗余图片 + * 删除数据库中存在但文件系统中不存在的图片记录 + * 以及文件系统中存在但数据库中没有记录的图片文件 + */ + public function cleanupRedundantImages() + { + $uploadDir = $_ENV['UPLOAD_DIR'] ?? '../uploads'; + $deletedCount = 0; + + // 1. 获取数据库中所有图片记录 + $stmt = $this->db->prepare("SELECT id, stored_name FROM image"); + $stmt->execute(); + $dbImages = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $dbImageFiles = []; + foreach ($dbImages as $image) { + $dbImageFiles[$image['stored_name']] = $image['id']; + } + + // 2. 获取文件系统中所有图片文件 + if (!is_dir($uploadDir)) { + return 0; + } + + $files = scandir($uploadDir); + $fsImages = array_filter($files, function($file) use ($uploadDir) { + return is_file($uploadDir . '/' . $file) && $file !== '.' && $file !== '..'; + }); + + // 3. 删除数据库中存在但文件系统中不存在的记录 + foreach ($dbImageFiles as $fileName => $imageId) { + $filePath = $uploadDir . '/' . $fileName; + if (!file_exists($filePath)) { + $stmt = $this->db->prepare("DELETE FROM image WHERE id = ?"); + $stmt->execute([$imageId]); + $deletedCount++; + } + } + + // 4. 删除文件系统中存在但数据库中没有记录的文件 + foreach ($fsImages as $file) { + if (!isset($dbImageFiles[$file])) { + $filePath = $uploadDir . '/' . $file; + if (file_exists($filePath)) { + unlink($filePath); + $deletedCount++; + } + } + } + + return $deletedCount; + } +} diff --git a/biji-php/src/Models/MarkdownFile.php b/biji-php/src/Models/MarkdownFile.php index 3cfcca6..056b819 100644 --- a/biji-php/src/Models/MarkdownFile.php +++ b/biji-php/src/Models/MarkdownFile.php @@ -98,6 +98,14 @@ class MarkdownFile return $stmt->execute($values); } + public function updateTitle($id, $title) + { + $stmt = $this->db->prepare( + "UPDATE markdown_file SET title = ?, updated_at = NOW() WHERE id = ?" + ); + return $stmt->execute([$title, $id]); + } + public function softDelete($id, $userId) { $stmt = $this->db->prepare( diff --git a/biji-php/src/Models/Trash.php b/biji-php/src/Models/Trash.php new file mode 100644 index 0000000..6520a38 --- /dev/null +++ b/biji-php/src/Models/Trash.php @@ -0,0 +1,117 @@ +db = Database::getInstance()->getConnection(); + } + + /** + * 获取回收站列表 + */ + public function getTrashItems() + { + $items = []; + + // 获取已删除的 Markdown 文件 + $stmt = $this->db->prepare(" + SELECT + id, + title, + 'markdown' as type, + deleted_at, + deleted_by + FROM markdown_file + WHERE is_deleted = 1 + ORDER BY deleted_at DESC + "); + $stmt->execute(); + $markdownItems = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + // 获取已删除的分组 + $stmt = $this->db->prepare(" + SELECT + id, + `grouping` as title, + 'grouping' as type, + deleted_at, + deleted_by + FROM `grouping` + WHERE is_deleted = 1 + ORDER BY deleted_at DESC + "); + $stmt->execute(); + $groupingItems = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + // 合并并按删除时间排序 + $items = array_merge($markdownItems, $groupingItems); + usort($items, function($a, $b) { + return strtotime($b['deleted_at']) - strtotime($a['deleted_at']); + }); + + return $items; + } + + /** + * 恢复项目 + */ + public function restoreItem($id, $type) + { + if ($type === 'markdown') { + $stmt = $this->db->prepare(" + UPDATE markdown_file + SET is_deleted = 0, deleted_at = NULL, deleted_by = NULL + WHERE id = ? + "); + } elseif ($type === 'grouping') { + $stmt = $this->db->prepare(" + UPDATE `grouping` + SET is_deleted = 0, deleted_at = NULL, deleted_by = NULL + WHERE id = ? + "); + } else { + throw new \Exception('不支持的类型'); + } + + return $stmt->execute([$id]); + } + + /** + * 永久删除项目 + */ + public function permanentlyDeleteItem($id, $type) + { + if ($type === 'markdown') { + $stmt = $this->db->prepare("DELETE FROM markdown_file WHERE id = ?"); + } elseif ($type === 'grouping') { + $stmt = $this->db->prepare("DELETE FROM `grouping` WHERE id = ?"); + } else { + throw new \Exception('不支持的类型'); + } + + return $stmt->execute([$id]); + } + + /** + * 清空回收站 + */ + public function cleanTrash() + { + // 删除所有已标记删除的 Markdown 文件 + $stmt = $this->db->prepare("DELETE FROM markdown_file WHERE is_deleted = 1"); + $stmt->execute(); + + // 删除所有已标记删除的分组 + $stmt = $this->db->prepare("DELETE FROM `grouping` WHERE is_deleted = 1"); + $stmt->execute(); + + return true; + } +} diff --git a/biji-qianduan/vite.config.js b/biji-qianduan/vite.config.js index 560a800..2311104 100644 --- a/biji-qianduan/vite.config.js +++ b/biji-qianduan/vite.config.js @@ -16,7 +16,7 @@ export default defineConfig(({ mode }) => { server: { proxy: { '/api': { - target: 'http://127.0.0.1:8084', + target: 'http://127.0.0.1:80', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') //去掉 api }