mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-04-21 09:24:33 +08:00
更新基础架构
This commit is contained in:
42
app/common/base/BaseController.php
Normal file
42
app/common/base/BaseController.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\base;
|
||||
|
||||
use support\Response;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
* - 提供统一的 success/fail 响应封装
|
||||
*
|
||||
* 约定:
|
||||
* - 控制器统一通过 $this->request->* 获取请求数据
|
||||
* - 为避免每个控制器构造函数重复注入 Request,本类通过 __get('request') 返回当前请求对象
|
||||
*/
|
||||
abstract class BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*/
|
||||
protected function success(mixed $data = null, string $message = 'success', int $code = 200): Response
|
||||
{
|
||||
return json([
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*/
|
||||
protected function fail(string $message = 'fail', int $code = 500, mixed $data = null): Response
|
||||
{
|
||||
return json([
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
164
app/common/base/BaseDao.php
Normal file
164
app/common/base/BaseDao.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
101
app/common/base/BaseModel.php
Normal file
101
app/common/base/BaseModel.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\base;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* 模型基础类
|
||||
* - 统一禁用时间戳(如需要可在子类开启)
|
||||
* - 提供常用查询作用域和便捷方法
|
||||
*/
|
||||
abstract class BaseModel extends Model
|
||||
{
|
||||
/**
|
||||
* 禁用时间戳(默认)
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* 允许批量赋值的字段(子类可覆盖)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
58
app/common/base/BaseRepository.php
Normal file
58
app/common/base/BaseRepository.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\base;
|
||||
|
||||
/**
|
||||
* 仓库基础类
|
||||
* - 支持注入 DAO 依赖
|
||||
* - 通过魔术方法代理 DAO 的方法调用
|
||||
* - 提供通用的数据访问封装
|
||||
*/
|
||||
abstract class BaseRepository
|
||||
{
|
||||
/**
|
||||
* DAO 实例(可选,子类通过构造函数注入)
|
||||
*/
|
||||
protected ?BaseDao $dao = null;
|
||||
|
||||
/**
|
||||
* 构造函数,子类可注入 DAO
|
||||
*/
|
||||
public function __construct(?BaseDao $dao = null)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法:代理 DAO 的方法调用
|
||||
* 如果仓库自身没有该方法,且存在 DAO 实例,则调用 DAO 的对应方法
|
||||
*/
|
||||
public function __call(string $method, array $arguments)
|
||||
{
|
||||
if ($this->dao && method_exists($this->dao, $method)) {
|
||||
return $this->dao->{$method}(...$arguments);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
app/common/base/BaseService.php
Normal file
56
app/common/base/BaseService.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\base;
|
||||
|
||||
use support\Log;
|
||||
|
||||
/**
|
||||
* 服务基础类
|
||||
* - 提供日志记录能力
|
||||
* - 预留事务、事件发布等扩展点
|
||||
*/
|
||||
abstract class BaseService
|
||||
{
|
||||
/**
|
||||
* 记录日志
|
||||
*/
|
||||
protected function log(string $level, string $message, array $context = []): void
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
app/common/middleware/Cors.php
Normal file
34
app/common/middleware/Cors.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\middleware;
|
||||
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
/**
|
||||
* 全局跨域中间件
|
||||
* 处理前后端分离项目中的跨域请求问题
|
||||
*/
|
||||
class Cors implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* 处理请求
|
||||
* @param Request $request 请求对象
|
||||
* @param callable $handler 下一个中间件处理函数
|
||||
* @return Response 响应对象
|
||||
*/
|
||||
public function process(Request $request, callable $handler): Response
|
||||
{
|
||||
$response = strtoupper($request->method()) === 'OPTIONS' ? response('', 204) : $handler($request);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace app\middleware;
|
||||
namespace app\common\middleware;
|
||||
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use support\Request;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return <<<EOF
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
iframe {
|
||||
border: none;
|
||||
overflow: scroll;
|
||||
}
|
||||
</style>
|
||||
<iframe
|
||||
src="https://www.workerman.net/wellcome"
|
||||
width="100%"
|
||||
height="100%"
|
||||
allow="clipboard-write"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups"
|
||||
></iframe>
|
||||
EOF;
|
||||
}
|
||||
|
||||
public function view(Request $request)
|
||||
{
|
||||
return view('index/view', ['name' => 'webman']);
|
||||
}
|
||||
|
||||
public function json(Request $request)
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
}
|
||||
16
app/exceptions/AuthFailedException.php
Normal file
16
app/exceptions/AuthFailedException.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\exceptions;
|
||||
|
||||
/**
|
||||
* 认证失败(账号或密码错误)
|
||||
*/
|
||||
class AuthFailedException extends BusinessException
|
||||
{
|
||||
public function __construct(string $message = '账号或者密码错误', int $bizCode = 400, mixed $data = null)
|
||||
{
|
||||
parent::__construct($message, $bizCode, $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
49
app/exceptions/BusinessException.php
Normal file
49
app/exceptions/BusinessException.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\exceptions;
|
||||
|
||||
/**
|
||||
* 业务基础异常
|
||||
*
|
||||
* 说明:
|
||||
* - 继承 webman 的 BusinessException,让框架自动捕获并渲染
|
||||
* - 重写 render() 以对齐前端期望字段:code/message/data
|
||||
*/
|
||||
class BusinessException extends \support\exception\BusinessException
|
||||
{
|
||||
public function __construct(string $message = '', int $bizCode = 500, array $data = [])
|
||||
{
|
||||
parent::__construct($message, $bizCode);
|
||||
$this->data($data);
|
||||
}
|
||||
|
||||
public function getBizCode(): int
|
||||
{
|
||||
return (int)$this->getCode();
|
||||
}
|
||||
|
||||
// 保持与 webman BusinessException 方法签名兼容
|
||||
public function getData(): array
|
||||
{
|
||||
return parent::getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义渲染
|
||||
* - json 请求:返回 {code,message,data}
|
||||
* - 非 json:返回文本
|
||||
*/
|
||||
public function render(\Webman\Http\Request $request): ?\Webman\Http\Response
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return json([
|
||||
'code' => $this->getBizCode() ?: 500,
|
||||
'message' => $this->getMessage(),
|
||||
'data' => $this->getData(),
|
||||
]);
|
||||
}
|
||||
return new \Webman\Http\Response(200, [], $this->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
app/exceptions/ForbiddenException.php
Normal file
16
app/exceptions/ForbiddenException.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\exceptions;
|
||||
|
||||
/**
|
||||
* 权限不足
|
||||
*/
|
||||
class ForbiddenException extends BusinessException
|
||||
{
|
||||
public function __construct(string $message = '无访问权限', int $bizCode = 403, mixed $data = null)
|
||||
{
|
||||
parent::__construct($message, $bizCode, $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
app/exceptions/UnauthorizedException.php
Normal file
16
app/exceptions/UnauthorizedException.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\exceptions;
|
||||
|
||||
/**
|
||||
* 未认证或登录过期
|
||||
*/
|
||||
class UnauthorizedException extends BusinessException
|
||||
{
|
||||
public function __construct(string $message = '登录状态已过期', int $bizCode = 401, mixed $data = null)
|
||||
{
|
||||
parent::__construct($message, $bizCode, $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
app/exceptions/ValidationException.php
Normal file
16
app/exceptions/ValidationException.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\exceptions;
|
||||
|
||||
/**
|
||||
* 参数校验异常
|
||||
*/
|
||||
class ValidationException extends BusinessException
|
||||
{
|
||||
public function __construct(string $message = '参数校验失败', int $bizCode = 422, mixed $data = null)
|
||||
{
|
||||
parent::__construct($message, $bizCode, $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Here is your custom functions.
|
||||
*/
|
||||
48
app/http/admin/controller/AuthController.php
Normal file
48
app/http/admin/controller/AuthController.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace app\http\admin\controller;
|
||||
|
||||
use app\common\base\BaseController;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use app\services\auth\AuthService;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
protected AuthService $authService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 管理后台登录
|
||||
*/
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
$username = (string)$request->post('username', '');
|
||||
$password = (string)$request->post('password', '');
|
||||
// 前端有本地验证码,这里暂不做服务端校验,仅预留字段
|
||||
$verifyCode = $request->post('verifyCode');
|
||||
|
||||
if ($username === '' || $password === '') {
|
||||
return $this->fail('账号或密码不能为空', 400);
|
||||
}
|
||||
|
||||
$token = $this->authService->login($username, $password, $verifyCode);
|
||||
|
||||
return $this->success(['token' => $token]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*/
|
||||
public function getUserInfo(Request $request): Response
|
||||
{
|
||||
// 前端在 Authorization 中直接传 token
|
||||
$token = (string)$request->header('authorization', '');
|
||||
$id = $request->get('id');
|
||||
|
||||
$data = $this->authService->getUserInfo($token, $id);
|
||||
|
||||
return $this->success($data);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class Test extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'test';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
}
|
||||
207
app/repositories/AdminUserRepository.php
Normal file
207
app/repositories/AdminUserRepository.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace app\repositories;
|
||||
|
||||
use app\common\base\BaseRepository;
|
||||
use app\common\base\BaseDao;
|
||||
|
||||
/**
|
||||
* 管理后台用户仓库(当前阶段使用内存数据模拟)
|
||||
*
|
||||
* 后续接入数据库时:
|
||||
* 1. 创建 AdminUserDao 继承 BaseDao
|
||||
* 2. 在构造函数中注入:public function __construct(AdminUserDao $dao) { parent::__construct($dao); }
|
||||
* 3. 将内存数据方法改为调用 $this->dao 的方法
|
||||
*/
|
||||
class AdminUserRepository extends BaseRepository
|
||||
{
|
||||
/**
|
||||
* 构造函数:支持注入 DAO(当前阶段为可选)
|
||||
*/
|
||||
public function __construct(?BaseDao $dao = null)
|
||||
{
|
||||
parent::__construct($dao);
|
||||
}
|
||||
/**
|
||||
* 模拟账户数据(对齐前端 mock accountData)
|
||||
*/
|
||||
protected function accounts(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => 1,
|
||||
'deptId' => '100',
|
||||
'deptName' => '研发部门',
|
||||
'userName' => 'admin',
|
||||
'nickName' => '超级管理员',
|
||||
'email' => '2547096351@qq.com',
|
||||
'phone' => '15888888888',
|
||||
'sex' => 1,
|
||||
'avatar' => 'https://ooo.0x0.ooo/2025/04/10/O0dG7r.jpg',
|
||||
'status' => 1,
|
||||
'description' => '系统初始用户',
|
||||
'roles' => ['admin'],
|
||||
'loginIp' => '0:0:0:0:0:0:0:1',
|
||||
'loginDate' => '2025-03-31 10:30:59',
|
||||
'createBy' => 'admin',
|
||||
'createTime' => '2024-03-19 11:21:01',
|
||||
'updateBy' => null,
|
||||
'updateTime' => null,
|
||||
'admin' => true,
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'deptId' => '100010101',
|
||||
'deptName' => '研发部门',
|
||||
'userName' => 'common',
|
||||
'nickName' => '普通用户',
|
||||
'email' => '2547096351@qq.com',
|
||||
'phone' => '15222222222',
|
||||
'sex' => 0,
|
||||
'avatar' => 'https://ooo.0x0.ooo/2025/04/10/O0ddJI.jpg',
|
||||
'status' => 1,
|
||||
'description' => 'UI组用户',
|
||||
'roles' => ['common'],
|
||||
'loginIp' => '0:0:0:0:0:0:0:1',
|
||||
'loginDate' => '2025-03-31 10:30:59',
|
||||
'createBy' => 'admin',
|
||||
'createTime' => '2024-03-19 11:21:01',
|
||||
'updateBy' => null,
|
||||
'updateTime' => null,
|
||||
'admin' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟角色数据(对齐前端 mock roleData)
|
||||
*/
|
||||
protected function roles(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => '超级管理员',
|
||||
'code' => 'admin',
|
||||
'sort' => 1,
|
||||
'status' => 1,
|
||||
'admin' => true,
|
||||
'description' => '默认角色,超级管理员,上帝角色',
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'name' => '普通员工',
|
||||
'code' => 'common',
|
||||
'sort' => 2,
|
||||
'status' => 1,
|
||||
'admin' => false,
|
||||
'description' => '负责一些基础功能',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟权限数据(对齐前端 mock permissionData)
|
||||
*/
|
||||
protected function permissions(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'meta' => [
|
||||
'roles' => ['admin'],
|
||||
'permission' => 'sys:btn:add',
|
||||
],
|
||||
],
|
||||
[
|
||||
'meta' => [
|
||||
'roles' => ['admin'],
|
||||
'permission' => 'sys:btn:edit',
|
||||
],
|
||||
],
|
||||
[
|
||||
'meta' => [
|
||||
'roles' => ['admin'],
|
||||
'permission' => 'sys:btn:delete',
|
||||
],
|
||||
],
|
||||
[
|
||||
'meta' => [
|
||||
'roles' => ['admin', 'common'],
|
||||
'permission' => 'common:btn:add',
|
||||
],
|
||||
],
|
||||
[
|
||||
'meta' => [
|
||||
'roles' => ['admin', 'common'],
|
||||
'permission' => 'common:btn:edit',
|
||||
],
|
||||
],
|
||||
[
|
||||
'meta' => [
|
||||
'roles' => ['admin', 'common'],
|
||||
'permission' => 'common:btn:delete',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function findByUsername(string $username): ?array
|
||||
{
|
||||
foreach ($this->accounts() as $account) {
|
||||
if ($account['userName'] === $username) {
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
foreach ($this->accounts() as $account) {
|
||||
if ($account['id'] === $id) {
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRoleInfoByCodes(array $codes): array
|
||||
{
|
||||
if (!$codes) {
|
||||
return [];
|
||||
}
|
||||
$roles = [];
|
||||
foreach ($this->roles() as $role) {
|
||||
if (in_array($role['code'], $codes, true)) {
|
||||
$roles[] = $role;
|
||||
}
|
||||
}
|
||||
return $roles;
|
||||
}
|
||||
|
||||
public function getPermissionsByRoleCodes(array $codes): array
|
||||
{
|
||||
if (!$codes) {
|
||||
return [];
|
||||
}
|
||||
$permissions = [];
|
||||
foreach ($this->permissions() as $item) {
|
||||
$meta = $item['meta'] ?? [];
|
||||
$roles = $meta['roles'] ?? [];
|
||||
$permission = $meta['permission'] ?? null;
|
||||
if (!$permission) {
|
||||
continue;
|
||||
}
|
||||
foreach ($codes as $code) {
|
||||
if (in_array($code, $roles, true)) {
|
||||
$permissions[] = $permission;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 去重
|
||||
return array_values(array_unique($permissions));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
app/routes/admin.php
Normal file
14
app/routes/admin.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 管理后台路由定义
|
||||
*/
|
||||
|
||||
use Webman\Route;
|
||||
use app\http\admin\controller\AuthController;
|
||||
|
||||
Route::group('/admin', function () {
|
||||
// 登录相关
|
||||
Route::post('/mock/login', [AuthController::class, 'login']);
|
||||
Route::get('/mock/user/getUserInfo', [AuthController::class, 'getUserInfo']);
|
||||
});
|
||||
0
app/routes/api.php
Normal file
0
app/routes/api.php
Normal file
0
app/routes/user.php
Normal file
0
app/routes/user.php
Normal file
104
app/services/auth/AuthService.php
Normal file
104
app/services/auth/AuthService.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace app\services\auth;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exceptions\AuthFailedException;
|
||||
use app\exceptions\UnauthorizedException;
|
||||
use app\repositories\AdminUserRepository;
|
||||
use support\Redis;
|
||||
|
||||
class AuthService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected AdminUserRepository $userRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录,返回 JWT token
|
||||
*/
|
||||
public function login(string $username, string $password, $verifyCode = null): string
|
||||
{
|
||||
$user = $this->userRepository->findByUsername($username);
|
||||
if (!$user) {
|
||||
throw new AuthFailedException();
|
||||
}
|
||||
|
||||
// 当前阶段使用明文模拟密码校验
|
||||
if ($password !== '123456') {
|
||||
throw new AuthFailedException();
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'uid' => $user['id'],
|
||||
'username' => $user['userName'],
|
||||
'roles' => $user['roles'] ?? [],
|
||||
];
|
||||
|
||||
$token = JwtService::generateToken($payload);
|
||||
|
||||
// 写入 Redis 会话,key 使用 token,方便快速失效
|
||||
$ttl = JwtService::getTtl();
|
||||
$key = 'mpay:auth:token:' . $token;
|
||||
$redis = Redis::connection('default');
|
||||
$redis->setex($key, $ttl, json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 token 获取用户信息(对齐前端 mock 返回结构)
|
||||
*/
|
||||
public function getUserInfo(string $token, $id = null): array
|
||||
{
|
||||
if ($token === '') {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$redis = Redis::connection('default');
|
||||
$key = 'mpay:auth:token:' . $token;
|
||||
$session = $redis->get($key);
|
||||
if (!$session) {
|
||||
// 尝试从 JWT 解出(例如服务重启后 Redis 丢失的情况)
|
||||
$payload = JwtService::parseToken($token);
|
||||
} else {
|
||||
$payload = json_decode($session, true) ?: [];
|
||||
}
|
||||
|
||||
if (empty($payload['uid']) && empty($payload['username'])) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
// 对齐 mock:如果有 id 参数则按 id 查,否则用 payload 中 uid 查
|
||||
if ($id !== null && $id !== '') {
|
||||
$user = $this->userRepository->findById((int)$id);
|
||||
} else {
|
||||
$user = $this->userRepository->findById((int)($payload['uid'] ?? 0));
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
// 角色信息
|
||||
$roleInfo = $this->userRepository->getRoleInfoByCodes($user['roles'] ?? []);
|
||||
$user['roles'] = $roleInfo;
|
||||
$roleCodes = array_map(static fn($item) => $item['code'], $roleInfo);
|
||||
|
||||
// 权限信息
|
||||
if (in_array('admin', $roleCodes, true)) {
|
||||
$permissions = ['*:*:*'];
|
||||
} else {
|
||||
$permissions = $this->userRepository->getPermissionsByRoleCodes($roleCodes);
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => $user,
|
||||
'roles' => $roleCodes,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
52
app/services/auth/JwtService.php
Normal file
52
app/services/auth/JwtService.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\services\auth;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
class JwtService
|
||||
{
|
||||
/**
|
||||
* 生成 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user