重构初始化

This commit is contained in:
技术老胡
2026-04-15 11:45:46 +08:00
parent 72d72d735b
commit 7612026773
381 changed files with 28287 additions and 14717 deletions

View File

@@ -2,49 +2,44 @@
namespace app\common\base;
use app\exception\ValidationException;
use support\Context;
use support\Request;
use support\Response;
/**
* 控制器基础父类
* HTTP 层基础控制器。
*
* 约定统一的 JSON 返回结构:
* {
* "code": 200,
* "message": "success",
* "data": ...
* }
* 统一提供响应封装、参数校验、请求上下文读取等通用能力。
*/
class BaseController
{
/**
* 成功返回
* 返回成功响应。
*/
protected function success(mixed $data = null, string $message = 'success', int $code = 200): Response
protected function success(mixed $data = null, string $message = '操作成功', int $code = 200): Response
{
return json([
'code' => $code,
'code' => $code,
'msg' => $message,
'data' => $data,
'data' => $data,
]);
}
/**
* 失败返回
* 返回失败响应。
*/
protected function fail(string $message = 'error', int $code = 500, mixed $data = null): Response
protected function fail(string $message = '操作失败', int $code = 500, mixed $data = null): Response
{
return json([
'code' => $code,
'code' => $code,
'msg' => $message,
'data' => $data,
'data' => $data,
]);
}
/**
* 统一分页返回结构
*
* @param mixed $paginator Laravel/Eloquent paginator
* 返回统一分页响应。
*/
protected function page(mixed $paginator): Response
{
@@ -58,28 +53,76 @@ class BaseController
}
return $this->success([
'list' => $paginator->items(),
'total' => $paginator->total(),
'page' => $paginator->currentPage(),
'size' => $paginator->perPage(),
'list' => $paginator->items(),
'total' => $paginator->total(),
'page' => $paginator->currentPage(),
'size' => $paginator->perPage(),
]);
}
/**
* 获取当前登录用户的 token 载荷
* 通过校验器类验证请求数据。
*
* 从 AuthMiddleware 注入的用户信息中获取
* @param class-string $validatorClass
*/
protected function currentUser(Request $request): ?array
protected function validated(array $data, string $validatorClass, ?string $scene = null): array
{
return $request->user ?? null;
$validator = $validatorClass::make($data);
if ($scene !== null) {
$validator = $validator->withScene($scene);
}
return $validator
->withException(ValidationException::class)
->validate();
}
/**
* 获取当前登录用户ID
* 获取中间件预处理后的标准化参数。
*/
protected function currentUserId(Request $request): int
protected function payload(Request $request): array
{
return (int) ($request->userId ?? 0);
$payload = (array) $request->all();
$normalized = Context::get('mpay.normalized_input', []);
if (is_array($normalized) && !empty($normalized)) {
$payload = array_replace($payload, $normalized);
}
return $payload;
}
/**
* 读取请求属性。
*/
protected function requestAttribute(Request $request, string $key, mixed $default = null): mixed
{
return Context::get($key, $default);
}
/**
* 获取中间件注入的当前管理员 ID。
*/
protected function currentAdminId(Request $request): int
{
return (int) $this->requestAttribute($request, 'auth.admin_id', 0);
}
/**
* 获取中间件注入的当前商户 ID。
*/
protected function currentMerchantId(Request $request): int
{
return (int) $this->requestAttribute($request, 'auth.merchant_id', 0);
}
/**
* 获取中间件注入的当前商户编号。
*/
protected function currentMerchantNo(Request $request): string
{
return (string) $this->requestAttribute($request, 'auth.merchant_no', '');
}
}

View File

@@ -2,35 +2,50 @@
namespace app\common\base;
use app\common\util\FormatHelper;
use DateTimeInterface;
use support\Model;
/**
* 所有业务模型的基础父类
* 所有业务模型的基础父类
*
* 统一主键、时间戳和默认批量赋值策略。
*/
class BaseModel extends Model
{
/**
* 约定所有主键字段名
* 默认主键字段名
*
* @var string
*/
protected $primaryKey = 'id';
/**
* 是否自动维护 created_at / updated_at
* 是否自动维护 created_at / updated_at
*
* 大部分业务表都这两个字段,如不需要可在子类覆盖为 false。
* 大部分业务表都包含这两个字段,如有例外可在子类覆盖为 false。
*
* @var bool
*/
public $timestamps = false;
public $timestamps = true;
/**
* 默认不禁止任何字段的批量赋值
* 默认仅保护主键,其他字段按子类 fillable 约束。
*
* 建议在具体模型中按需设置 $fillable 或 $guarded
* 建议在具体模型中显式声明 $fillable。
*
* @var array
*/
protected $guarded = [];
protected $guarded = ['id'];
/**
* 统一模型时间字段的 JSON 输出格式。
*
* 避免前端收到 ISO8601如 2026-04-02T01:50:40.000000Z)这类不直观的时间串,
* 统一改为后台常用的本地展示格式。
*/
protected function serializeDate(DateTimeInterface $date): string
{
return FormatHelper::dateTime($date);
}
}

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace app\common\base;
use app\common\contracts\PayPluginInterface;
use app\exceptions\PaymentException;
use app\common\interface\PayPluginInterface;
use app\exception\PaymentException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
@@ -21,7 +21,7 @@ use support\Log;
* - 子类可在 `init()` 中配置第三方 SDK例如 yansongda/pay或读取必填参数。
*
* 约定:
* - 这里的 `$channelConfig` 来源通常是 `ma_pay_channel.config_json`,属于“通道级配置”
* - 这里的 `$channelConfig` 来源通常是 `ma_payment_plugin_conf.config`,并附带通道维度上下文
* - 业务级入参(如订单号、金额、回调地址等)不要混进 `$channelConfig`,应从 `pay()` 的 `$order` 参数获取。
*/
abstract class BasePayment implements PayPluginInterface
@@ -108,6 +108,12 @@ abstract class BasePayment implements PayPluginInterface
return $this->paymentInfo['link'] ?? '';
}
/** 获取版本号 */
public function getVersion(): string
{
return $this->paymentInfo['version'] ?? '';
}
// ==================== 能力声明 ====================
/**

View File

@@ -2,65 +2,194 @@
namespace app\common\base;
use Illuminate\Database\UniqueConstraintViolationException;
use support\Model;
use support\Db;
/**
* 仓储层基础
* 仓储层基础类
*
* 封装单表常用的 CRUD / 分页操作,具体仓储继承后可扩展业务查询
* 封装通用 CRUD、条件查询、加锁查询和分页查询能力
*/
abstract class BaseRepository
{
/**
* @var Model
* 当前仓储绑定的模型实例。
*/
protected Model $model;
/**
* 构造函数,绑定模型实例。
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* 根据主键查询
* 获取查询构造器。
*/
public function find(int $id, array $columns = ['*']): ?Model
public function query()
{
return $this->model->newQuery()->find($id, $columns);
return $this->model->newQuery();
}
/**
* 新建记录
* 按主键查询记录
*/
public function find(int|string $id, array $columns = ['*']): ?Model
{
return $this->query()->find($id, $columns);
}
/**
* 新增记录。
*/
public function create(array $data): Model
{
return $this->model->newQuery()->create($data);
return $this->query()->create($data);
}
/**
* 按主键更新
* 按主键更新记录。
*/
public function updateById(int $id, array $data): bool
public function updateById(int|string $id, array $data): bool
{
return (bool) $this->model->newQuery()->whereKey($id)->update($data);
return (bool) $this->query()->whereKey($id)->update($data);
}
/**
* 按主键删除
* 按唯一键更新记录。
*/
public function deleteById(int $id): bool
public function updateByKey(int|string $key, array $data): bool
{
return (bool) $this->model->newQuery()->whereKey($id)->delete();
return (bool) $this->query()->whereKey($key)->update($data);
}
/**
* 简单分页查询示例
* 按条件批量更新记录。
*/
public function updateWhere(array $where, array $data): int
{
$query = $this->query();
if (!empty($where)) {
$query->where($where);
}
return (int) $query->update($data);
}
/**
* 按主键删除记录。
*/
public function deleteById(int|string $id): bool
{
return (bool) $this->query()->whereKey($id)->delete();
}
/**
* 按条件批量删除记录。
*/
public function deleteWhere(array $where): int
{
$query = $this->query();
if (!empty($where)) {
$query->where($where);
}
return (int) $query->delete();
}
/**
* 按条件获取首条记录。
*/
public function firstBy(array $where = [], array $columns = ['*']): ?Model
{
$query = $this->query();
if (!empty($where)) {
$query->where($where);
}
return $query->first($columns);
}
/**
* 先查后更,不存在则创建。
*/
public function updateOrCreate(array $where, array $data = []): Model
{
if ($where === []) {
return $this->create($data);
}
return Db::transaction(function () use ($where, $data): Model {
$query = $this->query()->lockForUpdate();
$query->where($where);
/** @var Model|null $model */
$model = $query->first();
if ($model) {
$model->fill($data);
$model->save();
return $model->refresh();
}
try {
return $this->create(array_merge($where, $data));
} catch (UniqueConstraintViolationException $e) {
$model = $this->firstBy($where);
if (!$model) {
throw $e;
}
$model->fill($data);
$model->save();
return $model->refresh();
}
});
}
/**
* 按条件统计数量。
*/
public function countBy(array $where = []): int
{
$query = $this->query();
if (!empty($where)) {
$query->where($where);
}
return (int) $query->count();
}
/**
* 判断条件下是否存在记录。
*/
public function existsBy(array $where = []): bool
{
$query = $this->query();
if (!empty($where)) {
$query->where($where);
}
return $query->exists();
}
/**
* 分页查询。
*
* @param array $where ['字段' => 值],值为 null / '' 时会被忽略
* @param array $where 条件数组,空值会被忽略
*/
public function paginate(array $where = [], int $page = 1, int $pageSize = 10, array $columns = ['*'])
{
$query = $this->model->newQuery();
$query = $this->query();
if (!empty($where)) {
$query->where($where);

View File

@@ -2,23 +2,172 @@
namespace app\common\base;
use app\common\util\FormatHelper;
use support\Db;
use Throwable;
/**
* 业务服务层基础
* 业务服务层基础类
*
* 统一承载业务单号生成、时间获取和事务封装等通用能力。
*/
class BaseService
{
/**
* 事务封装
* 生成业务单号。
*
* 使用方式:
* $this->transaction(function () { ... });
* 适用于 biz_no / pay_no / refund_no / settle_no / notify_no / ledger_no 等场景。
* 默认使用时间前缀 + 随机数,保证可读性和基本唯一性。
*/
protected function generateNo(string $prefix = ''): string
{
$time = FormatHelper::timestamp(time(), 'YmdHis');
$rand = (string) random_int(100000, 999999);
return $prefix === '' ? $time . $rand : $prefix . $time . $rand;
}
/**
* 获取当前时间字符串。
*
* 统一返回 `Y-m-d H:i:s` 格式,便于数据库写入和日志输出。
*/
protected function now(): string
{
return FormatHelper::timestamp(time());
}
/**
* 金额格式化,单位为元。
*/
protected function formatAmount(int $amount): string
{
return FormatHelper::amount($amount);
}
/**
* 金额格式化0 时显示不限。
*/
protected function formatAmountOrUnlimited(int $amount): string
{
return FormatHelper::amountOrUnlimited($amount);
}
/**
* 次数格式化0 时显示不限。
*/
protected function formatCountOrUnlimited(int $count): string
{
return FormatHelper::countOrUnlimited($count);
}
/**
* 费率格式化,单位为百分点。
*/
protected function formatRate(int $basisPoints): string
{
return FormatHelper::rate($basisPoints);
}
/**
* 延迟格式化。
*/
protected function formatLatency(int $latencyMs): string
{
return FormatHelper::latency($latencyMs);
}
/**
* 日期格式化。
*/
protected function formatDate(mixed $value, string $emptyText = ''): string
{
return FormatHelper::date($value, $emptyText);
}
/**
* 日期时间格式化。
*/
protected function formatDateTime(mixed $value, string $emptyText = ''): string
{
return FormatHelper::dateTime($value, $emptyText);
}
/**
* JSON 文本格式化。
*/
protected function formatJson(mixed $value, string $emptyText = ''): string
{
return FormatHelper::json($value, $emptyText);
}
/**
* 映射表文本转换。
*/
protected function textFromMap(int $value, array $map, string $default = '未知'): string
{
return FormatHelper::textFromMap($value, $map, $default);
}
/**
* 接口凭证明文脱敏。
*/
protected function maskCredentialValue(string $credentialValue, bool $maskShortValue = true): string
{
return FormatHelper::maskCredentialValue($credentialValue, $maskShortValue);
}
/**
* 将模型或对象归一化成数组。
*/
protected function normalizeModel(mixed $value): ?array
{
return FormatHelper::normalizeModel($value);
}
/**
* 事务封装。
*
* 适合单次数据库事务,不包含自动重试逻辑。
*
* @param callable $callback 事务体
* @return mixed
*/
protected function transaction(callable $callback)
{
return Db::connection()->transaction(function () use ($callback) {
return Db::transaction(function () use ($callback) {
return $callback();
});
}
/**
* 支持重试的事务封装。
*
* 适合余额冻结、扣减、状态推进和幂等写入等容易发生锁冲突的场景。
*/
protected function transactionRetry(callable $callback, int $attempts = 3, int $sleepMs = 50)
{
$attempts = max(1, $attempts);
beginning:
try {
return $this->transaction($callback);
} catch (Throwable $e) {
$message = strtolower($e->getMessage());
$retryable = str_contains($message, 'deadlock')
|| str_contains($message, 'lock wait timeout')
|| str_contains($message, 'try restarting transaction');
if (--$attempts > 0 && $retryable) {
if ($sleepMs > 0) {
usleep($sleepMs * 1000);
}
goto beginning;
}
throw $e;
}
}
}