初始化本地仓库

This commit is contained in:
2026-08-31 11:33:59 +08:00
commit 663f74b633
2390 changed files with 402918 additions and 0 deletions
@@ -0,0 +1,52 @@
<?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\middleware;
/**
*+------------------
* madong
*+------------------
* Copyright (c) https://gitee.com/motion-code All rights reserved.
*+------------------
* Author: Mr. April (405784684@qq.com)
*+------------------
* Official Website: http://www.madong.tech
*/
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 跨域中间件
*
* @author Mr.April
* @since 1.0
*/
class AllowCrossOriginMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
$response->withHeaders([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With,Origin',
'Access-Control-Allow-Methods' => 'GET,POST,PUT,DELETE,OPTIONS,PATCH',
]);
return $response;
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace app\middleware;
use core\tool\ApiEncryptService;
use core\tool\Json;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
class ApiEncryptMiddleware implements MiddlewareInterface
{
public function __construct(protected ApiEncryptService $service)
{
}
public function process(Request $request, callable $handler): Response
{
if ($this->service->isEnabled()
&& !$this->service->shouldExcludePath($request->path())
&& $this->service->isEncryptedRequest($request)) {
try {
$data = $this->service->decryptRequest($request);
$this->service->applyDecryptedPayload($request, $data);
} catch (\Throwable $exception) {
return Json::fail($exception->getMessage(), null, 400);
}
}
$response = $handler($request);
if (!$response instanceof Response) {
return $response;
}
if (!$this->service->shouldEncrypt($request)) {
return $response;
}
if (!$this->service->hasActiveSessionKey()) {
return $response;
}
if (!$this->service->isJsonResponse($response)) {
return $response;
}
try {
$body = $response->rawBody();
if ($body === '') {
return $response;
}
$encryptedBody = $this->service->encryptResponseBody($body);
return $response->withBody($encryptedBody)->withHeader('Content-Type', 'application/json');
} catch (\Throwable $exception) {
return Json::fail($exception->getMessage(), null, 500);
}
}
}
@@ -0,0 +1,39 @@
<?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\middleware;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 安装检查中间件
*
* @author Mr.April
* @since 1.0
*/
class CheckInstallMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
// 检查是否已安装
$lockFileExists = file_exists(base_path() . '/install.lock');
if (!$lockFileExists) {
// 如果未安装,重定向到安装页面
return redirect('/install');
}
// 如果已安装,继续处理请求
return $handler($request);
}
}
@@ -0,0 +1,141 @@
<?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\middleware;
use app\adminapi\middleware\helper\SseHelper;
use core\jwt\JwtToken;
use core\tool\Json;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 演示限制操作-实际生产移除
*
* @author Mr.April
* @since 1.0
*/
class DemoEnvRouteRestrictionMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
// 不允许操作的路由
$restrictedRoutes = [
'/adminapi/system/user',
'/adminapi/system/user/\d+',
'/adminapi/system/menu',
'/adminapi/system/menu/\d+',
'/adminapi/system/role',
'/adminapi/system/role/\d+',
'/adminapi/system/dept/',
'/adminapi/system/dept/\d+',
'/adminapi/system/post',
'/adminapi/system/post/\d+',
'/adminapi/system/dict',
'/adminapi/system/dict/\d+',
'/adminapi/system/dict-item',
'/adminapi/system/dict-item/\d+',
'/adminapi/system/recycle-bin',
'/adminapi/system/recycle-bin/\d+',
'/adminapi/system/config',
'/adminapi/dev/crontab',
'/adminapi/dev/crontab/\d+',
'/adminapi/platform/db',
'/adminapi/platform/tenant-subscription',
'/adminapi/platform/tenant-subscription/\d+',
'/adminapi/platform/tenant-member',
'/adminapi/platform/tenant-member/\d+',
'/adminapi/generator/\d+/deploy',
//offline插件限制路由
'/adminapi/market/category',
'/adminapi/market/category/\d+',
'/adminapi/market/apps',
'/adminapi/market/apps/\d+',
'/adminapi/market/apps/publish-version',
'/adminapi/docs',
'/adminapi/docs/\d+',
'/adminapi/doc/content',
'/adminapi/doc/content/\d+',
'/adminapi/ask/answer',
'/adminapi/ask/answer/\d+',
'/adminapi/ask/category',
'/adminapi/ask/category/\d+',
'/adminapi/ask/question',
'/adminapi/ask/question/\d+',
'/adminapi/ask/tag',
'/adminapi/ask/tag/\d+',
'/adminapi/generator/code/\d+/deploy',
//插件服务
'/adminapi/plugin/install',
'/adminapi/plugin',
'/adminapi/plugin/uninstall',
//WEB终端服务
'/adminapi/terminal',
'/adminapi/terminal/\d+',
];
$currentPath = $request->path();
$method = $request->method();
// 检查当前路径是否在限制的路由中
foreach ($restrictedRoutes as $pattern) {
if (preg_match("#^$pattern$#", $currentPath)) {
// 检查是否为 root 用户
if ($this->isRootUser()) {
return $handler($request);
}
// 检查是否为SSE请求
$isSseRequest = SseHelper::isSseRequest($request);
// SSE请求需要单独处理
if ($isSseRequest) {
return SseHelper::sendSseErrorViaConnection($request, '演示环境,不支持当前操作');
}
// 非SSE的写操作(PUT, POST, DELETE)也限制
if (in_array(strtoupper($method), ['PUT', 'POST', 'DELETE'])) {
return Json::fail('演示环境,不支持当前操作');
}
// 对于GET请求,如果不是SSE,默认允许(演示环境可能允许查看)
}
}
// 继续处理请求
return $handler($request);
}
/**
* 检查是否为 root 用户
*/
private function isRootUser(): bool
{
$payload = (new JwtToken())->getPayloadFromRequest();
$extra = is_array($payload) ? ($payload['extra'] ?? null) : (is_object($payload) ? ($payload->extra ?? null) : null);
if (!empty($payload['id'])) {
return (int)$payload['id'] == -1;
}
if (is_object($extra)) {
return ($extra->user_name ?? '') === 'root';
}
if (is_array($extra)) {
return ($extra['user_name'] ?? '') === 'root';
}
return false;
}
}
@@ -0,0 +1,75 @@
<?php
namespace app\middleware;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* 文件访问权限中间件
* 用于控制上传文件的访问权限
*/
class FileAccessMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
// $path = $request->path;
// $path = str_replace('/upload/', '', $path);
//
// // 公开文件类型,无需权限验证
// $publicExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'ico', 'css', 'js', 'woff', 'woff2', 'ttf', 'eot'];
//
// $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
//
// if (in_array($extension, $publicExtensions)) {
// return $handler($request);
// }
// // 私密文件需要身份验证
// if (!$request->user) {
// return json([
// 'code' => 401,
// 'msg' => '请先登录',
// 'data' => null
// ], 401);
// }
//
// // 根据文件路径进行权限控制
// // 示例:某些敏感文件只能管理员访问
// if (str_starts_with($path, 'private/')) {
// $user = $request->user;
// if (!isset($user['roles']) || !in_array('R_SUPER', $user['roles'] ?? [])) {
// return json([
// 'code' => 403,
// 'msg' => '无权访问',
// 'data' => null
// ], 403);
// }
// }
//
// // 用户个人文件访问控制
// // 例如:/uploads/avatar/user_123.png 只有 user_123 或管理员可以访问
// if (preg_match('/^(avatar|document|private)\/(\d+)_/', $path, $matches)) {
// $userId = $matches[2];
// $user = $request->user;
//
// // 管理员可访问所有文件
// if (isset($user['roles']) && in_array('R_SUPER', $user['roles'] ?? [])) {
// return $handler($request);
// }
//
// // 普通用户只能访问自己的文件
// if ($user['id'] != $userId) {
// return json([
// 'code' => 403,
// 'msg' => '无权访问此文件',
// 'data' => null
// ], 403);
// }
// }
return $handler($request);
}
}
+49
View File
@@ -0,0 +1,49 @@
<?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\middleware;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* @author Mr.April
* @since 1.0
*/
class Lang implements MiddlewareInterface
{
private const LANG_MAP = [
'en' => ['en', 'en_US', 'en_AU', 'en_CA', 'en_GB', 'en-US', 'en-AU', 'en-CA', 'en-GB'],
'zh_CN' => ['zh_CN', 'zh_HK', 'zh_TW', 'zh_SG', 'zh-CN', 'zh-HK', 'zh-TW', 'zh-SG'],
];
/**
* @throws \Exception
*/
public function process(Request $request, callable $handler): Response
{
$lang = $request->header('Accept-Language', config('app.lang'));
$langPrefix = substr($lang, 0, 2);
if (array_key_exists($langPrefix, self::LANG_MAP)) {
$locale = self::LANG_MAP[$langPrefix][0]; // 默认选择第一个匹配的语言
} else {
$locale = config('app.lang');
}
// 设置会话中的语言
session('lang', $locale);
locale($locale);
return $handler($request);
}
}
@@ -0,0 +1,258 @@
<?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\middleware;
use app\service\admin\gateway\RetaLimiterService;
use app\service\admin\gateway\RetaRestrictionsService;
use core\cache\CacheService;
use support\Container;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use Webman\RateLimiter\Limiter;
use Webman\RateLimiter\RateLimitException;
/**
* 限访限流中间件
*
* 功能特性:
* 1. IP-based 黑名单访问限制
* 2. 基于路径的限流控制
* 3. 支持多种匹配模式:精确匹配、通配符匹配、正则表达式匹配
* 4. 支持多种限流类型:IP限流、用户限流
* 5. 配置化限流规则(作为数据库规则的 fallback)
* 6. 缓存优化(1小时缓存过期)
*
* 配置示例(config/rate_limiter.php):
* [
* [
* 'path' => '/api/*',
* 'methods' => '*',
* 'match_type' => 'wildcard',
* 'limit_type' => 'ip',
* 'limit_value' => 100,
* 'ttl' => 60,
* 'message' => '请求过于频繁,请稍后再试'
* ]
* ]
*
* @author Mr.April
* @since 1.0
*/
class RateLimiterMiddleware implements MiddlewareInterface
{
/**
* process
*
* @param \Webman\Http\Request $request
* @param callable $handler
*
* @return \Webman\Http\Response
*/
public function process(Request $request, callable $handler): Response
{
$path = $request->path();
//跳过对应的安装接口
if (in_array($path, ['/'])) {
return $handler($request);
}
$ip = $request->getRealIp();
$method = $request->method();
$userId = $this->getUserId($request);
//限访处理
$rateRestrictions = $this->getRateRestrictions($ip, $path, $method);
if (!empty($rateRestrictions)) {
throw new RateLimitException($rateRestrictions['message'] ?? '拒绝访问');
}
$rateRule = $this->getRateLimit($path, $method);
//限流处理
if (!empty($rateRule)) {
$key = $this->generateLimitKey($rateRule, $ip, $userId, $method, $path);
Limiter::check(md5($key), $rateRule['limit_value'], $rateRule['ttl'] ?? 1, $rateRule['message']);
//可以实现更多自定义的限流规则
}
return $handler($request);
}
/**
* 获取用户ID
*
* @param Request $request
*
* @return int|null
*/
protected function getUserId(Request $request): ?int
{
return null;
}
/**
* 生成限流键
*
* @param array $rule
* @param string $ip
* @param int|null $userId
* @param string $method
* @param string $path
*
* @return string
*/
protected function generateLimitKey(array $rule, string $ip, ?int $userId, string $method, string $path): string
{
$limitType = $rule['limit_type'] ?? 'ip';
switch ($limitType) {
case 'user':
if ($userId) {
return $userId . $method . $path;
}
// 如果没有用户ID,回退到IP
return $ip . $method . $path;
case 'ip':
default:
return $ip . $method . $path;
}
}
/**
* 获取匹配的限流规则(缓存优先)
*
* @param string $path
* @param string $methods
*
* @return array|null
*/
protected function getRateLimit(string $path, string $methods = 'GET'): ?array
{
$cache = Container::make(CacheService::class);
$service = Container::make(RetaLimiterService::class);
$rules = $cache->remember(RetaLimiterService::CACHE_KEY, function () use ($service) {
return $service->selectList(['enabled' => 1], '*', 0, 0, 'priority desc')->toArray();
});
if (empty($rules)) {
return $this->getConfigBasedLimit($path, $methods);
}
foreach ($rules as $rule) {
if ($this->matchPath($rule['path'], $path, $rule['match_type'] ?? 'exact') &&
($rule['methods'] === '*' || $rule['methods'] === $methods)) {
return $rule;
}
}
return $this->getConfigBasedLimit($path, $methods);
}
/**
* 获取配置-based限流规则
*
* @param string $path
* @param string $methods
*
* @return array|null
*/
protected function getConfigBasedLimit(string $path, string $methods = 'GET'): ?array
{
$config = config('rate_limiter', []);
if (empty($config)) {
return null;
}
foreach ($config as $rule) {
if (isset($rule['path'], $rule['limit_value']) &&
$this->matchPath($rule['path'], $path, $rule['match_type'] ?? 'exact') &&
(($rule['methods'] ?? '*') === '*' || ($rule['methods'] ?? 'GET') === $methods)) {
return $rule;
}
}
return null;
}
/**
* 获取限访规则
*
* @param string $ip
* @param string $path
* @param string $methods
*
* @return array|null
*/
protected function getRateRestrictions(string $ip, string $path, string $methods = 'GET'): ?array
{
$currentTime = time();
$cache = Container::make(CacheService::class);
$service = Container::make(RetaRestrictionsService::class);
$rules = $cache->remember(RetaRestrictionsService::CACHE_KEY,function () use ($service) {
return $service->selectList(['enabled' => 1], '*', 0, 0, 'priority desc')->toArray();
});
if (empty($rules)) {
return null;
}
foreach ($rules as $rule) {
// IP匹配
if (!empty($rule['ip']) && $rule['ip'] !== $ip) {
continue;
}
// 路径匹配
if (!$this->matchPath($rule['path'], $path, $rule['match_type'] ?? 'exact')) {
continue;
}
// 方法匹配
if ($rule['methods'] !== '*' && $rule['methods'] !== $methods) {
continue;
}
$hasStartTime = isset($rule['start_time']) && $rule['start_time'] > 0;
$hasEndTime = isset($rule['end_time']) && $rule['end_time'] > 0;
// 规则未生效(未来时间)或已过期
if (($hasStartTime && $currentTime < $rule['start_time']) ||
($hasEndTime && $currentTime > $rule['end_time'])) {
continue;
}
return $rule;
}
return null;
}
/**
* 路径匹配
*
* @param string $rulePath
* @param string $requestPath
* @param string $matchType
*
* @return bool
*/
protected function matchPath(string $rulePath, string $requestPath, string $matchType = 'exact'): bool
{
switch ($matchType) {
case 'exact':
return $rulePath === $requestPath;
case 'wildcard':
$pattern = str_replace('*', '.*', preg_quote($rulePath, '/'));
return preg_match('/^' . $pattern . '$/', $requestPath) === 1;
case 'regex':
return preg_match('/^' . $rulePath . '$/', $requestPath) === 1;
default:
return $rulePath === $requestPath;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
<?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
*/
namespace app\middleware;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
/**
* Class StaticFile
* @package app\middleware
*/
class StaticFile implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
if (str_contains($request->path(), '/.')) {
return response('<h1>403 forbidden</h1>', 403);
}
/** @var Response $response */
$response = $handler($request);
// Add cross domain HTTP header
/*$response->withHeaders([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
]);*/
return $response;
}
}