feat(api): 添加图片管理、回收站和系统设置功能
- 在 Grouping 模型和控制器中添加删除和更新方法 - 实现完整的图片上传、删除、预览和批量操作功能 - 添加图片清理功能用于删除冗余图片文件 - 实现 Markdown 文件按分组查询、搜索和标题更新功能 - 添加回收站功能支持软删除和恢复操作 - 实现系统设置和注册码生成功能 - 配置新的 API 路由包括图片、系统和回收站相关接口 - 更新前端开发服务器代理地址从 8084 到 80 端口
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
29
biji-php/src/Controllers/ImageCleanupController.php
Normal file
29
biji-php/src/Controllers/ImageCleanupController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Models\ImageCleanup;
|
||||
use App\Utils\Response as ApiResponse;
|
||||
|
||||
class ImageCleanupController
|
||||
{
|
||||
/**
|
||||
* 手动触发清理冗余图片(需要管理员权限)
|
||||
*/
|
||||
public function cleanupImages(Request $request, Response $response)
|
||||
{
|
||||
try {
|
||||
$model = new ImageCleanup();
|
||||
$deletedCount = $model->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
212
biji-php/src/Controllers/ImageController.php
Normal file
212
biji-php/src/Controllers/ImageController.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Models\Image;
|
||||
use App\Utils\Response as ApiResponse;
|
||||
|
||||
class ImageController
|
||||
{
|
||||
/**
|
||||
* 上传图片
|
||||
*/
|
||||
public function upload(Request $request, Response $response)
|
||||
{
|
||||
$uploadedFiles = $request->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';
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
58
biji-php/src/Controllers/SystemController.php
Normal file
58
biji-php/src/Controllers/SystemController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Models\SystemSetting;
|
||||
use App\Models\RegistrationCode;
|
||||
use App\Utils\Response as ApiResponse;
|
||||
|
||||
class SystemController
|
||||
{
|
||||
/**
|
||||
* 获取注册功能状态
|
||||
*/
|
||||
public function getRegistrationStatus(Request $request, Response $response)
|
||||
{
|
||||
$model = new SystemSetting();
|
||||
$enabled = $model->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');
|
||||
}
|
||||
}
|
||||
80
biji-php/src/Controllers/TrashController.php
Normal file
80
biji-php/src/Controllers/TrashController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use App\Models\Trash;
|
||||
use App\Utils\Response as ApiResponse;
|
||||
|
||||
class TrashController
|
||||
{
|
||||
/**
|
||||
* 获取回收站列表
|
||||
*/
|
||||
public function getTrashItems(Request $request, Response $response)
|
||||
{
|
||||
$model = new Trash();
|
||||
$items = $model->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user