更新后端基础

This commit is contained in:
技术老胡
2026-02-23 11:33:27 +08:00
parent 4a34feec54
commit d29751cce8
75 changed files with 2978 additions and 1489 deletions

View File

@@ -2,41 +2,62 @@
namespace app\common\base;
use support\Response;
use support\Request;
use support\Response;
/**
* 控制器基础类
* - 提供统一的 success/fail 响应封装
* 控制器基础
*
* 约定:
* - 控制器统一通过 $this->request->* 获取请求数据
* - 为避免每个控制器构造函数重复注入 Request本类通过 __get('request') 返回当前请求对象
* 约定统一的 JSON 返回结构
* {
* "code": 200,
* "message": "success",
* "data": ...
* }
*/
abstract class BaseController
class BaseController
{
/**
* 成功响应
* 成功返回
*/
protected function success(mixed $data = null, string $message = 'success', int $code = 200): Response
{
return json([
'code' => $code,
'code' => $code,
'message' => $message,
'data' => $data,
'data' => $data,
]);
}
/**
* 失败响应
* 失败返回
*/
protected function fail(string $message = 'fail', int $code = 500, mixed $data = null): Response
protected function fail(string $message = 'error', int $code = 500, mixed $data = null): Response
{
return json([
'code' => $code,
'code' => $code,
'message' => $message,
'data' => $data,
'data' => $data,
]);
}
/**
* 获取当前登录用户的 token 载荷
*
* 从 AuthMiddleware 注入的用户信息中获取
*/
protected function currentUser(Request $request): ?array
{
return $request->user ?? null;
}
/**
* 获取当前登录用户ID
*/
protected function currentUserId(Request $request): int
{
return (int) ($request->userId ?? 0);
}
}

View File

@@ -1,164 +0,0 @@
<?php
namespace app\common\base;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Query\Builder;
use support\Db;
/**
* DAO 基础类
* - 封装数据库连接和基础 CRUD 操作
* - 提供查询构造器访问
*/
abstract class BaseDao
{
/**
* 数据库连接名称(子类可覆盖)
*/
protected string $connection = 'default';
/**
* 表名(子类必须定义)
*/
protected string $table = '';
/**
* 获取数据库连接
*/
protected function connection()
{
return Db::connection($this->connection);
}
/**
* 获取查询构造器
*/
protected function query(): Builder
{
return Db::connection($this->connection)->table($this->table);
}
/**
* 根据 ID 查找单条记录
*/
public function findById(int $id, array $columns = ['*']): ?array
{
$result = $this->query()->where('id', $id)->first($columns);
return $result ? (array)$result : null;
}
/**
* 根据条件查找单条记录
*/
public function findOne(array $where, array $columns = ['*']): ?array
{
$query = $this->query();
foreach ($where as $key => $value) {
$query->where($key, $value);
}
$result = $query->first($columns);
return $result ? (array)$result : null;
}
/**
* 根据条件查找多条记录
*/
public function findMany(array $where = [], array $columns = ['*'], array $orderBy = [], int $limit = 0): array
{
$query = $this->query();
foreach ($where as $key => $value) {
if (is_array($value)) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
foreach ($orderBy as $column => $direction) {
$query->orderBy($column, $direction);
}
if ($limit > 0) {
$query->limit($limit);
}
$results = $query->get($columns);
return array_map(fn($item) => (array)$item, $results->toArray());
}
/**
* 插入单条记录
*/
public function insert(array $data): int
{
return $this->query()->insertGetId($data);
}
/**
* 批量插入
*/
public function insertBatch(array $data): bool
{
return $this->query()->insert($data);
}
/**
* 根据 ID 更新记录
*/
public function updateById(int $id, array $data): int
{
return $this->query()->where('id', $id)->update($data);
}
/**
* 根据条件更新记录
*/
public function update(array $where, array $data): int
{
$query = $this->query();
foreach ($where as $key => $value) {
$query->where($key, $value);
}
return $query->update($data);
}
/**
* 根据 ID 删除记录
*/
public function deleteById(int $id): int
{
return $this->query()->where('id', $id)->delete();
}
/**
* 根据条件删除记录
*/
public function delete(array $where): int
{
$query = $this->query();
foreach ($where as $key => $value) {
$query->where($key, $value);
}
return $query->delete();
}
/**
* 统计记录数
*/
public function count(array $where = []): int
{
$query = $this->query();
foreach ($where as $key => $value) {
$query->where($key, $value);
}
return $query->count();
}
/**
* 判断记录是否存在
*/
public function exists(array $where): bool
{
return $this->count($where) > 0;
}
}

View File

@@ -2,100 +2,35 @@
namespace app\common\base;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use support\Model;
/**
* 模型基础类
* - 统一禁用时间戳(如需要可在子类开启)
* - 提供常用查询作用域和便捷方法
* 所有业务模型基础
*/
abstract class BaseModel extends Model
class BaseModel extends Model
{
/**
* 禁用时间戳(默认)
* 约定所有主键字段名
*
* @var string
*/
protected $primaryKey = 'id';
/**
* 是否自动维护 created_at / updated_at
*
* 大部分业务表都有这两个字段,如不需要可在子类里覆盖为 false。
*
* @var bool
*/
public $timestamps = false;
/**
* 允许批量赋值的字段(子类可覆盖)
* 默认不禁止任何字段的批量赋值
*
* 建议在具体模型中按需设置 $fillable 或 $guarded。
*
* @var array
*/
protected $guarded = [];
/**
* 连接名称(默认使用配置的 default
*/
protected $connection = 'default';
/**
* 根据 ID 查找(返回数组格式)
*/
public static function findById(int $id): ?array
{
$model = static::find($id);
return $model ? $model->toArray() : null;
}
/**
* 根据条件查找单条(返回数组格式)
*/
public static function findOne(array $where): ?array
{
$query = static::query();
foreach ($where as $key => $value) {
$query->where($key, $value);
}
$model = $query->first();
return $model ? $model->toArray() : null;
}
/**
* 根据条件查找多条(返回数组格式)
*/
public static function findMany(array $where = [], array $orderBy = [], int $limit = 0): array
{
$query = static::query();
foreach ($where as $key => $value) {
if (is_array($value)) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
foreach ($orderBy as $column => $direction) {
$query->orderBy($column, $direction);
}
if ($limit > 0) {
$query->limit($limit);
}
return $query->get()->map(fn($item) => $item->toArray())->toArray();
}
/**
* 启用状态作用域
*/
public function scopeEnabled(Builder $query): Builder
{
return $query->where('status', 1);
}
/**
* 禁用状态作用域
*/
public function scopeDisabled(Builder $query): Builder
{
return $query->where('status', 0);
}
/**
* 转换为数组(统一处理 null 值)
*/
public function toArray(): array
{
$array = parent::toArray();
// 将 null 值转换为空字符串(可选,根据业务需求调整)
return array_map(fn($value) => $value === null ? '' : $value, $array);
}
}

View File

@@ -2,57 +2,73 @@
namespace app\common\base;
use support\Model;
/**
* 仓基础类
* - 支持注入 DAO 依赖
* - 通过魔术方法代理 DAO 的方法调用
* - 提供通用的数据访问封装
* 仓储层基础
*
* 封装单表常用的 CRUD / 分页操作,具体仓储继承后可扩展业务查询。
*/
abstract class BaseRepository
{
/**
* DAO 实例(可选,子类通过构造函数注入)
* @var Model
*/
protected ?BaseDao $dao = null;
protected Model $model;
/**
* 构造函数,子类可注入 DAO
*/
public function __construct(?BaseDao $dao = null)
public function __construct(Model $model)
{
$this->dao = $dao;
$this->model = $model;
}
/**
* 魔术方法:代理 DAO 的方法调用
* 如果仓库自身没有该方法,且存在 DAO 实例,则调用 DAO 的对应方法
* 根据主键查询
*/
public function __call(string $method, array $arguments)
public function find(int $id, array $columns = ['*']): ?Model
{
if ($this->dao && method_exists($this->dao, $method)) {
return $this->dao->{$method}(...$arguments);
return $this->model->newQuery()->find($id, $columns);
}
/**
* 新建记录
*/
public function create(array $data): Model
{
return $this->model->newQuery()->create($data);
}
/**
* 按主键更新
*/
public function updateById(int $id, array $data): bool
{
return (bool) $this->model->newQuery()->whereKey($id)->update($data);
}
/**
* 按主键删除
*/
public function deleteById(int $id): bool
{
return (bool) $this->model->newQuery()->whereKey($id)->delete();
}
/**
* 简单分页查询示例
*
* @param array $where ['字段' => 值],值为 null / '' 时会被忽略
*/
public function paginate(array $where = [], int $page = 1, int $pageSize = 10, array $columns = ['*'])
{
$query = $this->model->newQuery();
foreach ($where as $field => $value) {
if ($value === null || $value === '') {
continue;
}
$query->where($field, $value);
}
throw new \BadMethodCallException(
sprintf('Method %s::%s does not exist', static::class, $method)
);
}
/**
* 检查 DAO 是否已注入
*/
protected function hasDao(): bool
{
return $this->dao !== null;
}
/**
* 获取 DAO 实例
*/
protected function getDao(): ?BaseDao
{
return $this->dao;
return $query->paginate($pageSize, $columns, 'page', $page);
}
}

View File

@@ -2,55 +2,23 @@
namespace app\common\base;
use support\Log;
use support\Db;
/**
* 服务基础类
* - 提供日志记录能力
* - 预留事务、事件发布等扩展点
* 业务服务基础
*/
abstract class BaseService
class BaseService
{
/**
* 记录日志
* 事务封装
*
* 使用方式:
* $this->transaction(function () { ... });
*/
protected function log(string $level, string $message, array $context = []): void
protected function transaction(callable $callback)
{
$logMessage = sprintf('[%s] %s', static::class, $message);
Log::log($level, $logMessage, $context);
}
/**
* 记录信息日志
*/
protected function info(string $message, array $context = []): void
{
$this->log('info', $message, $context);
}
/**
* 记录警告日志
*/
protected function warning(string $message, array $context = []): void
{
$this->log('warning', $message, $context);
}
/**
* 记录错误日志
*/
protected function error(string $message, array $context = []): void
{
$this->log('error', $message, $context);
}
/**
* 记录调试日志
*/
protected function debug(string $message, array $context = []): void
{
$this->log('debug', $message, $context);
return Db::connection()->transaction(function () use ($callback) {
return $callback();
});
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace app\common\constants;
/**
* 通用启用/禁用状态
* 多个表 status 字段复用users.status, departments.status, roles.status, cron_jobs.status 等
*/
class CommonStatus
{
/**
* 禁用 / 停用
*/
public const DISABLED = 0;
/**
* 启用
*/
public const ENABLED = 1;
}

View File

@@ -0,0 +1,32 @@
<?php
namespace app\common\constants;
/**
* 数据字典编码常量
* 对应表dict_groups.code
*/
class DictCode
{
/**
* 性别字典
*/
public const GENDER = 'gender';
/**
* 通用启用/禁用状态字典
*/
public const STATUS = 'status';
/**
* 岗位字典
*/
public const POST = 'post';
/**
* 任务状态字典
*/
public const TASK_STATUS = 'taskStatus';
}

View File

@@ -0,0 +1,22 @@
<?php
namespace app\common\constants;
/**
* 权限标识常量
* 对应表menus.permission 以及前端 permissionData.meta.permission
*/
class Permission
{
// 系统按钮权限(示例)
public const SYS_BTN_ADD = 'sys:btn:add';
public const SYS_BTN_EDIT = 'sys:btn:edit';
public const SYS_BTN_DELETE = 'sys:btn:delete';
// 通用按钮权限(示例)
public const COMMON_BTN_ADD = 'common:btn:add';
public const COMMON_BTN_EDIT = 'common:btn:edit';
public const COMMON_BTN_DELETE = 'common:btn:delete';
}

View File

@@ -0,0 +1,22 @@
<?php
namespace app\common\constants;
/**
* 角色编码常量
* 对应表roles.code
*/
class RoleCode
{
/**
* 超级管理员
*/
public const ADMIN = 'admin';
/**
* 普通员工
*/
public const COMMON = 'common';
}

View File

@@ -0,0 +1,15 @@
<?php
namespace app\common\constants;
/**
* 通用是/否布尔枚举
* 可复用在 is_admin 等字段
*/
class YesNo
{
public const NO = 0;
public const YES = 1;
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\common\enums;
/**
* 定时任务日志状态
* 对应表cron_logs.status
* 1 成功 0 失败
*/
class CronLogStatus
{
public const FAIL = 0;
public const SUCCESS = 1;
}

View File

@@ -0,0 +1,23 @@
<?php
namespace app\common\enums;
/**
* 定时任务执行策略misfire_policy
* 对应表cron_jobs.misfire_policy
* 1 循环执行 2 执行一次
*/
class CronMisfirePolicy
{
/**
* 循环执行
*/
public const LOOP = 1;
/**
* 只执行一次
*/
public const RUN_ONCE = 2;
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\common\enums;
/**
* 定时任务类型
* 对应表cron_jobs.task_type
* 0 cron 表达式 1 时间间隔(秒)
*/
class CronTaskType
{
public const CRON_EXPRESSION = 0;
public const INTERVAL_SECOND = 1;
}

View File

@@ -0,0 +1,28 @@
<?php
namespace app\common\enums;
/**
* 菜单类型枚举
* 对应表menus.type
* 1 目录 2 菜单 3 按钮
*/
class MenuType
{
/**
* 目录
*/
public const DIRECTORY = 1;
/**
* 菜单
*/
public const MENU = 2;
/**
* 按钮(权限点)
*/
public const BUTTON = 3;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace app\common\enums;
/**
* 用户性别枚举
* 对应表users.sex 以及 gender 字典
*/
class UserSex
{
/**
* 女
*/
public const FEMALE = 0;
/**
* 男
*/
public const MALE = 1;
/**
* 未知/其它
*/
public const UNKNOWN = 2;
}

View File

@@ -22,13 +22,11 @@ class Cors implements MiddlewareInterface
{
$response = strtoupper($request->method()) === 'OPTIONS' ? response('', 204) : $handler($request);
$response->withHeaders([
return $response->withHeaders([
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => $request->header('origin', '*'),
'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
]);
return $response;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace app\common\utils;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtUtil
{
/**
* 生成 JWT
*/
public static function generateToken(array $payloadBase): string
{
$config = config('jwt', []);
$secret = $config['secret'] ?? 'mpay-secret';
$ttl = (int)($config['ttl'] ?? 7200);
$alg = $config['alg'] ?? 'HS256';
$now = time();
$payload = array_merge($payloadBase, [
'iat' => $now,
'exp' => $now + $ttl,
]);
return JWT::encode($payload, $secret, $alg);
}
/**
* 解析 JWT
*/
public static function parseToken(string $token): array
{
$config = config('jwt', []);
$secret = $config['secret'] ?? 'mpay-secret';
$alg = $config['alg'] ?? 'HS256';
$decoded = JWT::decode($token, new Key($secret, $alg));
return json_decode(json_encode($decoded, JSON_UNESCAPED_UNICODE), true) ?: [];
}
/**
* 获取 ttl
*/
public static function getTtl(): int
{
$config = config('jwt', []);
return (int)($config['ttl'] ?? 7200);
}
/**
* 获取缓存前缀
*/
public static function getCachePrefix(): string
{
$config = config('jwt', []);
return $config['cache_prefix'] ?? 'token_';
}
}