PHP实战:从零到一构建企业级应用(三)

简介: 教程来源:https://htnus.cn/category/artificial-intelligence.html 该文档详述了PHP用户管理系统的业务逻辑层与控制器实现:UserService封装注册、登录、头像上传、密码重置等核心功能,并集成日志、图片处理与异常处理;AuthService提供基于角色的权限校验;UserController统一处理API请求,配合路由分组与中间件(认证/权限)实现安全访问控制。

第五部分:业务逻辑层与服务
5.1 用户服务

// app/Services/UserService.php
<?php
namespace App\Services;

use App\Models\User;
use App\Validators\UserValidator;
use App\Exceptions\ValidationException;
use App\Exceptions\BusinessException;
use Intervention\Image\ImageManager;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class UserService
{
    private Logger $logger;
    private ImageManager $imageManager;

    public function __construct()
    {
        $this->logger = new Logger('user-service');
        $this->logger->pushHandler(new StreamHandler(STORAGE_PATH . '/logs/user.log', Logger::INFO));
        $this->imageManager = new ImageManager(['driver' => 'gd']);
    }

    /**
     * 注册新用户
     */
    public function register(array $data): array
    {
        // 验证数据
        $validator = new UserValidator($data);
        if (!$validator->validate()) {
            throw new ValidationException($validator->getErrors());
        }

        // 检查邮箱是否已存在
        if (User::where('email', '=', $data['email'])->first()) {
            throw new BusinessException('邮箱已被注册');
        }

        // 检查用户名是否已存在
        if (User::where('username', '=', $data['username'])->first()) {
            throw new BusinessException('用户名已被占用');
        }

        // 创建用户
        $user = User::create($data);

        if (!$user) {
            throw new BusinessException('注册失败,请稍后重试');
        }

        // 记录日志
        $this->logger->info('用户注册成功', [
            'user_id' => $user->id,
            'username' => $user->username,
            'email' => $user->email,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
        ]);

        // 发送欢迎邮件(异步)
        $this->sendWelcomeEmail($user);

        // 生成API Token
        $token = $user->generateApiToken();

        return [
            'user' => $user->toArray(),
            'token' => $token,
            'expires_in' => 86400  // 24小时
        ];
    }

    /**
     * 用户登录
     */
    public function login(string $login, string $password, string $ip): array
    {
        // 验证用户
        $user = User::authenticate($login, $password);

        if (!$user) {
            $this->logger->warning('登录失败', [
                'login' => $login,
                'ip' => $ip,
                'reason' => 'invalid_credentials'
            ]);
            throw new BusinessException('用户名或密码错误');
        }

        // 检查用户状态
        if ($user->status != 1) {
            throw new BusinessException('账户已被禁用,请联系管理员');
        }

        // 更新登录信息
        $user->updateLoginInfo($ip);

        // 记录日志
        $this->logger->info('用户登录成功', [
            'user_id' => $user->id,
            'username' => $user->username,
            'ip' => $ip
        ]);

        // 生成Token
        $token = $user->generateApiToken();

        return [
            'user' => $user->toArray(),
            'token' => $token,
            'expires_in' => 86400
        ];
    }

    /**
     * 获取用户列表(分页)
     */
    public function getUserList(array $filters, int $page = 1, int $perPage = 15): array
    {
        $query = User::where('deleted_at', 'IS', null);

        // 应用过滤条件
        if (!empty($filters['keyword'])) {
            $keyword = '%' . $filters['keyword'] . '%';
            $query->where(function($q) use ($keyword) {
                $q->where('username', 'LIKE', $keyword)
                  ->orWhere('email', 'LIKE', $keyword);
            });
        }

        if (!empty($filters['role_id'])) {
            $query->where('role_id', '=', $filters['role_id']);
        }

        if (isset($filters['status']) && $filters['status'] !== '') {
            $query->where('status', '=', (int) $filters['status']);
        }

        // 排序
        $orderBy = $filters['order_by'] ?? 'created_at';
        $orderDir = $filters['order_dir'] ?? 'DESC';
        $query->orderBy($orderBy, $orderDir);

        return $query->paginate($perPage, $page);
    }

    /**
     * 更新用户信息
     */
    public function updateUser(int $userId, array $data): User
    {
        $user = User::find($userId);

        if (!$user) {
            throw new BusinessException('用户不存在');
        }

        // 验证邮箱唯一性
        if (isset($data['email']) && $data['email'] !== $user->email) {
            $exists = User::where('email', '=', $data['email'])->first();
            if ($exists) {
                throw new BusinessException('邮箱已被其他用户使用');
            }
            $user->email = $data['email'];
        }

        // 验证用户名唯一性
        if (isset($data['username']) && $data['username'] !== $user->username) {
            $exists = User::where('username', '=', $data['username'])->first();
            if ($exists) {
                throw new BusinessException('用户名已被占用');
            }
            $user->username = $data['username'];
        }

        // 更新字段
        $updatableFields = ['phone', 'avatar', 'role_id', 'status'];
        foreach ($updatableFields as $field) {
            if (isset($data[$field])) {
                $user->$field = $data[$field];
            }
        }

        // 更新密码
        if (!empty($data['password'])) {
            $user->password = HashService::make($data['password']);
        }

        $user->save();

        $this->logger->info('用户信息已更新', [
            'user_id' => $userId,
            'updated_by' => $_SESSION['user_id'] ?? 'system'
        ]);

        return $user;
    }

    /**
     * 上传用户头像
     */
    public function uploadAvatar(int $userId, array $file): string
    {
        // 验证文件
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new BusinessException('文件上传失败');
        }

        $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
        if (!in_array($file['type'], $allowedTypes)) {
            throw new BusinessException('只支持JPEG、PNG、GIF、WEBP格式的图片');
        }

        if ($file['size'] > 5 * 1024 * 1024) {
            throw new BusinessException('图片大小不能超过5MB');
        }

        // 处理图片
        $image = $this->imageManager->make($file['tmp_name']);

        // 调整尺寸(300x300)
        $image->fit(300, 300);

        // 生成文件名
        $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
        $filename = 'avatar_' . $userId . '_' . time() . '.' . $extension;
        $path = UPLOAD_PATH . '/avatars/' . $filename;

        // 保存图片
        $image->save($path);

        // 更新用户头像
        $user = User::find($userId);
        if ($user) {
            // 删除旧头像
            if ($user->avatar && file_exists(UPLOAD_PATH . '/avatars/' . $user->avatar)) {
                unlink(UPLOAD_PATH . '/avatars/' . $user->avatar);
            }
            $user->avatar = $filename;
            $user->save();
        }

        return $filename;
    }

    /**
     * 重置密码
     */
    public function resetPassword(string $email, string $token, string $newPassword): bool
    {
        // 验证token
        $reset = PasswordReset::where('email', '=', $email)
            ->where('token', '=', $token)
            ->where('expires_at', '>', date('Y-m-d H:i:s'))
            ->first();

        if (!$reset) {
            throw new BusinessException('重置链接无效或已过期');
        }

        // 更新密码
        $user = User::where('email', '=', $email)->first();
        if (!$user) {
            throw new BusinessException('用户不存在');
        }

        $user->password = HashService::make($newPassword);
        $user->save();

        // 删除重置记录
        $reset->delete();

        $this->logger->info('密码重置成功', [
            'user_id' => $user->id,
            'email' => $email
        ]);

        return true;
    }

    /**
     * 发送欢迎邮件
     */
    private function sendWelcomeEmail(User $user): void
    {
        // 异步发送邮件
        // 实际项目中可以使用消息队列(Redis、RabbitMQ)
        // 这里简化处理
        $mailService = new MailService();
        $mailService->sendWelcome($user->email, $user->username);
    }
}

5.2 权限服务

// app/Services/AuthService.php
<?php
namespace App\Services;

use App\Models\User;
use App\Models\Role;

class AuthService
{
    private ?User $currentUser = null;

    /**
     * 设置当前用户
     */
    public function setUser(?User $user): void
    {
        $this->currentUser = $user;
    }

    /**
     * 获取当前用户
     */
    public function getUser(): ?User
    {
        return $this->currentUser;
    }

    /**
     * 检查是否已登录
     */
    public function isLoggedIn(): bool
    {
        return $this->currentUser !== null;
    }

    /**
     * 检查是否为超级管理员
     */
    public function isSuperAdmin(): bool
    {
        if (!$this->currentUser) {
            return false;
        }

        $role = $this->currentUser->getRole();
        return $role && $role->slug === 'super_admin';
    }

    /**
     * 检查是否有权限
     */
    public function can(string $permission): bool
    {
        if (!$this->currentUser) {
            return false;
        }

        // 超级管理员拥有所有权限
        if ($this->isSuperAdmin()) {
            return true;
        }

        return $this->currentUser->hasPermission($permission);
    }

    /**
     * 检查是否有任一权限
     */
    public function canAny(array $permissions): bool
    {
        foreach ($permissions as $permission) {
            if ($this->can($permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检查是否有所有权限
     */
    public function canAll(array $permissions): bool
    {
        foreach ($permissions as $permission) {
            if (!$this->can($permission)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 检查是否有角色
     */
    public function hasRole(string $roleSlug): bool
    {
        if (!$this->currentUser) {
            return false;
        }

        $role = $this->currentUser->getRole();
        return $role && $role->slug === $roleSlug;
    }

    /**
     * 验证API Token
     */
    public function authenticateByToken(string $token): ?User
    {
        try {
            $payload = JWTService::verify($token);

            if (!$payload || empty($payload['user_id'])) {
                return null;
            }

            $user = User::find($payload['user_id']);

            // 检查用户状态
            if ($user && $user->status != 1) {
                return null;
            }

            return $user;
        } catch (\Exception $e) {
            return null;
        }
    }
}

第六部分:控制器与路由

6.1 基础控制器

// app/Controllers/Controller.php
<?php
namespace App\Controllers;

use App\Services\AuthService;

abstract class Controller
{
    protected AuthService $auth;

    public function __construct()
    {
        $this->auth = new AuthService();

        // 从Token或Session获取用户
        $this->authenticate();
    }

    protected function authenticate(): void
    {
        // 从Authorization头获取Token
        $headers = getallheaders();
        $token = $headers['Authorization'] ?? $_SESSION['api_token'] ?? null;

        if ($token) {
            $token = str_replace('Bearer ', '', $token);
            $user = $this->auth->authenticateByToken($token);
            $this->auth->setUser($user);
        }
    }

    protected function json($data, int $statusCode = 200): void
    {
        http_response_code($statusCode);
        header('Content-Type: application/json');
        echo json_encode($data);
        exit;
    }

    protected function success($data = null, string $message = 'success'): void
    {
        $this->json([
            'code' => 200,
            'message' => $message,
            'data' => $data,
            'timestamp' => time()
        ]);
    }

    protected function error(string $message, int $code = 400, $data = null): void
    {
        $this->json([
            'code' => $code,
            'message' => $message,
            'data' => $data,
            'timestamp' => time()
        ], $code >= 400 ? $code : 400);
    }

    protected function validate(array $data, array $rules): array
    {
        $errors = [];

        foreach ($rules as $field => $rule) {
            $value = $data[$field] ?? null;

            // 必填验证
            if (strpos($rule, 'required') !== false && empty($value)) {
                $errors[$field] = "{$field}字段不能为空";
                continue;
            }

            if (!empty($value)) {
                // 邮箱验证
                if (strpos($rule, 'email') !== false && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $errors[$field] = "{$field}格式不正确";
                }

                // 最小长度验证
                if (preg_match('/min:(\d+)/', $rule, $matches)) {
                    if (strlen($value) < $matches[1]) {
                        $errors[$field] = "{$field}长度不能小于{$matches[1]}";
                    }
                }

                // 最大长度验证
                if (preg_match('/max:(\d+)/', $rule, $matches)) {
                    if (strlen($value) > $matches[1]) {
                        $errors[$field] = "{$field}长度不能大于{$matches[1]}";
                    }
                }
            }
        }

        if (!empty($errors)) {
            throw new \InvalidArgumentException(json_encode($errors));
        }

        return $data;
    }
}

6.2 用户控制器

// app/Controllers/UserController.php
<?php
namespace App\Controllers;

use App\Services\UserService;
use App\Services\AuthService;
use App\Exceptions\BusinessException;

class UserController extends Controller
{
    private UserService $userService;

    public function __construct()
    {
        parent::__construct();
        $this->userService = new UserService();
    }

    /**
     * 用户注册
     * POST /api/register
     */
    public function register(): void
    {
        try {
            $data = json_decode(file_get_contents('php://input'), true);

            // 验证输入
            $this->validate($data, [
                'username' => 'required|min:3|max:50',
                'email' => 'required|email',
                'password' => 'required|min:6|max:20',
                'confirm_password' => 'required'
            ]);

            // 验证密码一致性
            if ($data['password'] !== $data['confirm_password']) {
                throw new BusinessException('两次输入的密码不一致');
            }

            $result = $this->userService->register($data);
            $this->success($result, '注册成功');

        } catch (BusinessException $e) {
            $this->error($e->getMessage(), 400);
        } catch (\InvalidArgumentException $e) {
            $this->error(json_decode($e->getMessage(), true), 422);
        } catch (\Exception $e) {
            $this->error('服务器错误', 500);
        }
    }

    /**
     * 用户登录
     * POST /api/login
     */
    public function login(): void
    {
        try {
            $data = json_decode(file_get_contents('php://input'), true);

            $this->validate($data, [
                'login' => 'required',
                'password' => 'required'
            ]);

            $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
            $result = $this->userService->login($data['login'], $data['password'], $ip);

            // 保存Token到Session
            $_SESSION['api_token'] = $result['token'];
            $_SESSION['user_id'] = $result['user']['id'];

            $this->success($result, '登录成功');

        } catch (BusinessException $e) {
            $this->error($e->getMessage(), 401);
        } catch (\Exception $e) {
            $this->error('服务器错误', 500);
        }
    }

    /**
     * 获取当前用户信息
     * GET /api/user/profile
     */
    public function profile(): void
    {
        if (!$this->auth->isLoggedIn()) {
            $this->error('未登录', 401);
            return;
        }

        $user = $this->auth->getUser();
        $this->success($user->toArray());
    }

    /**
     * 获取用户列表(需要权限)
     * GET /api/users
     */
    public function list(): void
    {
        if (!$this->auth->can('user.view')) {
            $this->error('无权限访问', 403);
            return;
        }

        $page = (int) ($_GET['page'] ?? 1);
        $perPage = (int) ($_GET['per_page'] ?? 15);
        $filters = [
            'keyword' => $_GET['keyword'] ?? '',
            'role_id' => $_GET['role_id'] ?? '',
            'status' => $_GET['status'] ?? '',
            'order_by' => $_GET['order_by'] ?? 'created_at',
            'order_dir' => $_GET['order_dir'] ?? 'DESC'
        ];

        $result = $this->userService->getUserList($filters, $page, $perPage);
        $this->success($result);
    }

    /**
     * 获取用户详情
     * GET /api/users/{id}
     */
    public function show(int $id): void
    {
        if (!$this->auth->can('user.view')) {
            $this->error('无权限访问', 403);
            return;
        }

        $user = User::find($id);
        if (!$user) {
            $this->error('用户不存在', 404);
            return;
        }

        $this->success($user->toArray());
    }

    /**
     * 创建用户(管理员)
     * POST /api/users
     */
    public function create(): void
    {
        if (!$this->auth->can('user.create')) {
            $this->error('无权限操作', 403);
            return;
        }

        try {
            $data = json_decode(file_get_contents('php://input'), true);

            $this->validate($data, [
                'username' => 'required|min:3|max:50',
                'email' => 'required|email',
                'password' => 'required|min:6',
                'role_id' => 'required|integer'
            ]);

            $user = User::create($data);
            $this->success($user->toArray(), '创建成功', 201);

        } catch (BusinessException $e) {
            $this->error($e->getMessage(), 400);
        } catch (\Exception $e) {
            $this->error('创建失败', 500);
        }
    }

    /**
     * 更新用户
     * PUT /api/users/{id}
     */
    public function update(int $id): void
    {
        if (!$this->auth->can('user.edit')) {
            $this->error('无权限操作', 403);
            return;
        }

        try {
            $data = json_decode(file_get_contents('php://input'), true);
            $user = $this->userService->updateUser($id, $data);
            $this->success($user->toArray(), '更新成功');

        } catch (BusinessException $e) {
            $this->error($e->getMessage(), 400);
        } catch (\Exception $e) {
            $this->error('更新失败', 500);
        }
    }

    /**
     * 删除用户
     * DELETE /api/users/{id}
     */
    public function delete(int $id): void
    {
        if (!$this->auth->can('user.delete')) {
            $this->error('无权限操作', 403);
            return;
        }

        $user = User::find($id);
        if (!$user) {
            $this->error('用户不存在', 404);
            return;
        }

        // 不能删除自己
        if ($this->auth->getUser() && $this->auth->getUser()->id == $id) {
            $this->error('不能删除自己的账号', 400);
            return;
        }

        $user->softDelete();
        $this->success(null, '删除成功');
    }

    /**
     * 上传头像
     * POST /api/user/avatar
     */
    public function uploadAvatar(): void
    {
        if (!$this->auth->isLoggedIn()) {
            $this->error('未登录', 401);
            return;
        }

        try {
            if (empty($_FILES['avatar'])) {
                throw new BusinessException('请选择要上传的图片');
            }

            $filename = $this->userService->uploadAvatar($this->auth->getUser()->id, $_FILES['avatar']);
            $this->success(['avatar' => $filename], '上传成功');

        } catch (BusinessException $e) {
            $this->error($e->getMessage(), 400);
        } catch (\Exception $e) {
            $this->error('上传失败', 500);
        }
    }

    /**
     * 退出登录
     * POST /api/logout
     */
    public function logout(): void
    {
        unset($_SESSION['api_token']);
        unset($_SESSION['user_id']);
        session_destroy();

        $this->success(null, '已退出登录');
    }
}

6.3 路由配置

// routes/web.php
<?php

use App\Controllers\UserController;
use App\Controllers\RoleController;
use App\Middleware\AuthMiddleware;
use App\Middleware\PermissionMiddleware;

// 简单路由实现
class Router
{
    private array $routes = [];

    public function get(string $path, $handler): void
    {
        $this->addRoute('GET', $path, $handler);
    }

    public function post(string $path, $handler): void
    {
        $this->addRoute('POST', $path, $handler);
    }

    public function put(string $path, $handler): void
    {
        $this->addRoute('PUT', $path, $handler);
    }

    public function delete(string $path, $handler): void
    {
        $this->addRoute('DELETE', $path, $handler);
    }

    private function addRoute(string $method, string $path, $handler): void
    {
        $this->routes[] = [
            'method' => $method,
            'path' => $path,
            'handler' => $handler,
            'middleware' => []
        ];
    }

    public function middleware(string $middleware): self
    {
        $lastRoute = &$this->routes[count($this->routes) - 1];
        $lastRoute['middleware'][] = $middleware;
        return $this;
    }

    public function group(array $options, callable $callback): void
    {
        // 实现路由分组
    }

    public function dispatch(string $method, string $uri): void
    {
        // 移除查询参数
        $uri = parse_url($uri, PHP_URL_PATH);

        foreach ($this->routes as $route) {
            if ($route['method'] !== $method) {
                continue;
            }

            // 匹配路径并提取参数
            $pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $route['path']);
            $pattern = '#^' . $pattern . '$#';

            if (preg_match($pattern, $uri, $matches)) {
                // 提取参数
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);

                // 执行中间件
                foreach ($route['middleware'] as $middlewareClass) {
                    $middleware = new $middlewareClass();
                    $middleware->handle();
                }

                // 执行控制器
                if (is_array($route['handler'])) {
                    $controller = new $route['handler'][0]();
                    $method = $route['handler'][1];
                    call_user_func_array([$controller, $method], $params);
                } else {
                    call_user_func_array($route['handler'], $params);
                }
                return;
            }
        }

        // 404 Not Found
        http_response_code(404);
        echo json_encode(['code' => 404, 'message' => 'Not Found']);
    }
}

$router = new Router();

// 公开路由
$router->post('/api/register', [UserController::class, 'register']);
$router->post('/api/login', [UserController::class, 'login']);

// 需要认证的路由
$router->get('/api/user/profile', [UserController::class, 'profile'])->middleware(AuthMiddleware::class);
$router->post('/api/user/avatar', [UserController::class, 'uploadAvatar'])->middleware(AuthMiddleware::class);
$router->post('/api/logout', [UserController::class, 'logout'])->middleware(AuthMiddleware::class);

// 需要权限的路由
$router->get('/api/users', [UserController::class, 'list'])
    ->middleware(AuthMiddleware::class)
    ->middleware(PermissionMiddleware::class);

$router->get('/api/users/{id}', [UserController::class, 'show'])
    ->middleware(AuthMiddleware::class)
    ->middleware(PermissionMiddleware::class);

$router->post('/api/users', [UserController::class, 'create'])
    ->middleware(AuthMiddleware::class)
    ->middleware(PermissionMiddleware::class);

$router->put('/api/users/{id}', [UserController::class, 'update'])
    ->middleware(AuthMiddleware::class)
    ->middleware(PermissionMiddleware::class);

$router->delete('/api/users/{id}', [UserController::class, 'delete'])
    ->middleware(AuthMiddleware::class)
    ->middleware(PermissionMiddleware::class);

来源:
https://htnus.cn/

相关文章
|
6天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
4326 17
|
16天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
14942 138
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
5天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
3103 8
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
7天前
|
人工智能 自然语言处理 数据挖掘
零基础30分钟搞定 Claude Code,这一步90%的人直接跳过了
本文直击Claude Code使用痛点,提供零基础30分钟上手指南:强调必须配置“工作上下文”(about-me.md+anti-ai-style.md)、采用Cowork/Code模式、建立标准文件结构、用提问式提示词驱动AI理解→规划→执行。附可复制模板与真实项目启动法,助你将Claude从聊天工具升级为高效执行系统。
|
6天前
|
人工智能 定位技术
Claude Code源码泄露:8大隐藏功能曝光
2026年3月,Anthropic因配置失误致Claude Code超51万行源码泄露,意外促成“被动开源”。代码中藏有8大未发布功能,揭示其向“超级智能体”演进的完整蓝图,引发AI编程领域震动。(239字)
2453 9