初始化本地仓库
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api;
|
||||
|
||||
use app\model\member\Member;
|
||||
use app\service\api\member\MemberService;
|
||||
use app\service\admin\member\MemberAuthService;
|
||||
use core\cache\CacheService;
|
||||
use core\exception\handler\ForbiddenHttpException;
|
||||
use core\jwt\JwtToken;
|
||||
use core\logger\Logger;
|
||||
use madong\swagger\attribute\Permission;
|
||||
|
||||
/**
|
||||
* 会员权限类(向后兼容的封装)
|
||||
* 注意:这个类是为了保持向后兼容性而存在
|
||||
* 新的代码应该使用 CurrentMenber 类(包含权限功能)
|
||||
*/
|
||||
final class CurrentMember
|
||||
{
|
||||
// 缓存键前缀
|
||||
public const CACHE_PREFIX = 'member_user';
|
||||
|
||||
// 缓存过期时间(秒)
|
||||
public const CACHE_EXPIRE = 3600;
|
||||
|
||||
private MemberService $service;
|
||||
private MemberAuthService $authService;
|
||||
private CacheService $cache;
|
||||
|
||||
public function __construct(
|
||||
MemberService $service,
|
||||
MemberAuthService $authService,
|
||||
CacheService $cache
|
||||
)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->authService = $authService;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成会员缓存键
|
||||
*
|
||||
* @param int|string $uid 会员ID
|
||||
*
|
||||
* @return string 缓存键
|
||||
*/
|
||||
public static function generateCacheKey(int|string $uid): string
|
||||
{
|
||||
return self::CACHE_PREFIX . '_' . $uid;
|
||||
}
|
||||
|
||||
public function user(bool $toArray = false): null|Member|array
|
||||
{
|
||||
if (!$this->getToken()) {
|
||||
return null;
|
||||
}
|
||||
$uid = $this->id();
|
||||
$cacheKey = self::generateCacheKey($uid);
|
||||
|
||||
// 使用缓存服务获取会员信息
|
||||
$member = $this->cache->remember($cacheKey, function () use ($uid) {
|
||||
return $this->service->get($uid, ['*'], ['level', 'tags'], '', []);
|
||||
}, self::CACHE_EXPIRE);
|
||||
|
||||
return $toArray && $member ? $member->toArray() : $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除会员缓存
|
||||
*
|
||||
* @param int|string|null $uid 会员ID,默认清除当前用户缓存
|
||||
*/
|
||||
public function clearCache(int|string|null $uid = null): void
|
||||
{
|
||||
$uid = $uid ?? $this->id();
|
||||
if ($uid) {
|
||||
$cacheKey = self::generateCacheKey($uid);
|
||||
$this->cache->delete($cacheKey);
|
||||
|
||||
// 同时清除权限缓存
|
||||
$permissionCacheKey = $cacheKey . '_permissions';
|
||||
$this->cache->delete($permissionCacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): array
|
||||
{
|
||||
if (!$this->getToken()) {
|
||||
Logger::debug("当前会员无有效 Token,无法刷新");
|
||||
return [];
|
||||
}
|
||||
return (new JwtToken())->refresh()->toArray();
|
||||
}
|
||||
|
||||
public function id(): int|string
|
||||
{
|
||||
$token = $this->getToken();
|
||||
if (!$token) {
|
||||
return 0;
|
||||
}
|
||||
$mid = (new JwtToken())->id();
|
||||
if ($mid === null) {
|
||||
return 0;
|
||||
}
|
||||
return $mid ?? 0;
|
||||
}
|
||||
|
||||
public function getToken(): ?string
|
||||
{
|
||||
$request = request();
|
||||
if (empty($request)) {
|
||||
return null;
|
||||
}
|
||||
$tokenName = config('core.jwt.app.token_name', 'Authorization');
|
||||
$authorization = $request->header($tokenName);
|
||||
if (empty($authorization) || $authorization === 'undefined') {
|
||||
$authorization = $request->get('token');
|
||||
}
|
||||
if (!$authorization || $authorization === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
if (count(explode(' ', $authorization)) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$type, $token] = explode(' ', $authorization);
|
||||
|
||||
if ($type !== 'Bearer') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$token || $token === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成新令牌
|
||||
* @param array $userInfo 用户信息
|
||||
* @param string $type 类型
|
||||
* @return array 令牌信息
|
||||
*/
|
||||
public function generateToken(array $userInfo, string $type = 'api'): array
|
||||
{
|
||||
$jwt = new JwtToken();
|
||||
$tokenObj = $jwt->generate((string)$this->id(), $type, $userInfo);
|
||||
return [
|
||||
'access_token' => $tokenObj->accessToken,
|
||||
'refresh_token' => $tokenObj->refreshToken,
|
||||
'expires_in' => $tokenObj->expiresIn,
|
||||
'expires_time' => time() + $tokenObj->expiresIn
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出(将令牌加入黑名单)
|
||||
* @param string|null $token 令牌,默认使用当前请求的令牌
|
||||
* @return bool 是否成功
|
||||
*/
|
||||
public function logout(?string $token = null): bool
|
||||
{
|
||||
$token = $token ?? $this->getToken();
|
||||
if (!$token) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
(new JwtToken())->logout($token);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Logger::error("登出失败: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取令牌负载信息
|
||||
* @return array 负载信息
|
||||
*/
|
||||
public function getPayload(): array
|
||||
{
|
||||
try {
|
||||
$jwt = new JwtToken();
|
||||
// 使用 getPayloadFromRequest() 方法获取负载信息
|
||||
$payload = $jwt->getPayloadFromRequest();
|
||||
return $payload['ext'] ?? $payload;
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员所有菜单权限码
|
||||
*
|
||||
* @return array 权限码数组
|
||||
*/
|
||||
public function getPermissions(): array
|
||||
{
|
||||
$member = $this->user();
|
||||
if (!$member) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$uid = $this->id();
|
||||
$cacheKey = self::generateCacheKey($uid) . '_permissions';
|
||||
|
||||
// 使用缓存服务获取权限信息
|
||||
return $this->cache->remember($cacheKey, function () use ($member) {
|
||||
// 通过标签获取菜单权限码
|
||||
$permissions = [];
|
||||
$tags = $member->tags()->with('permissions')->where('enabled', 1)->get();
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
foreach ($tag->permissions as $permission) {
|
||||
if (!empty($permission->code)) {
|
||||
$permissions[] = $permission->code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($permissions);
|
||||
}, self::CACHE_EXPIRE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查会员是否有权限
|
||||
*
|
||||
* @param string|array $codes 权限码,可以是字符串或数组
|
||||
* @param string $operation 操作类型,支持 'and' 或 'or'
|
||||
*
|
||||
* @return bool 是否有权限
|
||||
*/
|
||||
public function hasPermission(string|array $codes, string $operation = 'and'): bool
|
||||
{
|
||||
// 如果是超级会员(可以根据需求扩展,例如会员等级等),直接返回true
|
||||
// 暂时不实现超级会员逻辑
|
||||
|
||||
$codes = is_array($codes) ? $codes : [$codes];
|
||||
$memberPermissions = $this->getPermissions();
|
||||
|
||||
$operation = strtolower($operation);
|
||||
|
||||
if ($operation === Permission::OPERATION_AND) {
|
||||
// AND模式:所有权限码都必须有
|
||||
foreach ($codes as $code) {
|
||||
if (!in_array($code, $memberPermissions)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if ($operation === Permission::OPERATION_OR) {
|
||||
// OR模式:至少有一个权限码
|
||||
foreach ($codes as $code) {
|
||||
if (in_array($code, $memberPermissions)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("不支持的操作类型: {$operation}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查会员是否有权限,没有权限时抛出异常
|
||||
* 注意:这个功能暂时不套用中间件,仅作为工具方法使用
|
||||
*
|
||||
* @param string|array $codes 权限码,可以是字符串或数组
|
||||
* @param string $operation 操作类型,支持 'and' 或 'or'
|
||||
*
|
||||
* @throws ForbiddenHttpException 没有权限时抛出异常
|
||||
*/
|
||||
public function checkPermission(string|array $codes, string $operation = 'and'): void
|
||||
{
|
||||
if (!$this->hasPermission($codes, $operation)) {
|
||||
$codes = is_array($codes) ? $codes : [$codes];
|
||||
$codesStr = implode($operation === 'and' ? ',' : '或', $codes);
|
||||
throw new ForbiddenHttpException("缺少权限: {$codesStr}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员的所有菜单(基于标签权限)
|
||||
*
|
||||
* @param bool $includeEmptyCode 是否包含权限码为空的菜单(不需要标签权限的公共菜单)
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getMenus(bool $includeEmptyCode = false): ?\Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
$uid = $this->id();
|
||||
if (!$uid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->authService->getMenusByMemberTags($uid, $includeEmptyCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use OpenApi\Annotations as OA;
|
||||
use app\controller\OpenApiController;
|
||||
use Webman\Route;
|
||||
use WebmanTech\Swagger\DTO\ConfigOpenapiDocDTO;
|
||||
use WebmanTech\Swagger\Integrations\RouteRegister;
|
||||
use WebmanTech\Swagger\RouteAnnotation\Reader;
|
||||
|
||||
/**
|
||||
* 注册API路由
|
||||
*/
|
||||
Route::group('/api', function () {
|
||||
$openapiDocConfig = [
|
||||
'format' => 'json',
|
||||
'scan_path' => [
|
||||
base_path('app/api'),
|
||||
],
|
||||
'modify' => function (OA\OpenApi $openapi) {
|
||||
$openapi->info->title = config('app.name') . ' API';
|
||||
$openapi->info->version = '1.0.0';
|
||||
$openapi->servers = [
|
||||
new OA\Server([
|
||||
'url' => '/api',
|
||||
'description' => request()->host(),
|
||||
]),
|
||||
];
|
||||
/** @phpstan-ignore-next-line */
|
||||
if (!$openapi->components instanceof OA\Components) {
|
||||
$openapi->components = new OA\Components([]);
|
||||
}
|
||||
$openapi->components->securitySchemes = [
|
||||
new OA\SecurityScheme([
|
||||
'securityScheme' => 'api_key',
|
||||
'type' => 'apiKey',
|
||||
'name' => config('core.jwt.app.token_name', 'Authorization'),
|
||||
'in' => 'header',
|
||||
]),
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
$openApiController = new OpenApiController();
|
||||
Route::get('/openapi', static fn () => $openApiController->index('/api/openapi.json'));
|
||||
Route::get('/openapi.json', static fn () => $openApiController->document($openapiDocConfig));
|
||||
|
||||
$config = ConfigOpenapiDocDTO::fromConfig($openapiDocConfig);
|
||||
RouteRegister::create()->register((new Reader())->getData($config->getScanSources()));
|
||||
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use core\base\BaseController;
|
||||
use core\tool\Json;
|
||||
use madong\helper\Tree;
|
||||
|
||||
/**
|
||||
* 基类控制器继承的类
|
||||
*
|
||||
* @author Mr.April
|
||||
* @since 1.0
|
||||
*/
|
||||
class Base extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
protected function initialize(): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化树
|
||||
*
|
||||
* @param $items
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatTree($items): \support\Response
|
||||
{
|
||||
$format_items = [];
|
||||
foreach ($items as $item) {
|
||||
$format_items[] = [
|
||||
'name' => $item->title ?? $item->name ?? $item->id,
|
||||
'value' => (string)$item->id,
|
||||
'id' => $item->id,
|
||||
'pid' => $item->pid,
|
||||
];
|
||||
}
|
||||
$tree = new Tree($format_items);
|
||||
return Json::success('ok', $tree->getTree());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化表格树
|
||||
*
|
||||
* @param $data
|
||||
* @param $total
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatTableTree($data, $total): \support\Response
|
||||
{
|
||||
$tree = new Tree($data->toArray());
|
||||
$items = $tree->getTree();
|
||||
return Json::success('ok', $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化下拉列表
|
||||
*
|
||||
* @param $items
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatSelect($items): \support\Response
|
||||
{
|
||||
$formatted_items = [];
|
||||
foreach ($items as $item) {
|
||||
$formatted_items[] = [
|
||||
'label' => $item->title ?? $item->name ?? $item->real_name ?? $item->id,
|
||||
'value' => $item->id,
|
||||
];
|
||||
}
|
||||
return Json::success('ok', $formatted_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用格式化
|
||||
*
|
||||
* @param $items
|
||||
* @param $total
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatNormal($items, $total): \support\Response
|
||||
{
|
||||
return Json::success('ok', compact('items', 'total'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\auth;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\validate\auth\LoginValidate;
|
||||
use app\api\validate\auth\RegisterValidate;
|
||||
use app\service\api\auth\AuthService;
|
||||
use core\captcha\Captcha;
|
||||
use core\tool\Json;
|
||||
use Exception;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Container;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
*/
|
||||
#[OA\Tag(name: '认证模块')]
|
||||
final class AuthController extends Base
|
||||
{
|
||||
public function __construct(AuthService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/auth/login',
|
||||
summary: '用户登录',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['username', 'password'],
|
||||
properties: [
|
||||
new OA\Property(property: 'username', description: '用户名/手机号/邮箱', type: 'string'),
|
||||
new OA\Property(property: 'password', description: '密码', type: 'string'),
|
||||
new OA\Property(property: 'captcha', description: '验证码', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '登录成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
new OA\Response(response: 401, description: '登录失败'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: ['token' => 'string'])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
try {
|
||||
/** @var LoginValidate $validate */
|
||||
$validate = Container::make(LoginValidate::class);
|
||||
if (!$validate->scene('login')->check($request->post())) {
|
||||
throw new Exception($validate->getError());
|
||||
}
|
||||
$data = $request->all();
|
||||
$result = $this->service->login($data);
|
||||
return Json::success($result);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机验证码登录
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/login/mobile',
|
||||
summary: '手机验证码登录',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['mobile', 'code'],
|
||||
properties: [
|
||||
new OA\Property(property: 'mobile', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'code', description: '验证码', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证'],
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: ['token' => 'string'])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function loginWithMobile(Request $request): Response
|
||||
{
|
||||
$data = $request->post();
|
||||
$result = $this->service->loginWithMobile($data);
|
||||
return Json::success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户退出登录
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/logout',
|
||||
summary: '用户退出登录',
|
||||
tags: ['认证'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '退出成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
/** @Route(methods=["POST"], path="/auth/logout") */
|
||||
public function logout(): Response
|
||||
{
|
||||
$result = $this->service->logout();
|
||||
return Json::success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/register',
|
||||
summary: '用户注册',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['username', 'password'],
|
||||
properties: [
|
||||
new OA\Property(property: 'username', description: '用户名', type: 'string'),
|
||||
new OA\Property(property: 'password', description: '密码', type: 'string'),
|
||||
new OA\Property(property: 'email', description: '邮箱', type: 'string'),
|
||||
new OA\Property(property: 'mobile', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'pid', description: '推荐人ID', type: 'integer'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证'],
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function register(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$validate = Container::make(RegisterValidate::class);
|
||||
if (!$validate->scene('register')->check($request->post())) {
|
||||
throw new Exception($validate->getError());
|
||||
}
|
||||
$captcha = Container::make(Captcha::class);
|
||||
if (!$captcha->check($request->input('captcha_key', ''), $request->input('captcha_code', ''))) {
|
||||
throw new Exception('验证码错误');
|
||||
}
|
||||
$data = $request->all();
|
||||
$result = $this->service->register($data);
|
||||
return Json::success($result);
|
||||
} catch (Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号注册
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/register/mobile',
|
||||
summary: '手机号注册',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['mobile', 'code', 'password'],
|
||||
properties: [
|
||||
new OA\Property(property: 'mobile', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'code', description: '验证码', type: 'string'),
|
||||
new OA\Property(property: 'password', description: '密码', type: 'string'),
|
||||
new OA\Property(property: 'pid', description: '推荐人ID', type: 'integer'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function registerWithMobile(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
$result = $this->service->registerWithMobile($data);
|
||||
return Json::success($result);
|
||||
} catch (Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/bind',
|
||||
summary: '绑定手机号',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['mobile', 'code'],
|
||||
properties: [
|
||||
new OA\Property(property: 'mobile', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'code', description: '验证码', type: 'string'),
|
||||
new OA\Property(property: 'pid', description: '推荐人ID', type: 'integer'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function bindMobile(Request $request): Response
|
||||
{
|
||||
$data = $request->post();
|
||||
$result = $this->service->bindMobile($data);
|
||||
return Json::success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱和验证码(忘记密码第一步)
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/verify-email',
|
||||
summary: '验证邮箱和验证码',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['email', 'code', 'captcha_key'],
|
||||
properties: [
|
||||
new OA\Property(property: 'email', description: '邮箱地址', type: 'string'),
|
||||
new OA\Property(property: 'code', description: '邮箱验证码', type: 'string'),
|
||||
new OA\Property(property: 'captcha_key', description: '验证码key', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function verifyEmail(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
$result = $this->service->verifyEmail($data);
|
||||
return Json::success($result);
|
||||
} catch (Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/send-email-code',
|
||||
summary: '发送邮箱验证码',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['email', 'captcha_key', 'captcha_code'],
|
||||
properties: [
|
||||
new OA\Property(property: 'email', description: '邮箱地址', type: 'string'),
|
||||
new OA\Property(property: 'captcha_key', description: '图片验证码key', type: 'string'),
|
||||
new OA\Property(property: 'captcha_code', description: '图片验证码', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function sendEmailCode(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$email = $request->post('email');
|
||||
$captchaKey = $request->post('captcha_key');
|
||||
$captchaCode = $request->post('captcha_code');
|
||||
|
||||
// 验证图片验证码
|
||||
if (empty($captchaKey) || empty($captchaCode)) {
|
||||
throw new Exception('图片验证码不能为空', 400);
|
||||
}
|
||||
|
||||
$captcha = new Captcha();
|
||||
if (!$captcha->check($captchaKey, $captchaCode)) {
|
||||
throw new Exception('图片验证码错误', 400);
|
||||
}
|
||||
$this->service->sendEmailCode($email);
|
||||
return Json::success('success', []);
|
||||
} catch (Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码(忘记密码第二步)
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/forget-password',
|
||||
summary: '重置密码',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['email', 'code', 'new_password'],
|
||||
properties: [
|
||||
new OA\Property(property: 'email', description: '邮箱地址', type: 'string'),
|
||||
new OA\Property(property: 'code', description: '邮箱验证码', type: 'string'),
|
||||
new OA\Property(property: 'new_password', description: '新密码', type: 'string'),
|
||||
new OA\Property(property: 'confirm_password', description: '确认密码', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function forgetPassword(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
$this->service->forgetPassword($data);
|
||||
return Json::success('操作成功', []);
|
||||
} catch (Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 token
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/auth/refresh',
|
||||
summary: '刷新 token',
|
||||
tags: ['认证'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '刷新成功'),
|
||||
new OA\Response(response: 401, description: '刷新失败'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: ['access_token' => 'string', 'refresh_token' => 'string'])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function refresh(): Response
|
||||
{
|
||||
try {
|
||||
$jwt = new \core\jwt\JwtToken();
|
||||
$token = $jwt->refresh();
|
||||
return Json::success('ok', [
|
||||
'access_token' => $token->accessToken,
|
||||
'refresh_token' => $token->refreshToken,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage(), [], 401);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\auth;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\validate\auth\ThirdPartyAuthValidate;
|
||||
use app\service\api\auth\ThirdPartyAuthService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 第三方认证控制器
|
||||
*/
|
||||
class ThirdPartyAuthController extends Base
|
||||
{
|
||||
public function __construct(ThirdPartyAuthService $service, ThirdPartyAuthValidate $validate)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->validate = $validate;
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Get(
|
||||
path: '/auth/qq/callback',
|
||||
summary: 'QQ回调',
|
||||
tags: ['第三方认证'],
|
||||
parameters: [
|
||||
new \OpenApi\Attributes\Parameter(name: 'scene', description: '场景ID', in: 'query', required: true),
|
||||
new \OpenApi\Attributes\Parameter(name: 'code', description: '授权码', in: 'query', required: true),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function qqCallback(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene');
|
||||
$code = $request->input('code');
|
||||
|
||||
if (empty($scene) || empty($code)) {
|
||||
throw new \Exception('参数错误', 400);
|
||||
}
|
||||
|
||||
$this->service->handleQqCallback($scene, $code);
|
||||
return Json::success('绑定成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Get(
|
||||
path: '/auth/wechat/callback',
|
||||
summary: '微信回调',
|
||||
tags: ['第三方认证'],
|
||||
parameters: [
|
||||
new \OpenApi\Attributes\Parameter(name: 'scene', description: '场景ID', in: 'query', required: true),
|
||||
new \OpenApi\Attributes\Parameter(name: 'code', description: '授权码', in: 'query', required: true),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function wechatCallback(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene');
|
||||
$code = $request->input('code');
|
||||
|
||||
if (empty($scene) || empty($code)) {
|
||||
throw new \Exception('参数错误', 400);
|
||||
}
|
||||
|
||||
$this->service->handleWechatCallback($scene, $code);
|
||||
return Json::success('绑定成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Get(
|
||||
path: '/auth/weibo/callback',
|
||||
summary: '微博回调',
|
||||
tags: ['第三方认证'],
|
||||
parameters: [
|
||||
new \OpenApi\Attributes\Parameter(name: 'scene', description: '场景ID', in: 'query', required: true),
|
||||
new \OpenApi\Attributes\Parameter(name: 'code', description: '授权码', in: 'query', required: true),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function weiboCallback(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene');
|
||||
$code = $request->input('code');
|
||||
|
||||
if (empty($scene) || empty($code)) {
|
||||
throw new \Exception('参数错误', 400);
|
||||
}
|
||||
|
||||
$this->service->handleWeiboCallback($scene, $code);
|
||||
return Json::success('绑定成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Get(
|
||||
path: '/auth/douyin/callback',
|
||||
summary: '抖音回调',
|
||||
tags: ['第三方认证'],
|
||||
parameters: [
|
||||
new \OpenApi\Attributes\Parameter(name: 'scene', description: '场景ID', in: 'query', required: true),
|
||||
new \OpenApi\Attributes\Parameter(name: 'code', description: '授权码', in: 'query', required: true),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function douyinCallback(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene');
|
||||
$code = $request->input('code');
|
||||
if (empty($scene) || empty($code)) {
|
||||
throw new \Exception('参数错误', 400);
|
||||
}
|
||||
$this->service->handleDouyinCallback($scene, $code);
|
||||
return Json::success('绑定成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Get(
|
||||
path: '/auth/third-party/list',
|
||||
summary: '获取第三方绑定列表',
|
||||
tags: ['第三方认证'],
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getThirdPartyList(): Response
|
||||
{
|
||||
try {
|
||||
$result = $this->service->getThirdPartyList();
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Post(
|
||||
path: '/auth/third-party/bind',
|
||||
summary: '绑定第三方账号',
|
||||
requestBody: new \OpenApi\Attributes\RequestBody(
|
||||
required: true,
|
||||
content: new \OpenApi\Attributes\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new \OpenApi\Attributes\Schema(
|
||||
properties: [
|
||||
new \OpenApi\Attributes\Property(property: 'platform', description: '平台类型', type: 'integer'),
|
||||
new \OpenApi\Attributes\Property(property: 'openid', description: 'OpenID', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'unionid', description: 'UnionID', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'nickname', description: '昵称', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'avatar', description: '头像', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'gender', description: '性别', type: 'integer'),
|
||||
new \OpenApi\Attributes\Property(property: 'country', description: '国家', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'province', description: '省份', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'city', description: '城市', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'access_token', description: '访问令牌', type: 'string'),
|
||||
new \OpenApi\Attributes\Property(property: 'refresh_token', description: '刷新令牌', type: 'string'),
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
tags: ['第三方认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function bindThirdParty(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$validate = new ThirdPartyAuthValidate();
|
||||
$data = $request->all();
|
||||
if (!$validate->scene('bind')->check($data)) {
|
||||
throw new \Exception($validate->getError());
|
||||
};
|
||||
$this->service->bindThirdParty($data);
|
||||
return Json::success('绑定成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Delete(
|
||||
path: '/auth/third-party/unbind/{platform}',
|
||||
summary: '解绑第三方账号',
|
||||
tags: ['第三方认证'],
|
||||
parameters: [
|
||||
new \OpenApi\Attributes\Parameter(name: 'platform', description: '平台类型', in: 'path', required: true),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function unbindThirdParty(Request $request, int $platform): Response
|
||||
{
|
||||
try {
|
||||
$this->service->unbindThirdParty($platform);
|
||||
return Json::success('解绑成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Post(
|
||||
path: '/auth/third-party/qr-code',
|
||||
summary: '生成第三方绑定二维码',
|
||||
requestBody: new \OpenApi\Attributes\RequestBody(
|
||||
required: true,
|
||||
content: new \OpenApi\Attributes\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new \OpenApi\Attributes\Schema(
|
||||
properties: [
|
||||
new \OpenApi\Attributes\Property(property: 'platform', description: '平台类型', type: 'integer'),
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
tags: ['第三方认证'],
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function generateBindQrCode(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$platform = $request->post('platform');
|
||||
if (empty($platform)) {
|
||||
throw new \Exception('平台类型不能为空', 400);
|
||||
}
|
||||
|
||||
$result = $this->service->generateBindQrCode(['platform' => $platform]);
|
||||
return Json::success('生成成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[\OpenApi\Attributes\Get(
|
||||
path: '/auth/third-party/qr-status',
|
||||
summary: '检查绑定二维码状态',
|
||||
tags: ['第三方认证'],
|
||||
parameters: [
|
||||
new \OpenApi\Attributes\Parameter(name: 'scene_id', description: '场景ID', in: 'query', required: true),
|
||||
],
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function checkBindQrStatus(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$sceneId = $request->input('scene_id');
|
||||
if (empty($sceneId)) {
|
||||
throw new \Exception('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = $this->service->checkBindQrStatus($sceneId);
|
||||
return Json::success('检查成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\auth;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\auth\WechatService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '微信认证')]
|
||||
final class WechatController extends Base
|
||||
{
|
||||
public function __construct(WechatService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/auth/wechat',
|
||||
summary: '微信授权登录',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['code'],
|
||||
properties: [
|
||||
new OA\Property(property: 'code', description: '微信授权码', type: 'string'),
|
||||
new OA\Property(property: 'encrypted_data', description: '加密数据', type: 'string'),
|
||||
new OA\Property(property: 'iv', description: '加密向量', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['微信认证'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '登录成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
new OA\Response(response: 401, description: '登录失败'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function wechatLogin(Request $request): Response
|
||||
{
|
||||
$data = $request->post();
|
||||
$result = $this->service->wechatLogin($data);
|
||||
return json($result);
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/auth/weapp',
|
||||
summary: '微信小程序登录',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['code'],
|
||||
properties: [
|
||||
new OA\Property(property: 'code', description: '小程序登录code', type: 'string'),
|
||||
new OA\Property(property: 'encrypted_data', description: '加密数据', type: 'string'),
|
||||
new OA\Property(property: 'iv', description: '加密向量', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['微信认证'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '登录成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
new OA\Response(response: 401, description: '登录失败'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function weappLogin(Request $request): Response
|
||||
{
|
||||
$data = $request->post();
|
||||
$result = $this->service->weappLogin($data);
|
||||
return json($result);
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/auth/wechat/scan',
|
||||
summary: '生成微信扫码登录二维码',
|
||||
tags: ['微信认证']
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function generateWechatQrCode(): Response
|
||||
{
|
||||
$result = $this->service->generateWechatQrCode();
|
||||
return Json::success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查微信扫码状态
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/auth/wechat/scan/status',
|
||||
summary: '检查微信扫码状态',
|
||||
tags: ['微信认证'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'scene_id', description: '场景ID', in: 'query', required: true, schema: new OA\Schema(type: 'string')),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function checkWechatScanStatus(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$sceneId = $request->input('scene_id');
|
||||
if (empty($sceneId)) {
|
||||
return Json::fail('scene_id 不能为空');
|
||||
}
|
||||
$result = $this->service->checkWechatScanStatus($sceneId);
|
||||
return Json::success($result['msg'], $result['data']);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/auth/wechat/availability',
|
||||
summary: '检查微信登录可用性',
|
||||
tags: ['微信认证'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '查询成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function checkWechatAvailability(): Response
|
||||
{
|
||||
$result = $this->service->checkWechatAvailability();
|
||||
return Json::success('ok', $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\member;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\CurrentMember;
|
||||
use app\api\event\MemberInfoFetchedEvent;
|
||||
use app\service\api\member\MemberService;
|
||||
use core\exception\handler\UnauthorizedHttpException;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Container;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\UploadFile as WebmanUploadFile;
|
||||
|
||||
/**
|
||||
* 会员用户控制器
|
||||
*/
|
||||
#[OA\Tag(name: '会员模块')]
|
||||
final class MemberController extends Base
|
||||
{
|
||||
public function __construct(MemberService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/user-info',
|
||||
summary: '获取会员基本信息',
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getProfile(): Response
|
||||
{
|
||||
try {
|
||||
/** @var CurrentMember $currentMember */
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$data = $currentMember->user(true);
|
||||
if (empty($data)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
|
||||
$permissions = [];
|
||||
$extra = [];
|
||||
|
||||
$event = new MemberInfoFetchedEvent($data['id'], $data, $permissions, $extra);
|
||||
$event->dispatch();
|
||||
|
||||
// 将修改后的数据回写到原变量
|
||||
$data['permissions'] = $event->getPermissions();
|
||||
|
||||
if (!empty($event->getExtra())) {
|
||||
$data = array_merge($data, $event->getExtra());
|
||||
}
|
||||
|
||||
return Json::success('ok', $data);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage(), null, 401);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Put(
|
||||
path: '/member/user/update',
|
||||
summary: '更新会员信息',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'nickname', description: '昵称', type: 'string'),
|
||||
new OA\Property(property: 'avatar', description: '头像', type: 'string'),
|
||||
new OA\Property(property: 'gender', description: '性别', type: 'integer'),
|
||||
new OA\Property(property: 'birthday', description: '生日', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '更新成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function update(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->all();
|
||||
$this->service->updateInfo($data);
|
||||
return Json::success();
|
||||
} catch (\Exception $exception) {
|
||||
return Json::fail($exception->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/member/user/avatar',
|
||||
summary: '上传头像',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\MediaType(
|
||||
mediaType: 'multipart/form-data',
|
||||
schema: new OA\Schema(
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'file',
|
||||
description: '头像文件',
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '上传成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function uploadAvatar(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$file = $request->file('file');
|
||||
if (!$file || !$file instanceof WebmanUploadFile) {
|
||||
throw new \Exception('请上传头像文件', 400);
|
||||
}
|
||||
|
||||
$result = $this->service->uploadAvatar($file);
|
||||
return Json::success('上传成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Put(
|
||||
path: '/member/user/bind-phone',
|
||||
summary: '绑定会员手机号',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['phone', 'verify_code'],
|
||||
properties: [
|
||||
new OA\Property(property: 'phone', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'verify_code', description: '验证码', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '绑定成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function bindPhone(Request $request): Response
|
||||
{
|
||||
$data = $request->post();
|
||||
$result = $this->service->bindPhone($data);
|
||||
return json($result);
|
||||
}
|
||||
|
||||
#[OA\Put(
|
||||
path: '/member/user/change-password',
|
||||
summary: '修改密码',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['old_password', 'new_password'],
|
||||
properties: [
|
||||
new OA\Property(property: 'old_password', description: '旧密码', type: 'string'),
|
||||
new OA\Property(property: 'new_password', description: '新密码', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '修改成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function changePassword(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->all();
|
||||
$this->service->changePassword($data);
|
||||
return Json::success('密码修改成功');
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], $e->getCode() ?: -1);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/user/address/list',
|
||||
summary: '获取地址列表',
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getAddressList(): Response
|
||||
{
|
||||
try {
|
||||
$result = $this->service->getAddressList();
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/member/user/address/create',
|
||||
summary: '创建地址',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['name', 'phone', 'province', 'city', 'district', 'address'],
|
||||
properties: [
|
||||
new OA\Property(property: 'name', description: '收件人姓名', type: 'string'),
|
||||
new OA\Property(property: 'phone', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'province', description: '省份', type: 'string'),
|
||||
new OA\Property(property: 'city', description: '城市', type: 'string'),
|
||||
new OA\Property(property: 'district', description: '区域', type: 'string'),
|
||||
new OA\Property(property: 'address', description: '详细地址', type: 'string'),
|
||||
new OA\Property(property: 'is_default', description: '是否默认地址', type: 'boolean'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['会员模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '创建成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function createAddress(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
$result = $this->service->createAddress($data);
|
||||
return Json::success('创建成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Put(
|
||||
path: '/member/user/address/update/{id}',
|
||||
summary: '更新地址',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'name', description: '收件人姓名', type: 'string'),
|
||||
new OA\Property(property: 'phone', description: '手机号', type: 'string'),
|
||||
new OA\Property(property: 'province', description: '省份', type: 'string'),
|
||||
new OA\Property(property: 'city', description: '城市', type: 'string'),
|
||||
new OA\Property(property: 'district', description: '区域', type: 'string'),
|
||||
new OA\Property(property: 'address', description: '详细地址', type: 'string'),
|
||||
new OA\Property(property: 'is_default', description: '是否默认地址', type: 'boolean'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['会员模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'id', description: '地址ID', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '更新成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
new OA\Response(response: 404, description: '地址不存在'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function updateAddress(int $id, Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
$result = $this->service->updateAddress($id, $data);
|
||||
return Json::success('更新成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
path: '/member/user/address/delete/{id}',
|
||||
summary: '删除地址',
|
||||
tags: ['会员模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'id', description: '地址ID', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '删除成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 404, description: '地址不存在'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: true, requirePermission: false, description: '公共接口')]
|
||||
public function deleteAddress(int $id): Response
|
||||
{
|
||||
try {
|
||||
$result = $this->service->deleteAddress($id);
|
||||
return Json::success('删除成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller\member;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\CurrentMember;
|
||||
use app\service\api\member\MemberPointsService;
|
||||
use core\exception\handler\UnauthorizedHttpException;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Container;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '积分模块')]
|
||||
#
|
||||
final class MemberPointsController extends Base
|
||||
{
|
||||
public function __construct(MemberPointsService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/points/record',
|
||||
summary: '获取积分流水记录',
|
||||
tags: ['积分模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'page', description: '页码', in: 'query', schema: new OA\Schema(type: 'integer')),
|
||||
new OA\Parameter(name: 'limit', description: '每页数量', in: 'query', schema: new OA\Schema(type: 'integer')),
|
||||
new OA\Parameter(name: 'type', description: '积分类型', in: 'query', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function record(Request $request): Response
|
||||
{
|
||||
$params = $request->all();
|
||||
try {
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
$params['member_id'] = $member['id'];
|
||||
$result = $this->service->getPointsRecord($params);
|
||||
return Json::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), []);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/points/total',
|
||||
summary: '获取会员积分总额',
|
||||
tags: ['积分模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function total(): Response
|
||||
{
|
||||
try {
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
$result = $this->service->getPointsTotal($member['id']);
|
||||
return Json::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), []);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/points/level',
|
||||
summary: '获取会员等级信息',
|
||||
tags: ['积分模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getMemberLevel(): Response
|
||||
{
|
||||
try {
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
$result = $this->service->getMemberLevel($member['id']);
|
||||
return Json::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), []);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/member/points/sign',
|
||||
summary: '会员签到',
|
||||
tags: ['积分模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '签到成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '今日已签到'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function memberSign(): Response
|
||||
{
|
||||
try {
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
$result = $this->service->memberSign($member['id']);
|
||||
return Json::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), []);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller\member;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\CurrentMember;
|
||||
use app\api\middleware\ApiAccessTokenMiddleware;
|
||||
use app\service\api\member\MemberSignService;
|
||||
use core\exception\handler\UnauthorizedHttpException;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\annotation\Middleware;
|
||||
use support\Container;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '会员签到')]
|
||||
#[Middleware(ApiAccessTokenMiddleware::class)]
|
||||
final class MemberSignController extends Base
|
||||
{
|
||||
public function __construct(MemberSignService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/member/sign',
|
||||
summary: '每日签到',
|
||||
tags: ['会员签到'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '签到成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
new OA\Response(response: 400, description: '今日已签到'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function sign(): Response
|
||||
{
|
||||
try {
|
||||
/** @var CurrentMember $currentMember */
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
|
||||
$memberId = $member['id'];
|
||||
|
||||
// 记录设备信息
|
||||
$deviceInfo = [
|
||||
'ip' => request()->getRealIp(),
|
||||
'ua' => request()->header('user-agent'),
|
||||
];
|
||||
// 调用服务层执行签到
|
||||
$result = $this->service->sign($memberId, $deviceInfo);
|
||||
|
||||
return Json::success('签到成功', [
|
||||
'points' => $result['points'],
|
||||
'continuous_days' => $result['continuous_days'],
|
||||
'sign_date' => $result['sign_date'],
|
||||
]);
|
||||
|
||||
} catch (UnauthorizedHttpException $e) {
|
||||
return Json::fail($e->getMessage(), null, 401);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), null, 400);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/sign/status',
|
||||
summary: '获取签到状态',
|
||||
tags: ['会员签到'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getStatus(): Response
|
||||
{
|
||||
try {
|
||||
/** @var CurrentMember $currentMember */
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
$memberId = $member['id'];
|
||||
$result = $this->service->getStatus($memberId);
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/sign/calendar',
|
||||
summary: '获取签到日历',
|
||||
tags: ['会员签到'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'year', description: '年份', in: 'query', schema: new OA\Schema(type: 'integer')),
|
||||
new OA\Parameter(name: 'month', description: '月份', in: 'query', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getCalendar(Request $request): Response
|
||||
{
|
||||
try {
|
||||
/** @var CurrentMember $currentMember */
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
|
||||
$memberId = (int)$member['id'];
|
||||
$year = (int)$request->input('year', date('Y'));
|
||||
$month = (int)$request->input('month', date('m'));
|
||||
|
||||
// 调用服务层获取签到日历
|
||||
$result = $this->service->getCalendar($memberId, $year, $month);
|
||||
|
||||
return Json::success('获取成功', $result);
|
||||
|
||||
} catch (UnauthorizedHttpException $e) {
|
||||
return Json::fail($e->getMessage(), null, 401);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/member/sign/statistics',
|
||||
summary: '获取签到统计',
|
||||
tags: ['会员签到'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'type', description: '统计类型 (week/month/year)', in: 'query', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'year', description: '年份', in: 'query', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 401, description: '未登录'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getStatistics(Request $request): Response
|
||||
{
|
||||
try {
|
||||
/** @var CurrentMember $currentMember */
|
||||
$currentMember = Container::make(CurrentMember::class);
|
||||
$member = $currentMember->user(true);
|
||||
|
||||
if (empty($member)) {
|
||||
throw new UnauthorizedHttpException('用户凭证失效请重新登录');
|
||||
}
|
||||
|
||||
$memberId = (int)$member['id'];
|
||||
$type = $request->input('type', 'month');
|
||||
|
||||
// 调用服务层获取签到统计
|
||||
$result = $this->service->getStatistics($memberId, $type);
|
||||
|
||||
return Json::success('获取成功', $result);
|
||||
|
||||
} catch (UnauthorizedHttpException $e) {
|
||||
return Json::fail($e->getMessage(), null, 401);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller\site;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\web\AdvertisementService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '广告模块')]
|
||||
final class AdvertisementController extends Base
|
||||
{
|
||||
public function __construct(AdvertisementService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取广告列表
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/site/ads',
|
||||
summary: '获取广告列表',
|
||||
tags: ['广告模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '[]')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function index(): Response
|
||||
{
|
||||
try {
|
||||
$result = $this->service->getAds();
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取广告位信息
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/site/ads/info',
|
||||
summary: '获取广告位信息',
|
||||
tags: ['广告模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'type',
|
||||
description: '广告类型',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: new OA\Schema(type: 'string')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '[]')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getInfo(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$params = $request->get();
|
||||
$result = $this->service->getAdvertisementInfo($params);
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller\site;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\web\LinkService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '友情链接模块')]
|
||||
final class LinkController extends Base
|
||||
{
|
||||
public function __construct(LinkService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链接列表
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/site/link',
|
||||
summary: '获取友情链接',
|
||||
tags: ['友情链接模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'type',
|
||||
description: '链接类型(footer-页脚)',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: new OA\Schema(type: 'string', default: 'footer')
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '[]')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$type = $request->get('type', 'footer');
|
||||
$result = $this->service->getLinksByType($type);
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有链接
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/site/link/all',
|
||||
summary: '获取所有链接',
|
||||
tags: ['友情链接模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '[]')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getAll(): Response
|
||||
{
|
||||
try {
|
||||
$result = $this->service->getAllLinks();
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\site;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\web\MenuService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '导航菜单模块')]
|
||||
final class MenuController extends Base
|
||||
{
|
||||
public function __construct(MenuService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导航菜单列表
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/site/nav',
|
||||
summary: '导航菜单',
|
||||
tags: ['导航菜单模块'],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '[]')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function index(): Response
|
||||
{
|
||||
try {
|
||||
$result = $this->service->getNavigationList();
|
||||
return Json::success('获取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\system;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\system\AgreementService;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '协议模块')]
|
||||
final class AgreementController extends Base
|
||||
{
|
||||
public function __construct(AgreementService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/agreement/{key}',
|
||||
summary: '获取协议内容',
|
||||
tags: ['协议模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'key', description: '协议标识', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 404, description: '协议不存在'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getAgreementContent(string $key): Response
|
||||
{
|
||||
$result = $this->service->getAgreementContent($key);
|
||||
return json($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\system;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\system\CaptchaService;
|
||||
use core\captcha\Captcha;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* 验证码控制器
|
||||
*/
|
||||
#[OA\Tag(name: '验证码模块')]
|
||||
final class CaptchaController extends Base
|
||||
{
|
||||
public function __construct(CaptchaService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/captcha',
|
||||
summary: '获取验证码图片',
|
||||
tags: ['验证码模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'time', description: '时间戳,用于防止缓存', in: 'query', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function captcha(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$captcha = new Captcha();
|
||||
$type = $request->input('type', 'web-login');
|
||||
$result = $captcha->captcha($request, $type);
|
||||
return Json::success('ok', $result);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*/
|
||||
#[OA\Post(
|
||||
path: '/sms/{type}',
|
||||
summary: '发送短信验证码',
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['mobile'],
|
||||
properties: [
|
||||
new OA\Property(property: 'mobile', description: '手机号', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
tags: ['验证码模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'type', description: '验证码类型(login, register, bind等)', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '发送成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
new OA\Response(response: 429, description: '发送过于频繁'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
public function sendSmsCode(string $type): Response
|
||||
{
|
||||
$result = $this->service->sendSmsCode($type);
|
||||
return json($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller\system;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\middleware\ApiAccessTokenMiddleware;
|
||||
use app\api\schema\request\system\ConfigGroupQueryRequest;
|
||||
use app\api\schema\request\system\ConfigQueryRequest;
|
||||
use app\api\schema\request\system\ConfigValueRequest;
|
||||
use app\service\api\system\ConfigService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\annotation\Middleware;
|
||||
use support\Request;
|
||||
use WebmanTech\Swagger\DTO\SchemaConstants;
|
||||
|
||||
//#[Middleware(ApiAccessTokenMiddleware::class)]
|
||||
final class ConfigController extends Base
|
||||
{
|
||||
public function __construct(ConfigService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按分组获取配置
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $group_code
|
||||
*
|
||||
* @return \support\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: "/system/config/group/{group_code}",
|
||||
summary: "按分组获取配置",
|
||||
tags: ["系统配置"],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => ConfigGroupQueryRequest::class,
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '{"site_open": "1","site_url": "http://127.0.0.1:8001","site_name": "madong-admin","site_logo": "https://madong.tech/assets/images/logo.svg","site_network_security": "2024042441号-2","site_description": "快速开发框架","site_record_no": "2024042442","site_icp_url": "https://beian.miit.gov.cn/","site_network_security_url": ""}')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getByGroup(Request $request, string $group_code): \support\Response
|
||||
{
|
||||
try {
|
||||
// 参数验证
|
||||
if (empty($group_code)) {
|
||||
return Json::fail('分组编码不能为空');
|
||||
}
|
||||
|
||||
$options = [
|
||||
'enabled_only' => $request->input('enabled_only', true),
|
||||
'with_metadata' => $request->input('with_metadata', false),
|
||||
];
|
||||
$result = $this->service->getByGroup($group_code, [], $options);
|
||||
return Json::success('操作成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: "/system/config/code/{code}",
|
||||
summary: "按code获取配置",
|
||||
tags: ["系统配置"],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => ConfigQueryRequest::class,
|
||||
]
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "code",
|
||||
description: "配置编码",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: new OA\Schema(type: "string"),
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: ' {"site_open": "1","site_url": "http://127.0.0.1:8001"}')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getByCode(Request $request, string $code): \support\Response
|
||||
{
|
||||
try {
|
||||
// 参数验证
|
||||
if (empty($code)) {
|
||||
return Json::fail('配置编码不能为空');
|
||||
}
|
||||
|
||||
$groupCode = $request->input('group_code', '');
|
||||
$options = [];
|
||||
if (!empty($groupCode)) {
|
||||
$options['group_code'] = $groupCode;
|
||||
}
|
||||
$result = $this->service->config($code, [], $options);
|
||||
return Json::success('操作成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定配置项的特定键值
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $code
|
||||
*
|
||||
* @return \support\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: "/system/config/{code}/value",
|
||||
summary: "获取特定配置项的特定键值",
|
||||
tags: ["系统配置"],
|
||||
x: [
|
||||
SchemaConstants::X_SCHEMA_REQUEST => ConfigValueRequest::class,
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getValue(Request $request, string $code): \support\Response
|
||||
{
|
||||
try {
|
||||
// 参数验证
|
||||
if (empty($code)) {
|
||||
return Json::fail('配置项编码不能为空');
|
||||
}
|
||||
|
||||
$groupCode = $request->input('group_code');
|
||||
$key = $request->input('key');
|
||||
|
||||
if (empty($groupCode) || empty($key)) {
|
||||
return Json::fail('分组编码和配置键不能为空');
|
||||
}
|
||||
|
||||
$result = $this->service->getValue($code, $key, null, ['group_code' => $groupCode]);
|
||||
return Json::success('操作成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置并按分组整理
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \support\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: "/system/config/all-grouped",
|
||||
summary: "获取所有配置并按分组整理",
|
||||
tags: ["系统配置"],
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "enabled_only",
|
||||
description: "是否只获取启用的配置项",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: new OA\Schema(type: "boolean", default: true),
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "group_filter",
|
||||
description: "分组过滤,多个分组用逗号分隔",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: new OA\Schema(type: "string"),
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "with_metadata",
|
||||
description: "是否包含配置项的完整元数据",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: new OA\Schema(type: "boolean", default: false),
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "key_by",
|
||||
description: "按指定字段作为键名('code' 或 'id')",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: new OA\Schema(type: "string", enum: ["code", "id"]),
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "sort_groups",
|
||||
description: "是否对分组进行排序",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: new OA\Schema(type: "boolean", default: false),
|
||||
)]
|
||||
#[OA\Parameter(
|
||||
name: "sort_configs",
|
||||
description: "是否对配置项进行排序",
|
||||
in: "query",
|
||||
required: false,
|
||||
schema: new OA\Schema(type: "boolean", default: false),
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: '{"site": {"site_name": "网站名称"}, "oss": {"accessKeyId": "xxx"}}')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getAllGrouped(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$options = [
|
||||
'enabled_only' => $request->input('enabled_only', true),
|
||||
'with_metadata' => $request->input('with_metadata', false),
|
||||
'key_by' => $request->input('key_by', null),
|
||||
'sort_groups' => $request->input('sort_groups', false),
|
||||
'sort_configs' => $request->input('sort_configs', false),
|
||||
];
|
||||
|
||||
// 处理分组过滤参数
|
||||
$groupFilter = $request->input('group_filter', '');
|
||||
if (!empty($groupFilter)) {
|
||||
$options['group_filter'] = explode(',', $groupFilter);
|
||||
}
|
||||
|
||||
$result = $this->service->getAllGrouped($options);
|
||||
return Json::success('操作成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\system;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\api\CurrentMember;
|
||||
use app\dao\system\FeedbackDao;
|
||||
use core\exception\handler\UnauthorizedHttpException;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
#[OA\Tag(name: '问题反馈')]
|
||||
final class FeedbackController extends Base
|
||||
{
|
||||
private FeedbackDao $dao;
|
||||
|
||||
public function __construct(FeedbackDao $dao, private readonly CurrentMember $currentMember)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/feedback/mine',
|
||||
summary: '获取当前用户的问题反馈列表',
|
||||
tags: ['问题反馈'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'page', description: '页码', in: 'query', schema: new OA\Schema(type: 'integer', default: 1)),
|
||||
new OA\Parameter(name: 'limit', description: '每页数量', in: 'query', schema: new OA\Schema(type: 'integer', default: 10)),
|
||||
],
|
||||
responses: [new OA\Response(response: 200, description: '获取成功')]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: true, requirePermission: false, description: '会员接口')]
|
||||
public function mine(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$memberId = $this->currentMember->id();
|
||||
if (!$memberId) {
|
||||
throw new UnauthorizedHttpException('请先登录后查看反馈记录');
|
||||
}
|
||||
|
||||
$page = max(1, (int)$request->get('page', 1));
|
||||
$limit = min(50, max(1, (int)$request->get('limit', 10)));
|
||||
$where = ['created_by' => $memberId];
|
||||
$total = $this->dao->getCount($where);
|
||||
$items = $this->dao->selectList($where, '*', $page, $limit, 'created_at desc')?->values()->all() ?? [];
|
||||
|
||||
return Json::success('ok', [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage(), [], $e instanceof UnauthorizedHttpException ? 401 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/feedback/mine/{id}',
|
||||
summary: '获取当前用户的问题反馈详情',
|
||||
tags: ['问题反馈'],
|
||||
parameters: [new OA\Parameter(name: 'id', description: '反馈 ID', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
|
||||
responses: [new OA\Response(response: 200, description: '获取成功')]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: true, requirePermission: false, description: '会员接口')]
|
||||
public function mineShow(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$memberId = $this->currentMember->id();
|
||||
if (!$memberId) {
|
||||
throw new UnauthorizedHttpException('请先登录后查看反馈记录');
|
||||
}
|
||||
|
||||
$feedback = $this->dao->get([
|
||||
'id' => $request->route->param('id'),
|
||||
'created_by' => $memberId,
|
||||
]);
|
||||
if (!$feedback) {
|
||||
return Json::fail('反馈记录不存在或无权查看', [], 404);
|
||||
}
|
||||
|
||||
$feedback->load('replyAdmin');
|
||||
return Json::success('ok', $feedback->toArray());
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage(), [], $e instanceof UnauthorizedHttpException ? 401 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/feedback',
|
||||
summary: '提交问题反馈',
|
||||
tags: ['问题反馈'],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['type', 'content', 'source'],
|
||||
properties: [
|
||||
new OA\Property(property: 'type', description: '反馈类型:bug/suggestion/correction', type: 'string'),
|
||||
new OA\Property(property: 'content', description: '问题描述,最多2000个字符', type: 'string'),
|
||||
new OA\Property(property: 'contact', description: '联系方式,最多100个字符', type: 'string'),
|
||||
new OA\Property(property: 'images', description: '截图地址列表,最多3张', type: 'array', items: new OA\Items(type: 'string')),
|
||||
new OA\Property(property: 'source', description: '反馈来源:app/web', type: 'string'),
|
||||
]
|
||||
)
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '提交成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
$data['content'] = trim((string)($data['content'] ?? ''));
|
||||
$data['contact'] = trim((string)($data['contact'] ?? ''));
|
||||
$data['source'] = (string)($data['source'] ?? 'app');
|
||||
$data['images'] = $data['images'] ?? [];
|
||||
|
||||
$validator = validator($data, [
|
||||
'type' => 'required|in:bug,suggestion,correction',
|
||||
'content' => 'required|max:2000',
|
||||
'contact' => 'nullable|max:100',
|
||||
'images' => 'array|max:3',
|
||||
'source' => 'required|in:app,web',
|
||||
], [
|
||||
'type.required' => '请选择反馈类型',
|
||||
'type.in' => '反馈类型不正确',
|
||||
'content.required' => '请填写问题描述',
|
||||
'content.max' => '问题描述不能超过2000个字符',
|
||||
'contact.max' => '联系方式不能超过100个字符',
|
||||
'images.array' => '截图附件格式不正确',
|
||||
'images.max' => '截图附件最多上传3张',
|
||||
'source.required' => '请选择反馈来源',
|
||||
'source.in' => '反馈来源不正确',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
throw new \InvalidArgumentException($validator->errors()->first());
|
||||
}
|
||||
|
||||
$images = array_values(array_filter($data['images'], static fn ($image) => is_string($image) && $image !== ''));
|
||||
$type = $data['type'];
|
||||
$content = $data['content'];
|
||||
$contact = $data['contact'];
|
||||
$source = $data['source'];
|
||||
$memberId = $this->currentMember->id();
|
||||
$model = $this->dao->save([
|
||||
'type' => $type,
|
||||
'content' => $content,
|
||||
'contact' => $contact,
|
||||
'images' => $images,
|
||||
'source' => $source,
|
||||
'created_by' => $memberId ?: null,
|
||||
]);
|
||||
|
||||
return Json::success('提交成功', ['id' => $model->id]);
|
||||
} catch (\Throwable $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\system;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\attribute\SkipApiEncrypt;
|
||||
use core\tool\ApiEncryptService;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\DataResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
#[SkipApiEncrypt]
|
||||
final class SecurityController extends Base
|
||||
{
|
||||
protected ApiEncryptService $encryptService;
|
||||
|
||||
public function __construct(ApiEncryptService $encryptService)
|
||||
{
|
||||
$this->encryptService = $encryptService;
|
||||
}
|
||||
|
||||
#[OA\Get(path: '/system/security/api-encrypt/public-key', summary: '获取接口加密公钥', tags: ['system-security'])]
|
||||
#[DataResponse(description: '获取成功')]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function publicKey(Request $request): Response
|
||||
{
|
||||
$publicKey = $this->encryptService->getPublicKey();
|
||||
if ($publicKey === '') {
|
||||
return Json::fail('接口加密公钥未配置');
|
||||
}
|
||||
|
||||
return Json::success('获取成功', [
|
||||
'enabled' => $this->encryptService->isEnabled(),
|
||||
'scope' => $this->encryptService->getScope(),
|
||||
'algorithm' => $this->encryptService->getAlgorithm(),
|
||||
'key_version' => $this->encryptService->getKeyVersion(),
|
||||
'public_key' => $publicKey,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller\system;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\system\WechatService;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* 微信控制器
|
||||
*/
|
||||
#[OA\Tag(name: '微信模块')]
|
||||
final class WechatController extends Base
|
||||
{
|
||||
public function __construct(WechatService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/wechat/auth-code',
|
||||
summary: '获取微信授权码',
|
||||
tags: ['微信模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'redirect_url', description: '回调URL', in: 'query', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getWechatAuthCode(Request $request): Response
|
||||
{
|
||||
$params = $request->get();
|
||||
$result = $this->service->getWechatAuthCode($params);
|
||||
return json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步微信用户信息
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/wechat/sync',
|
||||
summary: '同步微信用户信息',
|
||||
tags: ['微信模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'code', description: '微信授权码', in: 'query', required: true, schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '同步成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function syncWechatUserInfo(Request $request): Response
|
||||
{
|
||||
$params = $request->get();
|
||||
$result = $this->service->syncWechatUserInfo($params);
|
||||
return json($result);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/wechat/jssdk-config',
|
||||
summary: '获取微信 JSSDK 配置',
|
||||
tags: ['微信模块'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'url', description: '当前页面URL', in: 'query', required: true, schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: '获取成功'),
|
||||
new OA\Response(response: 400, description: '参数错误'),
|
||||
]
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function getWechatJssdkConfig(Request $request): Response
|
||||
{
|
||||
$params = $request->get();
|
||||
$result = $this->service->getWechatJssdkConfig($params);
|
||||
return json($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\controller\upload;
|
||||
|
||||
use app\api\controller\Base;
|
||||
use app\service\api\upload\UploadService;
|
||||
use core\exception\handler\AdminException;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\annotation\response\SimpleResponse;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use OpenApi\Attributes as OA;
|
||||
use OpenApi\Attributes\RequestBody;
|
||||
use support\Request;
|
||||
|
||||
final class UploadController extends Base
|
||||
{
|
||||
|
||||
public function __construct(UploadService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/file/image',
|
||||
description: '上传图片文件,支持 JPG/PNG/GIF/WEBP 格式,最大 5MB',
|
||||
summary: '图片上传',
|
||||
tags: ['上传管理'],
|
||||
)]
|
||||
#[RequestBody(
|
||||
required: true,
|
||||
content: new OA\MediaType(
|
||||
mediaType: 'multipart/form-data',
|
||||
schema: new OA\Schema(
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'file',
|
||||
description: '图片文件',
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'sub_dir',
|
||||
description: '子目录路径,例如:video/202603',
|
||||
type: 'string',
|
||||
example: 'images'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function image(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$subDir = $request->input('sub_dir', 'image');
|
||||
$subDir .= '/' . date('Ym');
|
||||
$result = $this->service->uploadImage($subDir);
|
||||
return Json::success('上传成功', $result->toArray());
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/file/video',
|
||||
description: '上传视频文件,支持 MP4/AVI/MOV/WMV/FLV/MKV 格式,最大 100MB',
|
||||
summary: '视频上传',
|
||||
tags: ['上传管理'],
|
||||
)]
|
||||
#[RequestBody(
|
||||
required: true,
|
||||
content: new OA\MediaType(
|
||||
mediaType: 'multipart/form-data',
|
||||
schema: new OA\Schema(
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'file',
|
||||
description: '视频文件',
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'sub_dir',
|
||||
description: '子目录路径,例如:video/202603',
|
||||
type: 'string',
|
||||
example: 'video/202603'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function video(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$subDir = $request->input('sub_dir', 'video');
|
||||
$subDir .= '/' . date('Ym');
|
||||
$result = $this->service->uploadVideo($subDir);
|
||||
return Json::success('上传成功', $result->toArray());
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/file',
|
||||
description: '上传普通文件,支持文档和压缩包,最大 50MB',
|
||||
summary: '文件上传',
|
||||
tags: ['上传管理'],
|
||||
)]
|
||||
#[RequestBody(
|
||||
required: true,
|
||||
content: new OA\MediaType(
|
||||
mediaType: 'multipart/form-data',
|
||||
schema: new OA\Schema(
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'file',
|
||||
description: '文件',
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'sub_dir',
|
||||
description: '子目录路径,例如:file/202603',
|
||||
type: 'string',
|
||||
example: 'file/202603'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function file(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$subDir = $request->input('sub_dir', 'file');
|
||||
$subDir .= '/' . date('Ym');
|
||||
$result = $this->service->uploadFile($subDir);
|
||||
return Json::success('上传成功', $result->toArray());
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/file/fetch-image',
|
||||
description: '从远程URL下载图片并保存到服务器',
|
||||
summary: '远程图片拉取',
|
||||
tags: ['上传管理'],
|
||||
)]
|
||||
#[RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'url',
|
||||
description: '图片URL',
|
||||
type: 'string',
|
||||
example: 'https://example.com/image.jpg'
|
||||
),
|
||||
]
|
||||
)
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function fetchImage(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$url = $request->input('url', '');
|
||||
if (empty($url)) {
|
||||
throw new AdminException('图片URL不能为空');
|
||||
}
|
||||
$result = $this->service->fetchImage($url);
|
||||
return Json::success('拉取成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
path: '/file/base64-image',
|
||||
description: '上传Base64编码的图片',
|
||||
summary: 'Base64图片上传',
|
||||
tags: ['上传管理'],
|
||||
)]
|
||||
#[RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'base64',
|
||||
description: 'Base64编码的图片数据',
|
||||
type: 'string',
|
||||
example: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
|
||||
),
|
||||
]
|
||||
)
|
||||
)]
|
||||
#[SimpleResponse(schema: [], example: [])]
|
||||
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
|
||||
public function base64Image(Request $request): \support\Response
|
||||
{
|
||||
try {
|
||||
$base64 = $request->input('base64', '');
|
||||
if (empty($base64)) {
|
||||
throw new AdminException('Base64数据不能为空');
|
||||
}
|
||||
$result = $this->service->uploadBase64Image($base64);
|
||||
return Json::success('上传成功', $result);
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\event;
|
||||
|
||||
use Webman\Event\Event;
|
||||
|
||||
/**
|
||||
* 会员用户信息获取事件
|
||||
*
|
||||
* 当获取用户信息时触发,允许监听器动态添加权限、自定义字段等
|
||||
*/
|
||||
class MemberInfoFetchedEvent
|
||||
{
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
public int $memberId;
|
||||
|
||||
/**
|
||||
* 用户数据(允许监听器修改)
|
||||
*/
|
||||
public array $userData = [];
|
||||
|
||||
/**
|
||||
* 权限数组(允许监听器追加)
|
||||
*/
|
||||
public array $permissions = [];
|
||||
|
||||
/**
|
||||
* 额外数据(允许插件等追加自定义字段)
|
||||
*/
|
||||
public array $extra = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct(int $memberId, array $userData = [], array $permissions = [], array $extra = [])
|
||||
{
|
||||
$this->memberId = $memberId;
|
||||
$this->userData = $userData;
|
||||
$this->permissions = $permissions;
|
||||
$this->extra = $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户数据
|
||||
*/
|
||||
public function getUserData(): array
|
||||
{
|
||||
return $this->userData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限数组
|
||||
*/
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取额外数据
|
||||
*/
|
||||
public function getExtra(): array
|
||||
{
|
||||
return $this->extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
*/
|
||||
public function dispatch(): void
|
||||
{
|
||||
Event::emit('member.info.fetched', $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加权限
|
||||
*/
|
||||
public function addPermission(string $permission): void
|
||||
{
|
||||
if (!in_array($permission, $this->permissions)) {
|
||||
$this->permissions[] = $permission;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加权限
|
||||
*/
|
||||
public function addPermissions(array $permissions): void
|
||||
{
|
||||
foreach ($permissions as $permission) {
|
||||
$this->addPermission($permission);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加额外字段
|
||||
*/
|
||||
public function addExtra(string $key, mixed $value): void
|
||||
{
|
||||
$this->extra[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户数据字段
|
||||
*/
|
||||
public function addUserData(string $key, mixed $value): void
|
||||
{
|
||||
$this->userData[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有某个权限
|
||||
*/
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
return in_array($permission, $this->permissions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\listener;
|
||||
|
||||
use app\api\event\MemberInfoFetchedEvent;
|
||||
use app\model\member\MemberTag;
|
||||
use support\Container;
|
||||
|
||||
/**
|
||||
* 会员标签权限监听器
|
||||
*
|
||||
* 监听用户信息获取事件,自动添加标签权限
|
||||
*/
|
||||
class MemberTagPermissionListener
|
||||
{
|
||||
/**
|
||||
* 处理事件
|
||||
*/
|
||||
public function handle(MemberInfoFetchedEvent $event): void
|
||||
{
|
||||
try {
|
||||
$memberId = $event->memberId;
|
||||
|
||||
// 查询会员的所有启用标签及权限
|
||||
$tags = MemberTag::with(['permissions'])
|
||||
->whereHas('members', function ($query) use ($memberId) {
|
||||
$query->where('member_id', $memberId);
|
||||
})
|
||||
->where('enabled', 1)
|
||||
->get();
|
||||
|
||||
if ($tags->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集所有权限
|
||||
$permissions = [];
|
||||
foreach ($tags as $tag) {
|
||||
foreach ($tag->permissions as $permission) {
|
||||
// permission 是 Menu 对象,使用 code 字段
|
||||
$permissions[] = $permission->code;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到事件
|
||||
if (!empty($permissions)) {
|
||||
$event->addPermissions(array_unique($permissions));
|
||||
}
|
||||
|
||||
// 也可以将标签信息添加到额外数据中(tags 字段已存在,这里可以不添加)
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录日志但不影响主流程
|
||||
error_log('MemberTagPermissionListener error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\middleware;
|
||||
|
||||
|
||||
use core\exception\handler\UnauthorizedHttpException;
|
||||
use core\jwt\JwtToken;
|
||||
use core\tool\Json;
|
||||
use madong\swagger\attribute\AllowAnonymous;
|
||||
use madong\swagger\helper\AnnotationHelper;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use Webman\MiddlewareInterface;
|
||||
|
||||
/**
|
||||
* ApiAccessToken 中间件(JWT Token 验证)
|
||||
* 功能:
|
||||
* 1. 检查请求是否需要跳过 Token 验证(通过 SkipAuth 注解)
|
||||
* 2. 验证 JWT Token 有效性,无效则返回 401
|
||||
*/
|
||||
final class ApiAccessTokenMiddleware implements MiddlewareInterface
|
||||
{
|
||||
|
||||
public function process(Request $request, callable $handler): Response
|
||||
{
|
||||
$route = $request->route;
|
||||
if (!$route || !isset($request->action)) {
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
$controllerClass = $request->controller;
|
||||
$action = $request->action;
|
||||
|
||||
$skipAuth = AnnotationHelper::getMethodAnnotation($controllerClass, $action, AllowAnonymous::class);
|
||||
if ($skipAuth && !$skipAuth->shouldRequireToken()) {
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
try {
|
||||
$jwt = new JwtToken();
|
||||
$userId = $jwt->id();
|
||||
if (empty($userId)) {
|
||||
throw new UnauthorizedHttpException();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return Json::fail($e->getMessage(), [], 401);
|
||||
}
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\request\system;
|
||||
|
||||
use app\schema\request\BaseFormRequest;
|
||||
use OpenApi\Attributes as OA;
|
||||
use WebmanTech\DTO\Attributes\ValidationRules;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '配置表单',
|
||||
description: '配置创建和编辑接口共用的表单请求参数'
|
||||
)]
|
||||
class ConfigFormRequest extends BaseFormRequest
|
||||
{
|
||||
#[OA\Property(
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:50')]
|
||||
public string $group_code;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置编码',
|
||||
type: 'string',
|
||||
example: 'site_name',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:50|unique:sys_config,code')]
|
||||
public string $code;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置名称',
|
||||
type: 'string',
|
||||
example: '网站名称',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:100')]
|
||||
public string $name;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置内容',
|
||||
type: 'mixed',
|
||||
example: '我的网站',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required')]
|
||||
public mixed $content;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否启用(0:禁用,1:启用)',
|
||||
type: 'integer',
|
||||
enum: [0, 1],
|
||||
example: 1,
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|in:0,1')]
|
||||
public int $enabled;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置描述',
|
||||
type: 'string',
|
||||
example: '网站名称配置项',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'string|max:255|nullable')]
|
||||
public ?string $description = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '排序',
|
||||
type: 'integer',
|
||||
example: 1,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'integer|min:0|nullable')]
|
||||
public ?int $sort = null;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\request\system;
|
||||
|
||||
|
||||
use app\schema\request\BaseQueryRequest;
|
||||
use OpenApi\Attributes as OA;
|
||||
use WebmanTech\DTO\Attributes\ValidationRules;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '分组配置查询',
|
||||
description: '获取分组配置接口的请求参数'
|
||||
)]
|
||||
class ConfigGroupQueryRequest extends BaseQueryRequest
|
||||
{
|
||||
#[OA\Property(
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:50')]
|
||||
public string $group_code;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否只获取启用的配置项',
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $enabled_only = true;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否包含配置项的完整元数据',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $with_metadata = false;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否包含配置项的描述信息',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $with_description = false;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否对配置项进行排序',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $sort_configs = false;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置项过滤,多个配置编码用逗号分隔',
|
||||
type: 'string',
|
||||
example: 'site_name,site_url',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'string|max:200|nullable')]
|
||||
public ?string $config_filter = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否返回默认值(当配置项不存在时)',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $with_defaults = false;
|
||||
|
||||
#[OA\Property(
|
||||
description: '默认值映射(JSON格式)',
|
||||
type: 'string',
|
||||
example: '{"site_name":"默认网站名称","site_url":"http://localhost"}',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'json|nullable')]
|
||||
public ?string $default_values = null;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\request\system;
|
||||
|
||||
use app\schema\request\BaseQueryRequest;
|
||||
use OpenApi\Attributes as OA;
|
||||
use WebmanTech\DTO\Attributes\ValidationRules;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '配置查询',
|
||||
description: '配置查询接口的请求参数'
|
||||
)]
|
||||
class ConfigQueryRequest extends BaseQueryRequest
|
||||
{
|
||||
#[OA\Property(
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'string|max:50|nullable')]
|
||||
public ?string $group_code = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置名称',
|
||||
type: 'string',
|
||||
example: '网站名称',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'string|max:100|nullable')]
|
||||
public ?string $name = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否只获取启用的配置项',
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $enabled_only = true;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否包含配置项的完整元数据',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $with_metadata = false;
|
||||
|
||||
#[OA\Property(
|
||||
description: '分组过滤,多个分组用逗号分隔',
|
||||
type: 'string',
|
||||
example: 'site,oss',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'string|max:200|nullable')]
|
||||
public ?string $group_filter = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '按指定字段作为键名(code 或 id)',
|
||||
type: 'string',
|
||||
enum: ['code', 'id'],
|
||||
example: 'code',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'in:code,id|nullable')]
|
||||
public ?string $key_by = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否对分组进行排序',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $sort_groups = false;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否对配置项进行排序',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $sort_configs = false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\request\system;
|
||||
|
||||
use madong\swagger\schema\BaseResponseDTO;
|
||||
use OpenApi\Attributes as OA;
|
||||
use WebmanTech\DTO\Attributes\ValidationRules;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '配置值操作',
|
||||
description: '配置值获取和更新接口的请求参数'
|
||||
)]
|
||||
class ConfigValueRequest extends BaseResponseDTO
|
||||
{
|
||||
#[OA\Property(
|
||||
description: '配置项编码',
|
||||
type: 'string',
|
||||
example: 'site_config',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:50')]
|
||||
public string $code;
|
||||
|
||||
#[OA\Property(
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:50')]
|
||||
public string $group_code;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置键',
|
||||
type: 'string',
|
||||
example: 'site_name',
|
||||
nullable: false
|
||||
)]
|
||||
#[ValidationRules(rules: 'required|string|max:100')]
|
||||
public string $key;
|
||||
|
||||
#[OA\Property(
|
||||
description: '配置值',
|
||||
type: 'mixed',
|
||||
example: '我的网站',
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'nullable')]
|
||||
public mixed $value = null;
|
||||
|
||||
#[OA\Property(
|
||||
description: '是否强制创建配置项',
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
nullable: true
|
||||
)]
|
||||
#[ValidationRules(rules: 'boolean|nullable')]
|
||||
public ?bool $force_create = false;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\response\system;
|
||||
|
||||
use madong\swagger\schema\BaseResponseDTO;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '配置分组响应模型',
|
||||
description: '配置分组接口的返回数据结构'
|
||||
)]
|
||||
class ConfigGroupResponse extends BaseResponseDTO
|
||||
{
|
||||
#[OA\Property(
|
||||
property: 'code',
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site'
|
||||
)]
|
||||
public string $code;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'name',
|
||||
description: '分组名称',
|
||||
type: 'string',
|
||||
example: '网站配置'
|
||||
)]
|
||||
public string $name;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'description',
|
||||
description: '分组描述',
|
||||
type: 'string',
|
||||
example: '网站相关配置项',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $description;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'sort',
|
||||
description: '排序号',
|
||||
type: 'integer',
|
||||
example: 1
|
||||
)]
|
||||
public int $sort;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'enabled',
|
||||
description: '状态(1启用 0禁用)',
|
||||
type: 'integer',
|
||||
enum: [0, 1],
|
||||
example: 1
|
||||
)]
|
||||
public int $enabled;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'config_count',
|
||||
description: '配置项数量',
|
||||
type: 'integer',
|
||||
example: 5
|
||||
)]
|
||||
public int $config_count;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_by',
|
||||
description: '创建人ID',
|
||||
type: 'string',
|
||||
example: '1'
|
||||
)]
|
||||
public string $created_by;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_by',
|
||||
description: '更新人ID',
|
||||
type: 'string',
|
||||
example: '1'
|
||||
)]
|
||||
public string $updated_by;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_at',
|
||||
description: '创建时间',
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-01-01 12:00:00'
|
||||
)]
|
||||
public string $created_at;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_at',
|
||||
description: '更新时间',
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-01-01 12:00:00'
|
||||
)]
|
||||
public string $updated_at;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_date',
|
||||
description: '创建时间(格式化)',
|
||||
type: 'string',
|
||||
example: '2024-01-01 12:00:00',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $created_date;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_date',
|
||||
description: '更新时间(格式化)',
|
||||
type: 'string',
|
||||
example: '2024-01-01 12:00:00',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $updated_date;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\response\system;
|
||||
|
||||
use madong\swagger\schema\BaseResponseDTO;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '配置详情响应模型',
|
||||
description: '配置详情接口的返回数据结构'
|
||||
)]
|
||||
class ConfigResponse extends BaseResponseDTO
|
||||
{
|
||||
#[OA\Property(
|
||||
property: 'id',
|
||||
description: '配置ID',
|
||||
type: 'string',
|
||||
example: '242218273104986112'
|
||||
)]
|
||||
public string $id;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'group_code',
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site'
|
||||
)]
|
||||
public string $group_code;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'code',
|
||||
description: '配置编码',
|
||||
type: 'string',
|
||||
example: 'site_name'
|
||||
)]
|
||||
public string $code;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'name',
|
||||
description: '配置名称',
|
||||
type: 'string',
|
||||
example: '网站名称'
|
||||
)]
|
||||
public string $name;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'content',
|
||||
description: '配置内容',
|
||||
type: 'mixed',
|
||||
example: '我的网站'
|
||||
)]
|
||||
public mixed $content;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'description',
|
||||
description: '配置描述',
|
||||
type: 'string',
|
||||
example: '网站名称配置项',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $description;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'sort',
|
||||
description: '排序号',
|
||||
type: 'integer',
|
||||
example: 1
|
||||
)]
|
||||
public int $sort;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'enabled',
|
||||
description: '状态(1启用 0禁用)',
|
||||
type: 'integer',
|
||||
enum: [0, 1],
|
||||
example: 1
|
||||
)]
|
||||
public int $enabled;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_by',
|
||||
description: '创建人ID',
|
||||
type: 'string',
|
||||
example: '1'
|
||||
)]
|
||||
public string $created_by;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_by',
|
||||
description: '更新人ID',
|
||||
type: 'string',
|
||||
example: '1'
|
||||
)]
|
||||
public string $updated_by;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_at',
|
||||
description: '创建时间',
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-01-01 12:00:00'
|
||||
)]
|
||||
public string $created_at;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_at',
|
||||
description: '更新时间',
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-01-01 12:00:00'
|
||||
)]
|
||||
public string $updated_at;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_date',
|
||||
description: '创建时间(格式化)',
|
||||
type: 'string',
|
||||
example: '2024-01-01 12:00:00',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $created_date;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_date',
|
||||
description: '更新时间(格式化)',
|
||||
type: 'string',
|
||||
example: '2024-01-01 12:00:00',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $updated_date;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: https://madong.tech
|
||||
*/
|
||||
|
||||
namespace app\api\schema\response\system;
|
||||
|
||||
use madong\swagger\schema\BaseResponseDTO;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[OA\Schema(
|
||||
title: '配置值响应模型',
|
||||
description: '配置值操作接口的返回数据结构'
|
||||
)]
|
||||
class ConfigValueResponse extends BaseResponseDTO
|
||||
{
|
||||
#[OA\Property(
|
||||
property: 'code',
|
||||
description: '配置编码',
|
||||
type: 'string',
|
||||
example: 'site_name'
|
||||
)]
|
||||
public string $code;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'group_code',
|
||||
description: '分组编码',
|
||||
type: 'string',
|
||||
example: 'site'
|
||||
)]
|
||||
public string $group_code;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'name',
|
||||
description: '配置名称',
|
||||
type: 'string',
|
||||
example: '网站名称'
|
||||
)]
|
||||
public string $name;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'value',
|
||||
description: '配置值',
|
||||
type: 'mixed',
|
||||
example: '我的网站'
|
||||
)]
|
||||
public mixed $value;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'description',
|
||||
description: '配置描述',
|
||||
type: 'string',
|
||||
example: '网站名称配置项',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $description;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'enabled',
|
||||
description: '状态(1启用 0禁用)',
|
||||
type: 'integer',
|
||||
enum: [0, 1],
|
||||
example: 1
|
||||
)]
|
||||
public int $enabled;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'data_type',
|
||||
description: '数据类型(1:字符串 2:数字 3:布尔 4:数组 5:对象)',
|
||||
type: 'integer',
|
||||
enum: [1, 2, 3, 4, 5],
|
||||
example: 1
|
||||
)]
|
||||
public int $data_type;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_at',
|
||||
description: '创建时间',
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-01-01 12:00:00'
|
||||
)]
|
||||
public string $created_at;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_at',
|
||||
description: '更新时间',
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: '2024-01-01 12:00:00'
|
||||
)]
|
||||
public string $updated_at;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'created_date',
|
||||
description: '创建时间(格式化)',
|
||||
type: 'string',
|
||||
example: '2024-01-01 12:00:00',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $created_date;
|
||||
|
||||
#[OA\Property(
|
||||
property: 'updated_date',
|
||||
description: '更新时间(格式化)',
|
||||
type: 'string',
|
||||
example: '2024-01-01 12:00:00',
|
||||
nullable: true
|
||||
)]
|
||||
public ?string $updated_date;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\validate\auth;
|
||||
|
||||
use core\base\BaseValidate;
|
||||
|
||||
/**
|
||||
* 登录参数验证器
|
||||
*/
|
||||
class LoginValidate extends BaseValidate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected array $rules = [
|
||||
'username' => 'required|min:3|max:50',
|
||||
'password' => 'required|min:6|max:32',
|
||||
'captcha' => 'sometimes|max:10',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证提示信息
|
||||
*/
|
||||
protected array $message = [
|
||||
'username.required' => '用户名不能为空',
|
||||
'username.min' => '用户名长度不能少于3个字符',
|
||||
'username.max' => '用户名长度不能超过50个字符',
|
||||
'password.required' => '密码不能为空',
|
||||
'password.min' => '密码长度不能少于6个字符',
|
||||
'password.max' => '密码长度不能超过32个字符',
|
||||
'captcha.max' => '验证码长度不能超过10个字符',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected array $scene = [
|
||||
'login' => ['username', 'password', 'captcha'],
|
||||
'quick_login' => ['username', 'password'],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\validate\auth;
|
||||
|
||||
use core\base\BaseValidate;
|
||||
|
||||
/**
|
||||
* 手机验证码登录参数验证器
|
||||
*/
|
||||
class MobileLoginValidate extends BaseValidate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected array $rules = [
|
||||
'mobile' => 'required|regex:/^1[3-9]\d{9}$/',
|
||||
'code' => 'required|digits_between:4,6',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证提示信息
|
||||
*/
|
||||
protected array $message = [
|
||||
'mobile.required' => '手机号不能为空',
|
||||
'mobile.regex' => '手机号格式不正确',
|
||||
'code.required' => '验证码不能为空',
|
||||
'code.digits_between' => '验证码长度必须为4-6位',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected array $scene = [
|
||||
'login' => ['mobile', 'code'],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\validate\auth;
|
||||
|
||||
use app\model\member\Member;
|
||||
use core\base\BaseValidate;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* 用户注册参数验证器
|
||||
*/
|
||||
class RegisterValidate extends BaseValidate
|
||||
{
|
||||
/**
|
||||
* 验证规则 - 使用方法定义,支持动态规则
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// 获取路由参数ID(更新场景时用于排除唯一验证)
|
||||
$id = request()->route->param('id');
|
||||
|
||||
return [
|
||||
'username' => [
|
||||
'required',
|
||||
'min:3',
|
||||
'max:50',
|
||||
'alpha_dash',
|
||||
Rule::unique(Member::class, 'username')->ignore($id ?? null, 'id'),
|
||||
],
|
||||
'password' => 'required|min:6|max:32|confirmed:confirm_password',
|
||||
'email' => [
|
||||
'required',
|
||||
'email',
|
||||
Rule::unique(Member::class, 'email')->ignore($id ?? null, 'id'),
|
||||
],
|
||||
'mobile' => [
|
||||
'sometimes',
|
||||
'regex:/^1[3-9]\d{9}$/',
|
||||
Rule::unique(Member::class, 'mobile')->ignore($id ?? null, 'id'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证提示信息
|
||||
*/
|
||||
protected array $message = [
|
||||
'username.required' => '用户名不能为空',
|
||||
'username.min' => '用户名长度不能少于3个字符',
|
||||
'username.max' => '用户名长度不能超过50个字符',
|
||||
'username.alpha_dash' => '用户名只能包含字母、数字、下划线和破折号',
|
||||
'username.unique' => '用户名已存在',
|
||||
'password.required' => '密码不能为空',
|
||||
'password.min' => '密码长度不能少于6个字符',
|
||||
'password.max' => '密码长度不能超过32个字符',
|
||||
'password.confirmed' => '两次输入的密码不一致',
|
||||
'email.email' => '邮箱格式不正确',
|
||||
'email.unique' => '邮箱已存在',
|
||||
'mobile.regex' => '手机号格式不正确',
|
||||
'mobile.unique' => '手机号已存在',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected array $scene = [
|
||||
'register' => ['username', 'password', 'email', 'mobile'],
|
||||
'quick_register' => ['username', 'password'],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\validate\auth;
|
||||
|
||||
use app\enum\member\ThirdPartyPlatform;
|
||||
use core\base\BaseValidate;
|
||||
|
||||
/**
|
||||
* 绑定验证器
|
||||
*/
|
||||
class ThirdPartyAuthValidate extends BaseValidate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected array $rule = [
|
||||
'platform' => 'required|integer|checkPlatform',
|
||||
'openid' => 'required|string|max:255',
|
||||
'unionid' => 'max:255',
|
||||
'nickname' => 'max:50',
|
||||
'avatar' => 'max:255',
|
||||
'gender' => 'integer|between:0,2',
|
||||
'country' => 'max:50',
|
||||
'province' => 'max:50',
|
||||
'city' => 'max:50',
|
||||
'access_token' => 'max:255',
|
||||
'refresh_token' => 'max:255',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证消息
|
||||
*/
|
||||
protected array $message = [
|
||||
'platform.required' => '平台类型不能为空',
|
||||
'platform.integer' => '平台类型必须是整数',
|
||||
'openid.required' => 'OpenID不能为空',
|
||||
'openid.string' => 'OpenID必须是字符串',
|
||||
'openid.max' => 'OpenID不能超过255个字符',
|
||||
'unionid.max' => 'UnionID不能超过255个字符',
|
||||
'nickname.max' => '昵称不能超过50个字符',
|
||||
'avatar.max' => '头像地址不能超过255个字符',
|
||||
'gender.integer' => '性别必须是整数',
|
||||
'gender.between' => '性别只能是0-2',
|
||||
'country.max' => '国家不能超过50个字符',
|
||||
'province.max' => '省份不能超过50个字符',
|
||||
'city.max' => '城市不能超过50个字符',
|
||||
'access_token.max' => '访问令牌不能超过255个字符',
|
||||
'refresh_token.max' => '刷新令牌不能超过255个字符',
|
||||
];
|
||||
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected array $scene = [
|
||||
'bind' => ['platform', 'openid', 'unionid', 'nickname', 'avatar', 'gender', 'country', 'province', 'city', 'access_token', 'refresh_token'],
|
||||
'qrCode' => ['platform'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 检查平台类型是否有效
|
||||
*/
|
||||
protected function checkPlatform($value, $rule, $data): bool
|
||||
{
|
||||
$platform = ThirdPartyPlatform::tryFrom((int)$value);
|
||||
if (!$platform) {
|
||||
$this->error = '无效的平台类型';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user