feat(api): 初始化笔记应用后端基础架构

- 添加环境配置文件 .env.example 包含数据库、JWT、CORS等配置
- 创建 .gitignore 文件忽略敏感文件和临时文件
- 配置 Apache 重写规则支持路由转发
- 实现 JWT 认证中间件提供用户身份验证功能
- 添加 MySQL 数据库初始化脚本包含分组、图片、笔记表结构
This commit is contained in:
ikmkj
2026-01-26 08:49:10 +08:00
parent 0426ad22b7
commit 90f63d9df1
31 changed files with 1898 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\Grouping;
use App\Utils\Response as ApiResponse;
class GroupingController
{
public function getAll(Request $request, Response $response)
{
$params = $request->getQueryParams();
$parentId = $params['parentId'] ?? null;
$model = new Grouping();
$groupings = $model->getAll($parentId);
$response->getBody()->write(json_encode(ApiResponse::success($groupings), JSON_UNESCAPED_UNICODE));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function create(Request $request, Response $response)
{
$data = $request->getParsedBody();
$grouping = $data['grouping'] ?? '';
$parentId = $data['parentId'] ?? 0;
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();
$id = $model->create($grouping, $parentId);
$created = $model->findById($id);
$response->getBody()->write(json_encode(ApiResponse::success($created), JSON_UNESCAPED_UNICODE));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\MarkdownFile;
use App\Utils\Response as ApiResponse;
class MarkdownController
{
public function getById(Request $request, Response $response, $args)
{
$id = $args['id'];
$model = new MarkdownFile();
$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);
}
// 检查是否为私密笔记
if ($file['is_private'] == 1) {
// 检查是否已认证
$userId = $request->getAttribute('userId');
if (!$userId) {
// 未认证,返回空内容
$response->getBody()->write(ApiResponse::json(ApiResponse::success('')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
}
$response->getBody()->write(ApiResponse::json(ApiResponse::success($file['content'])));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function getAll(Request $request, Response $response)
{
$model = new MarkdownFile();
$files = $model->getAll();
$response->getBody()->write(ApiResponse::json(ApiResponse::success($files)));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function update(Request $request, Response $response)
{
$data = $request->getParsedBody();
$id = $data['id'] ?? null;
if (!$id) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('缺少文件ID')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
$model = new MarkdownFile();
$model->update($id, $data);
$file = $model->findById($id);
$response->getBody()->write(ApiResponse::json(ApiResponse::success($file)));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function delete(Request $request, Response $response, $args)
{
$id = $args['id'];
$userId = $request->getAttribute('userId');
$model = new MarkdownFile();
$model->softDelete($id, $userId);
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '删除成功')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
}

View File

@@ -0,0 +1,137 @@
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Models\User;
use App\Models\SystemSetting;
use App\Models\RegistrationCode;
use App\Utils\JWTUtil;
use App\Utils\Response as ApiResponse;
class UserController
{
public function register(Request $request, Response $response)
{
$data = $request->getParsedBody();
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
$email = $data['email'] ?? '';
$registrationCode = $data['registrationCode'] ?? '';
// 验证输入
if (empty($username) || empty($password) || empty($email)) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名、密码和邮箱不能为空')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
// 检查注册功能是否开启
$systemSetting = new SystemSetting();
if (!$systemSetting->isRegistrationEnabled()) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('注册功能已关闭')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(403);
}
// 验证注册码
$regCodeModel = new RegistrationCode();
if (!$regCodeModel->validateCode($registrationCode)) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('无效或已过期的注册码')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
$userModel = new User();
// 检查用户名是否已存在
if ($userModel->findByUsername($username)) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名已存在')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
try {
$userId = $userModel->create($username, $password, $email);
$user = $userModel->findById($userId);
unset($user['password']); // 不返回密码
$response->getBody()->write(ApiResponse::json(ApiResponse::success($user, '注册成功')));
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 login(Request $request, Response $response)
{
$data = $request->getParsedBody();
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
if (empty($username) || empty($password)) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名和密码不能为空')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
$userModel = new User();
$user = $userModel->findByUsername($username);
if (!$user || !$userModel->verifyPassword($password, $user['password'])) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('用户名或密码错误')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(401);
}
// 生成 JWT token
$token = JWTUtil::encode($user['id'], $user['username']);
$expireTime = date('Y-m-d H:i:s', time() + ($_ENV['JWT_EXPIRE'] ?? 86400));
// 更新数据库中的 token
$userModel->updateToken($user['id'], $token, $expireTime);
$response->getBody()->write(ApiResponse::json(ApiResponse::success(['token' => $token], '登录成功')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function validateToken(Request $request, Response $response)
{
// 如果能到达这里,说明 token 已经通过中间件验证
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, 'Token is valid')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function updatePassword(Request $request, Response $response)
{
$userId = $request->getAttribute('userId');
$data = $request->getParsedBody();
$oldPassword = $data['oldPassword'] ?? '';
$newPassword = $data['newPassword'] ?? '';
if (empty($oldPassword) || empty($newPassword)) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('旧密码和新密码不能为空')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
$userModel = new User();
$user = $userModel->findById($userId);
if (!$userModel->verifyPassword($oldPassword, $user['password'])) {
$response->getBody()->write(ApiResponse::json(ApiResponse::fail('旧密码错误')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
}
$userModel->updatePassword($userId, $newPassword);
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '密码更新成功')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
public function deleteUser(Request $request, Response $response)
{
$userId = $request->getAttribute('userId');
$userModel = new User();
$userModel->delete($userId);
$response->getBody()->write(ApiResponse::json(ApiResponse::success(null, '用户删除成功')));
return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
}
}