第三部分:核心架构实现
3.1 数据库连接与模型基类
// config/database.php
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'port' => $_ENV['DB_PORT'] ?? '3306',
'database' => $_ENV['DB_NAME'] ?? 'user_system',
'username' => $_ENV['DB_USER'] ?? 'root',
'password' => $_ENV['DB_PASS'] ?? '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
],
];
// app/Core/Database/Connection.php
<?php
namespace App\Core\Database;
use PDO;
use PDOException;
class Connection
{
private static ?PDO $instance = null;
private static array $config = [];
public static function setConfig(array $config): void
{
self::$config = $config;
}
public static function getInstance(): PDO
{
if (self::$instance === null) {
self::connect();
}
return self::$instance;
}
private static function connect(): void
{
$config = self::$config;
$dsn = sprintf(
"%s:host=%s;port=%s;dbname=%s;charset=%s",
$config['driver'],
$config['host'],
$config['port'],
$config['database'],
$config['charset']
);
try {
self::$instance = new PDO($dsn, $config['username'], $config['password'], $config['options']);
} catch (PDOException $e) {
throw new \RuntimeException("数据库连接失败: " . $e->getMessage());
}
}
public static function beginTransaction(): bool
{
return self::getInstance()->beginTransaction();
}
public static function commit(): bool
{
return self::getInstance()->commit();
}
public static function rollback(): bool
{
return self::getInstance()->rollBack();
}
}
3.2 Model基类
// app/Core/Model.php
<?php
namespace App\Core;
use App\Core\Database\Connection;
use PDO;
abstract class Model
{
protected static string $table;
protected static string $primaryKey = 'id';
protected array $attributes = [];
protected array $original = [];
protected array $fillable = [];
protected array $hidden = [];
protected array $casts = [];
public function __construct(array $attributes = [])
{
$this->fill($attributes);
}
public function fill(array $attributes): self
{
foreach ($attributes as $key => $value) {
if (in_array($key, $this->fillable)) {
$this->setAttribute($key, $value);
}
}
return $this;
}
public function setAttribute(string $key, $value): self
{
$this->attributes[$key] = $value;
return $this;
}
public function getAttribute(string $key)
{
return $this->attributes[$key] ?? null;
}
public function __get($key)
{
return $this->getAttribute($key);
}
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
public function save(): bool
{
if (isset($this->attributes[static::$primaryKey])) {
return $this->update();
}
return $this->insert();
}
protected function insert(): bool
{
$fields = array_keys($this->attributes);
$placeholders = ':' . implode(', :', $fields);
$sql = sprintf(
"INSERT INTO %s (%s) VALUES (%s)",
static::$table,
implode(', ', $fields),
$placeholders
);
$stmt = Connection::getInstance()->prepare($sql);
$result = $stmt->execute($this->attributes);
if ($result) {
$this->attributes[static::$primaryKey] = Connection::getInstance()->lastInsertId();
$this->syncOriginal();
}
return $result;
}
protected function update(): bool
{
$fields = [];
foreach ($this->attributes as $key => $value) {
if ($key !== static::$primaryKey) {
$fields[] = "{$key} = :{$key}";
}
}
$sql = sprintf(
"UPDATE %s SET %s WHERE %s = :primary_key",
static::$table,
implode(', ', $fields),
static::$primaryKey
);
$params = $this->attributes;
$params['primary_key'] = $this->attributes[static::$primaryKey];
$stmt = Connection::getInstance()->prepare($sql);
$result = $stmt->execute($params);
if ($result) {
$this->syncOriginal();
}
return $result;
}
public function delete(): bool
{
$sql = sprintf(
"DELETE FROM %s WHERE %s = :primary_key",
static::$table,
static::$primaryKey
);
$stmt = Connection::getInstance()->prepare($sql);
return $stmt->execute(['primary_key' => $this->attributes[static::$primaryKey]]);
}
public static function find($id): ?static
{
$sql = sprintf(
"SELECT * FROM %s WHERE %s = :id LIMIT 1",
static::$table,
static::$primaryKey
);
$stmt = Connection::getInstance()->prepare($sql);
$stmt->execute(['id' => $id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
return (new static())->syncOriginal()->fill($result);
}
return null;
}
public static function where(string $column, string $operator, $value): QueryBuilder
{
return (new QueryBuilder(static::class))->where($column, $operator, $value);
}
public static function all(): array
{
$sql = sprintf("SELECT * FROM %s", static::$table);
$stmt = Connection::getInstance()->query($sql);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
return array_map(function($result) {
return (new static())->syncOriginal()->fill($result);
}, $results);
}
protected function syncOriginal(): self
{
$this->original = $this->attributes;
return $this;
}
public function toArray(): array
{
$attributes = $this->attributes;
foreach ($this->hidden as $key) {
unset($attributes[$key]);
}
foreach ($this->casts as $key => $type) {
if (isset($attributes[$key])) {
$attributes[$key] = $this->cast($attributes[$key], $type);
}
}
return $attributes;
}
protected function cast($value, string $type)
{
return match($type) {
'int', 'integer' => (int) $value,
'float', 'double' => (float) $value,
'bool', 'boolean' => (bool) $value,
'json', 'array' => json_decode($value, true),
default => $value,
};
}
}
3.3 QueryBuilder实现
// app/Core/Database/QueryBuilder.php
<?php
namespace App\Core\Database;
use PDO;
class QueryBuilder
{
private string $modelClass;
private string $table;
private array $wheres = [];
private array $bindings = [];
private array $orderBy = [];
private ?int $limit = null;
private ?int $offset = null;
public function __construct(string $modelClass)
{
$this->modelClass = $modelClass;
$this->table = $modelClass::$table;
}
public function where(string $column, string $operator, $value): self
{
$this->wheres[] = [
'type' => 'basic',
'column' => $column,
'operator' => $operator,
'value' => $value
];
$this->bindings[] = $value;
return $this;
}
public function whereIn(string $column, array $values): self
{
$placeholders = implode(',', array_fill(0, count($values), '?'));
$this->wheres[] = [
'type' => 'in',
'column' => $column,
'values' => $values,
'placeholders' => $placeholders
];
$this->bindings = array_merge($this->bindings, $values);
return $this;
}
public function orderBy(string $column, string $direction = 'ASC'): self
{
$this->orderBy[] = "{$column} {$direction}";
return $this;
}
public function limit(int $limit): self
{
$this->limit = $limit;
return $this;
}
public function offset(int $offset): self
{
$this->offset = $offset;
return $this;
}
public function get(): array
{
$sql = "SELECT * FROM {$this->table}";
if (!empty($this->wheres)) {
$sql .= " WHERE " . $this->buildWhereClause();
}
if (!empty($this->orderBy)) {
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
}
if ($this->limit !== null) {
$sql .= " LIMIT {$this->limit}";
}
if ($this->offset !== null) {
$sql .= " OFFSET {$this->offset}";
}
$stmt = Connection::getInstance()->prepare($sql);
$stmt->execute($this->bindings);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
return array_map(function($result) {
return (new $this->modelClass())->syncOriginal()->fill($result);
}, $results);
}
public function first(): ?object
{
$this->limit(1);
$results = $this->get();
return $results[0] ?? null;
}
public function count(): int
{
$sql = "SELECT COUNT(*) as count FROM {$this->table}";
if (!empty($this->wheres)) {
$sql .= " WHERE " . $this->buildWhereClause();
}
$stmt = Connection::getInstance()->prepare($sql);
$stmt->execute($this->bindings);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return (int) $result['count'];
}
public function paginate(int $perPage = 15, int $page = 1): array
{
$total = $this->count();
$lastPage = ceil($total / $perPage);
$page = max(1, min($page, $lastPage));
$offset = ($page - 1) * $perPage;
$items = $this->limit($perPage)->offset($offset)->get();
return [
'data' => $items,
'meta' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'last_page' => $lastPage,
]
];
}
private function buildWhereClause(): string
{
$clauses = [];
foreach ($this->wheres as $where) {
if ($where['type'] === 'basic') {
$clauses[] = "{$where['column']} {$where['operator']} ?";
} elseif ($where['type'] === 'in') {
$clauses[] = "{$where['column']} IN ({$where['placeholders']})";
}
}
return implode(' AND ', $clauses);
}
}
第四部分:用户模型与业务逻辑
4.1 User模型
// app/Models/User.php
<?php
namespace App\Models;
use App\Core\Model;
use App\Core\Database\Connection;
use App\Services\HashService;
use App\Services\JWTService;
use Ramsey\Uuid\Uuid;
class User extends Model
{
protected static string $table = 'users';
protected static string $primaryKey = 'id';
protected array $fillable = [
'uuid', 'username', 'email', 'phone',
'password', 'avatar', 'role_id', 'status'
];
protected array $hidden = [
'password', 'deleted_at'
];
protected array $casts = [
'id' => 'int',
'role_id' => 'int',
'status' => 'int',
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
if (empty($this->uuid)) {
$this->uuid = Uuid::uuid4()->toString();
}
}
/**
* 创建新用户
*/
public static function create(array $data): ?self
{
$user = new self();
$user->fill($data);
if (isset($data['password'])) {
$user->password = HashService::make($data['password']);
}
return $user->save() ? $user : null;
}
/**
* 验证用户凭证
*/
public static function authenticate(string $login, string $password): ?self
{
$user = self::where('email', '=', $login)
->orWhere('username', '=', $login)
->first();
if ($user && HashService::verify($password, $user->password)) {
return $user;
}
return null;
}
/**
* 更新最后登录信息
*/
public function updateLoginInfo(string $ip): bool
{
$this->last_login_ip = $ip;
$this->last_login_at = date('Y-m-d H:i:s');
return $this->save();
}
/**
* 检查用户权限
*/
public function hasPermission(string $permission): bool
{
$role = Role::find($this->role_id);
if (!$role) {
return false;
}
return $role->hasPermission($permission);
}
/**
* 获取用户角色
*/
public function getRole(): ?Role
{
return Role::find($this->role_id);
}
/**
* 生成API Token
*/
public function generateApiToken(): string
{
return JWTService::generate([
'user_id' => $this->id,
'uuid' => $this->uuid,
'email' => $this->email,
'role_id' => $this->role_id
]);
}
/**
* 软删除用户
*/
public function softDelete(): bool
{
$sql = "UPDATE users SET deleted_at = NOW() WHERE id = :id";
$stmt = Connection::getInstance()->prepare($sql);
return $stmt->execute(['id' => $this->id]);
}
}
4.2 Role模型
// app/Models/Role.php
<?php
namespace App\Models;
use App\Core\Model;
class Role extends Model
{
protected static string $table = 'roles';
protected array $fillable = ['name', 'slug', 'description', 'permissions'];
protected array $casts = [
'permissions' => 'array',
];
/**
* 检查是否有权限
*/
public function hasPermission(string $permission): bool
{
$permissions = $this->permissions ?? [];
// 超级管理员拥有所有权限
if (in_array('*', $permissions)) {
return true;
}
return in_array($permission, $permissions);
}
/**
* 授予权限
*/
public function givePermission(string $permission): self
{
$permissions = $this->permissions ?? [];
if (!in_array($permission, $permissions)) {
$permissions[] = $permission;
$this->permissions = $permissions;
$this->save();
}
return $this;
}
/**
* 撤销权限
*/
public function revokePermission(string $permission): self
{
$permissions = $this->permissions ?? [];
$key = array_search($permission, $permissions);
if ($key !== false) {
unset($permissions[$key]);
$this->permissions = array_values($permissions);
$this->save();
}
return $this;
}
}