mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
feat(database): 添加用户角色权限系统及相关监控功能
- 创建用户表(users)包含基本信息和认证字段 - 创建角色表(roles)用于权限控制 - 创建权限表(permissions)定义系统权限 - 创建用户角色关联表(user_roles)建立用户与角色关系 - 创建角色权限关联表(role_permissions)建立角色与权限关系 - 创建迁移记录表(migrations)追踪数据库变更 - 添加AdminController提供管理员面板功能 - 实现系统监控、配置管理、缓存清理等功能 - 添加AOP切面编程支持的各种通知类型 - 实现告警管理AlertManager支持多渠道告警 - 添加文档注解接口规范
This commit is contained in:
24
fendx-framework/fendx-file/composer.json
Normal file
24
fendx-framework/fendx-file/composer.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "fendx/file",
|
||||
"description": "FendxPHP File Module - 统一存储接口",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lawson",
|
||||
"email": "lawson@fendx.cn"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"fendx/common": "^1.0",
|
||||
"fendx/core": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fendx\\File\\": "src/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
198
fendx-framework/fendx-file/src/FileManager.php
Normal file
198
fendx-framework/fendx-file/src/FileManager.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\File;
|
||||
|
||||
use Fendx\File\Storage\StorageInterface;
|
||||
use Fendx\File\Storage\LocalStorage;
|
||||
use Fendx\Common\Exception\BusinessException;
|
||||
|
||||
final class FileManager
|
||||
{
|
||||
private static ?self $instance = null;
|
||||
private StorageInterface $storage;
|
||||
private array $config;
|
||||
|
||||
private function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->storage = $this->createStorage($config);
|
||||
}
|
||||
|
||||
public static function getInstance(array $config = []): self
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self($config);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function createStorage(array $config): StorageInterface
|
||||
{
|
||||
$type = $config['type'] ?? 'local';
|
||||
|
||||
switch ($type) {
|
||||
case 'local':
|
||||
$root = $config['root'] ?? runtime_path('storage');
|
||||
$urlPrefix = $config['url_prefix'] ?? '';
|
||||
return new LocalStorage($root, $urlPrefix);
|
||||
|
||||
default:
|
||||
throw new BusinessException(500, 'Unsupported storage type: ' . $type);
|
||||
}
|
||||
}
|
||||
|
||||
public function put(string $path, string $content, array $options = []): bool
|
||||
{
|
||||
return $this->storage->put($path, $content, $options);
|
||||
}
|
||||
|
||||
public function putFile(string $path, string $localPath, array $options = []): bool
|
||||
{
|
||||
return $this->storage->putFile($path, $localPath, $options);
|
||||
}
|
||||
|
||||
public function get(string $path): ?string
|
||||
{
|
||||
return $this->storage->get($path);
|
||||
}
|
||||
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
return $this->storage->exists($path);
|
||||
}
|
||||
|
||||
public function delete(string $path): bool
|
||||
{
|
||||
return $this->storage->delete($path);
|
||||
}
|
||||
|
||||
public function copy(string $from, string $to): bool
|
||||
{
|
||||
return $this->storage->copy($from, $to);
|
||||
}
|
||||
|
||||
public function move(string $from, string $to): bool
|
||||
{
|
||||
return $this->storage->move($from, $to);
|
||||
}
|
||||
|
||||
public function size(string $path): ?int
|
||||
{
|
||||
return $this->storage->size($path);
|
||||
}
|
||||
|
||||
public function lastModified(string $path): ?int
|
||||
{
|
||||
return $this->storage->lastModified($path);
|
||||
}
|
||||
|
||||
public function url(string $path): ?string
|
||||
{
|
||||
return $this->storage->url($path);
|
||||
}
|
||||
|
||||
public function list(string $directory): array
|
||||
{
|
||||
return $this->storage->list($directory);
|
||||
}
|
||||
|
||||
public function allFiles(string $directory): array
|
||||
{
|
||||
return $this->storage->allFiles($directory);
|
||||
}
|
||||
|
||||
public function deleteDirectory(string $directory): bool
|
||||
{
|
||||
return $this->storage->deleteDirectory($directory);
|
||||
}
|
||||
|
||||
public function upload(string $key, array $options = []): ?string
|
||||
{
|
||||
if (!isset($_FILES[$key]) || $_FILES[$key]['error'] !== UPLOAD_ERR_OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $_FILES[$key];
|
||||
$originalName = $file['name'];
|
||||
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
|
||||
// 生成唯一文件名
|
||||
$filename = uniqid() . '.' . $extension;
|
||||
|
||||
// 支持自定义目录
|
||||
$directory = $options['directory'] ?? 'uploads/' . date('Y/m/d');
|
||||
$path = $directory . '/' . $filename;
|
||||
|
||||
if ($this->putFile($path, $file['tmp_name'], $options)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function uploadMultiple(string $key, array $options = []): array
|
||||
{
|
||||
if (!isset($_FILES[$key])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = $_FILES[$key];
|
||||
$paths = [];
|
||||
|
||||
// 处理多个文件上传
|
||||
if (is_array($files['name'])) {
|
||||
$count = count($files['name']);
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ($files['error'][$i] !== UPLOAD_ERR_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmpName = $files['tmp_name'][$i];
|
||||
$originalName = $files['name'][$i];
|
||||
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
|
||||
$filename = uniqid() . '.' . $extension;
|
||||
$directory = $options['directory'] ?? 'uploads/' . date('Y/m/d');
|
||||
$path = $directory . '/' . $filename;
|
||||
|
||||
if ($this->putFile($path, $tmpName, $options)) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 单个文件
|
||||
if ($files['error'] === UPLOAD_ERR_OK) {
|
||||
$path = $this->upload($key, $options);
|
||||
if ($path) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
public function setStorage(StorageInterface $storage): void
|
||||
{
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
public function getStorage(): StorageInterface
|
||||
{
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('runtime_path')) {
|
||||
function runtime_path(string $path = ''): string
|
||||
{
|
||||
return dirname(__DIR__, 4) . '/runtime/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
230
fendx-framework/fendx-file/src/Storage/LocalStorage.php
Normal file
230
fendx-framework/fendx-file/src/Storage/LocalStorage.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\File\Storage;
|
||||
|
||||
use Fendx\Common\Exception\BusinessException;
|
||||
|
||||
final class LocalStorage implements StorageInterface
|
||||
{
|
||||
private string $root;
|
||||
private string $urlPrefix;
|
||||
|
||||
public function __construct(string $root, string $urlPrefix = '')
|
||||
{
|
||||
$this->root = rtrim($root, '/');
|
||||
$this->urlPrefix = rtrim($urlPrefix, '/');
|
||||
|
||||
if (!is_dir($this->root)) {
|
||||
if (!mkdir($this->root, 0755, true) && !is_dir($this->root)) {
|
||||
throw new BusinessException(500, 'Failed to create storage directory');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function put(string $path, string $content, array $options = []): bool
|
||||
{
|
||||
$fullPath = $this->getFullPath($path);
|
||||
$this->ensureDirectory(dirname($fullPath));
|
||||
|
||||
$result = file_put_contents($fullPath, $content);
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
public function putFile(string $path, string $localPath, array $options = []): bool
|
||||
{
|
||||
if (!file_exists($localPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fullPath = $this->getFullPath($path);
|
||||
$this->ensureDirectory(dirname($fullPath));
|
||||
|
||||
return copy($localPath, $fullPath);
|
||||
}
|
||||
|
||||
public function get(string $path): ?string
|
||||
{
|
||||
$fullPath = $this->getFullPath($path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return file_get_contents($fullPath);
|
||||
}
|
||||
|
||||
public function exists(string $path): bool
|
||||
{
|
||||
return file_exists($this->getFullPath($path));
|
||||
}
|
||||
|
||||
public function delete(string $path): bool
|
||||
{
|
||||
$fullPath = $this->getFullPath($path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return unlink($fullPath);
|
||||
}
|
||||
|
||||
public function copy(string $from, string $to): bool
|
||||
{
|
||||
$fromPath = $this->getFullPath($from);
|
||||
$toPath = $this->getFullPath($to);
|
||||
|
||||
if (!file_exists($fromPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->ensureDirectory(dirname($toPath));
|
||||
|
||||
return copy($fromPath, $toPath);
|
||||
}
|
||||
|
||||
public function move(string $from, string $to): bool
|
||||
{
|
||||
$fromPath = $this->getFullPath($from);
|
||||
$toPath = $this->getFullPath($to);
|
||||
|
||||
if (!file_exists($fromPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->ensureDirectory(dirname($toPath));
|
||||
|
||||
return rename($fromPath, $toPath);
|
||||
}
|
||||
|
||||
public function size(string $path): ?int
|
||||
{
|
||||
$fullPath = $this->getFullPath($path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return filesize($fullPath);
|
||||
}
|
||||
|
||||
public function lastModified(string $path): ?int
|
||||
{
|
||||
$fullPath = $this->getFullPath($path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return filemtime($fullPath);
|
||||
}
|
||||
|
||||
public function url(string $path): ?string
|
||||
{
|
||||
if (!$this->urlPrefix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->urlPrefix . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
public function list(string $directory): array
|
||||
{
|
||||
$fullPath = $this->getFullPath($directory);
|
||||
|
||||
if (!is_dir($fullPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = [];
|
||||
$items = scandir($fullPath);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item === '.' || $item === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemPath = $directory . '/' . $item;
|
||||
$files[] = [
|
||||
'path' => $itemPath,
|
||||
'name' => $item,
|
||||
'type' => is_dir($this->getFullPath($itemPath)) ? 'directory' : 'file',
|
||||
'size' => $this->size($itemPath),
|
||||
'last_modified' => $this->lastModified($itemPath)
|
||||
];
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function allFiles(string $directory): array
|
||||
{
|
||||
$fullPath = $this->getFullPath($directory);
|
||||
|
||||
if (!is_dir($fullPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = [];
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($fullPath, \RecursiveDirectoryIterator::SKIP_DOTS)
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$relativePath = str_replace($this->root . '/', '', $file->getPathname());
|
||||
$files[] = $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function deleteDirectory(string $directory): bool
|
||||
{
|
||||
$fullPath = $this->getFullPath($directory);
|
||||
|
||||
if (!is_dir($fullPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$files = array_diff(scandir($fullPath), ['.', '..']);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$path = $fullPath . '/' . $file;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$this->deleteDirectory($directory . '/' . $file);
|
||||
} else {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($fullPath);
|
||||
}
|
||||
|
||||
private function getFullPath(string $path): string
|
||||
{
|
||||
return $this->root . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
private function ensureDirectory(string $directory): void
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
if (!mkdir($directory, 0755, true) && !is_dir($directory)) {
|
||||
throw new BusinessException(500, 'Failed to create directory: ' . $directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getRoot(): string
|
||||
{
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
public function getUrlPrefix(): string
|
||||
{
|
||||
return $this->urlPrefix;
|
||||
}
|
||||
}
|
||||
33
fendx-framework/fendx-file/src/Storage/StorageInterface.php
Normal file
33
fendx-framework/fendx-file/src/Storage/StorageInterface.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\File\Storage;
|
||||
|
||||
interface StorageInterface
|
||||
{
|
||||
public function put(string $path, string $content, array $options = []): bool;
|
||||
|
||||
public function putFile(string $path, string $localPath, array $options = []): bool;
|
||||
|
||||
public function get(string $path): ?string;
|
||||
|
||||
public function exists(string $path): bool;
|
||||
|
||||
public function delete(string $path): bool;
|
||||
|
||||
public function copy(string $from, string $to): bool;
|
||||
|
||||
public function move(string $from, string $to): bool;
|
||||
|
||||
public function size(string $path): ?int;
|
||||
|
||||
public function lastModified(string $path): ?int;
|
||||
|
||||
public function url(string $path): ?string;
|
||||
|
||||
public function list(string $directory): array;
|
||||
|
||||
public function allFiles(string $directory): array;
|
||||
|
||||
public function deleteDirectory(string $directory): bool;
|
||||
}
|
||||
Reference in New Issue
Block a user