ubxty/multi-tenant-laravel-permissions 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

ubxty/multi-tenant-laravel-permissions

Composer 安装命令:

composer require ubxty/multi-tenant-laravel-permissions

包简介

A powerful, multi-tenant capable permission handling package for Laravel with role-based access control, wildcard permissions, high-performance caching, and complete domain/subdomain-based tenancy support.

README 文档

README

Latest Version on Packagist Total Downloads License PHP Version

A powerful, multi-tenant capable permission handling package for Laravel with role-based access control, wildcard permissions, high-performance caching, and complete domain/subdomain-based tenancy support.

Built by Ubxty

Table of Contents

Features

Permissions & Roles

  • 🏢 Multi-Tenancy Support - Tenant-scoped roles and permissions out of the box
  • 🔑 UUID Support - First-class UUID support for roles and permissions
  • High-Performance Caching - In-memory + database JSON caching for minimal DB queries
  • 🃏 Wildcard Permissions - Grant access to permission groups with posts.*
  • 🎛️ Permission Scopes - Attach JSON settings/constraints to permissions per role
  • 🛡️ Middleware - Ready-to-use permission and role middleware
  • 🎨 Blade Directives - Convenient @role, @hasrole, @hasanyrole, @hasallroles, @unlessrole

Tenancy

  • 🌐 Domain/Subdomain Resolution - Automatic tenant resolution from tenant.yourapp.com
  • 🔒 Model Scoping - Automatic query filtering with TenantScoped trait
  • 👤 User-Based Resolution - Resolve tenant from authenticated user's tenant_id
  • 🔄 Context Switching - Run code within specific tenant contexts
  • 🛠️ Helper Functions - tenant(), tenant_id(), has_tenant(), run_for_tenant()
  • 🎭 Facade Support - Tenant::get(), Tenant::run(), etc.
  • 📦 Laravel 10, 11 & 12 - Full support for modern Laravel versions

Requirements

  • PHP 8.2+
  • Laravel 10.x, 11.x, or 12.x

Installation

Install the package via Composer:

composer require ubxty/multi-tenant-laravel-permissions

Publish the configuration and migrations:

php artisan vendor:publish --provider="Ubxty\MultiTenantLaravelPermissions\MultiTenantPermissionsServiceProvider"

Run the migrations:

php artisan migrate

Quick Start Guide

Tenancy Setup

Get multi-tenancy working in your app in 5 steps:

Step 1: Configure your tenant model

// config/multi-tenant-permissions.php
'tenancy' => [
    'tenant_model' => App\Models\Company::class,
    'tenant_foreign_key' => 'company_id',
    'subdomain_column' => 'subdomain',
],

Step 2: Create your Tenant model

// app/Models/Company.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Ubxty\MultiTenantLaravelPermissions\Tenancy\Contracts\Tenant;
use Ubxty\MultiTenantLaravelPermissions\Tenancy\Traits\IsTenant;

class Company extends Model implements Tenant
{
    use IsTenant;
    
    protected $fillable = ['name', 'subdomain', 'email'];
}

Step 3: Add middleware (Laravel 11+)

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->web(prepend: [
        \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromHost::class,
    ]);
})

Step 4: Scope your models

// app/Models/Project.php
use Ubxty\MultiTenantLaravelPermissions\Tenancy\Traits\TenantScoped;

class Project extends Model
{
    use TenantScoped;
}

Step 5: Use helpers anywhere

$tenant = tenant();           // Get current tenant model
$id = tenant_id();            // Get current tenant ID
if (has_tenant()) { ... }     // Check if tenant is set

// Run code for a specific tenant
run_for_tenant($tenant, function ($tenant) {
    return Project::all(); // Scoped to $tenant
});

📚 Full Tenancy Documentation →

Permissions Setup

Step 1: Add HasRoles trait to User model

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Ubxty\MultiTenantLaravelPermissions\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
}

Step 2: Create roles and permissions

use App\Models\Role;
use App\Models\Permission;

// Create permissions
$permission = Permission::create(['name' => 'edit articles']);

// Create roles (with optional tenant scoping)
$role = Role::create([
    'name' => 'writer',
    'title' => 'Content Writer',
    'tenant_id' => tenant_id(), // Optional: scope to current tenant
]);

// Assign permission to role
$role->givePermissionTo($permission);

// Assign role to user
$user->assignRole('writer');

Tenancy Deep Dive

Tenancy Configuration Options

The tenancy section in your config file controls all tenant behavior:

// config/multi-tenant-permissions.php
'tenancy' => [
    /*
    |--------------------------------------------------------------------------
    | Tenant Model
    |--------------------------------------------------------------------------
    | The Eloquent model that represents a tenant (Company, Organization, etc.)
    */
    'tenant_model' => App\Models\Company::class,
    
    /*
    |--------------------------------------------------------------------------
    | Foreign Key
    |--------------------------------------------------------------------------
    | The foreign key column used in tenant-scoped models
    | e.g., 'tenant_id', 'company_id', 'organization_id'
    */
    'tenant_foreign_key' => 'company_id',
    
    /*
    |--------------------------------------------------------------------------
    | Identification Columns
    |--------------------------------------------------------------------------
    | Columns used to identify tenants from URLs
    */
    'subdomain_column' => 'subdomain',    // For subdomain.yourapp.com
    'domain_column' => 'domain',          // For full domain identification
    'identifier_column' => 'subdomain',   // Primary identifier
    
    /*
    |--------------------------------------------------------------------------
    | Domain Settings
    |--------------------------------------------------------------------------
    */
    'domain_identification' => false,     // Enable full domain mode (tenant.com)
    'central_domains' => [                // Domains that don't resolve a tenant
        'yourapp.com',
        'www.yourapp.com',
        'localhost',
    ],
    'ignored_subdomains' => ['www', 'api', 'app', 'admin'],
    
    /*
    |--------------------------------------------------------------------------
    | Path Exclusions
    |--------------------------------------------------------------------------
    | URL paths that skip tenant resolution (OAuth callbacks, webhooks)
    */
    'excluded_paths' => [
        'oauth/callback/*',
        'webhooks/*',
        'api/health',
    ],
    
    /*
    |--------------------------------------------------------------------------
    | Security Settings
    |--------------------------------------------------------------------------
    */
    'abort_without_tenant' => false,       // 404 if no tenant on non-central domain
    'no_tenant_message' => 'Organization not found',
    'validate_user_tenant' => true,        // Ensure user belongs to resolved tenant
    'tenant_mismatch_action' => 'logout',  // 'logout', 'abort', 'redirect'
    'hide_data_without_tenant' => true,    // Return empty results if no tenant
    
    /*
    |--------------------------------------------------------------------------
    | Super Admin
    |--------------------------------------------------------------------------
    */
    'super_admin_role' => 'super_admin',   // Role that bypasses tenant restrictions
    
    /*
    |--------------------------------------------------------------------------
    | URL Configuration
    |--------------------------------------------------------------------------
    | Auto-configure app URL based on tenant subdomain
    */
    'configure_urls' => false,
    'url_scheme' => 'https',
    'base_domain' => env('APP_BASE_DOMAIN', 'yourapp.com'),
    
    /*
    |--------------------------------------------------------------------------
    | Debug
    |--------------------------------------------------------------------------
    */
    'debug_logging' => env('TENANCY_DEBUG', false),
],

Middleware Reference

Middleware Alias Description Use Case
SetTenantFromHost tenant.host Resolves tenant from domain/subdomain Web routes
SetTenantFromUser tenant.user Resolves tenant from authenticated user API routes
EnsureTenant tenant.ensure Aborts 403 if no tenant resolved Protected routes
SkipTenantResolution tenant.skip Bypasses tenant resolution OAuth, webhooks

Laravel 11+ Setup (bootstrap/app.php)

use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // Web: Resolve from subdomain
        $middleware->web(prepend: [
            \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromHost::class,
        ]);
        
        // API: Resolve from authenticated user
        $middleware->api(append: [
            \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromUser::class,
        ]);
        
        // Register aliases for route-level usage
        $middleware->alias([
            'tenant.host' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromHost::class,
            'tenant.user' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromUser::class,
            'tenant.ensure' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\EnsureTenant::class,
            'tenant.skip' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SkipTenantResolution::class,
        ]);
    });

Laravel 10 Setup (app/Http/Kernel.php)

protected $middlewareGroups = [
    'web' => [
        \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromHost::class,
        // ... other middleware
    ],
    'api' => [
        \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromUser::class,
        // ... other middleware
    ],
];

protected $middlewareAliases = [
    'tenant.host' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromHost::class,
    'tenant.user' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SetTenantFromUser::class,
    'tenant.ensure' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\EnsureTenant::class,
    'tenant.skip' => \Ubxty\MultiTenantLaravelPermissions\Tenancy\Middleware\SkipTenantResolution::class,
];

Route-Level Usage

// Require tenant context
Route::middleware(['tenant.ensure'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
    Route::resource('projects', ProjectController::class);
});

// Skip tenant resolution (OAuth, webhooks)
Route::middleware(['tenant.skip'])->group(function () {
    Route::get('/oauth/callback/{provider}', [OAuthController::class, 'callback']);
    Route::post('/webhooks/stripe', [StripeWebhookController::class, 'handle']);
});

// Mix: require auth + tenant
Route::middleware(['auth', 'tenant.ensure'])->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
});

Model Scoping

The TenantScoped Trait

Add to any model that should be filtered by tenant:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Ubxty\MultiTenantLaravelPermissions\Tenancy\Traits\TenantScoped;

class Project extends Model
{
    use TenantScoped;

    protected $fillable = ['name', 'description', 'company_id'];
}

What it does automatically:

  1. Filters all queries - Project::all() only returns projects for current tenant
  2. Sets tenant on create - Project::create([...]) auto-sets company_id
  3. Hides data without tenant - If no tenant context, returns empty (security)

Bypassing the Scope

// Get all records across all tenants (admin panels, reports)
$allProjects = Project::withoutTenantScope()->get();

// Query for a specific tenant
$projects = Project::forTenant($tenantId)->get();

// Check if model belongs to current tenant
if ($project->belongsToCurrentTenant()) {
    // ...
}

Custom Foreign Key

class Project extends Model
{
    use TenantScoped;
    
    // Override if different from config
    public function getTenantForeignKeyName(): string
    {
        return 'organization_id';
    }
}

Helper Functions & Facade

Helper Functions

Available globally after package installation:

// Get current tenant model (or null)
$tenant = tenant();

// Get current tenant ID (or null)
$id = tenant_id();

// Check if tenant is set
if (has_tenant()) {
    // In tenant context
}

// Backward compatible alias (for migration from CompanyContext)
$company = company();

// Run code for a specific tenant
$result = run_for_tenant($tenant, function ($tenant) {
    // All queries here are scoped to $tenant
    return Project::count();
});

// Access TenantContext directly
$context = tenancy();

Tenant Facade

use Ubxty\MultiTenantLaravelPermissions\Facades\Tenant;

// Get current tenant
$tenant = Tenant::get();

// Get tenant ID
$id = Tenant::id();

// Check if tenant is set
if (Tenant::check()) { ... }

// Run for tenant
Tenant::run($tenant, function ($tenant) {
    return Project::all();
});

// Register booting callback
Tenant::booting(function ($tenant) {
    Log::info("Booting tenant: {$tenant->name}");
});

// Register ending callback
Tenant::ending(function ($tenant) {
    Log::info("Ending tenant: {$tenant->name}");
});

Context Switching

Run code within a specific tenant's context:

// Using helper
$projects = run_for_tenant($tenant, function ($tenant) {
    // All TenantScoped models query this tenant
    return Project::with('tasks')->get();
});

// Using facade
$count = Tenant::run($tenant, fn($t) => Project::count());

// Using TenantContext directly
use Ubxty\MultiTenantLaravelPermissions\Tenancy\Services\TenantContext;

app(TenantContext::class)->run($tenant, function ($tenant) {
    // ...
});

Note: Context switching is synchronous. The original tenant is restored after the callback.

Permissions & Roles

Creating & Managing Roles and Permissions

use App\Models\Role;
use App\Models\Permission;

// Create permissions
Permission::create(['name' => 'view posts']);
Permission::create(['name' => 'create posts']);
Permission::create(['name' => 'edit posts']);
Permission::create(['name' => 'delete posts']);

// Create a role (optionally scoped to tenant)
$role = Role::create([
    'name' => 'editor',
    'title' => 'Content Editor',
    'tenant_id' => tenant_id(), // null for global roles
]);

// Assign permissions to role
$role->givePermissionTo('view posts', 'create posts', 'edit posts');

// Or sync permissions
$role->syncPermissions(['view posts', 'create posts']);

// Remove a permission
$role->revokePermissionTo('delete posts');

Assigning Roles to Users

$user = User::find(1);

// Assign a single role
$user->assignRole('editor');

// Assign multiple roles
$user->assignRole('editor', 'writer');

// Sync roles (replaces all current roles)
$user->syncRoles(['editor', 'admin']);

// Remove a role
$user->removeRole('writer');

Checking Permissions

// Check specific permission
if ($user->hasPermissionTo('edit posts')) { ... }

// Check via role
if ($user->hasRole('admin')) { ... }

// Check any role
if ($user->hasAnyRole(['admin', 'editor'])) { ... }

// Check all roles
if ($user->hasAllRoles(['editor', 'writer'])) { ... }

// Using Laravel's can() - works with Gate
if ($user->can('edit posts')) { ... }

Tenant-Scoped Permissions

Check permissions for a specific tenant:

// Check if user has role for a specific tenant
if ($user->hasRoleForTenant('admin', $tenantId)) { ... }

// Assign a role for a specific tenant
$user->assignRoleForTenant('manager', $tenantId);

// Get all roles for a tenant
$roles = $user->getRolesForTenant($tenantId);

// Sync roles for a tenant
$user->syncRolesForTenant(['editor', 'writer'], $tenantId);

// Check permission for a tenant
if ($user->hasPermissionForTenant('edit posts', $tenantId)) { ... }

Custom Models (Optional)

Extend the package models for custom logic:

// App\Models\Role.php
namespace App\Models;

use Ubxty\MultiTenantLaravelPermissions\Models\Role as BaseRole;

class Role extends BaseRole
{
    // Your custom logic
}

Update your config to use custom models:

// config/multi-tenant-permissions.php
'models' => [
    'permission' => App\Models\Permission::class,
    'role' => App\Models\Role::class,
],

Wildcard Permissions

Grant access to a group of permissions with wildcards:

// Create specific permissions
Permission::create(['name' => 'posts.create']);
Permission::create(['name' => 'posts.edit']);
Permission::create(['name' => 'posts.delete']);
Permission::create(['name' => 'posts.publish']);

// Create wildcard permission
$wildcard = Permission::create(['name' => 'posts.*']);

// Assign wildcard - grants access to ALL posts.* permissions
$role->givePermissionTo($wildcard);

// Now this role has access to all posts permissions
$user->can('posts.create'); // true
$user->can('posts.edit');   // true
$user->can('posts.delete'); // true
$user->can('posts.publish'); // true

// Multi-level wildcards
Permission::create(['name' => 'admin.*']);         // All admin permissions
Permission::create(['name' => 'admin.users.*']);   // All admin user permissions

Permission Settings & Scopes

Attach JSON settings to permissions when assigning to roles:

$role = Role::findByName('manager');

// Grant permission with specific scope settings
$role->givePermissionToWithSettings('view_projects', [
    'scope' => 'team_only',
    'max_count' => 10,
    'regions' => ['us', 'eu'],
]);

// Update settings
$role->updatePermissionSettings('view_projects', [
    'scope' => 'global',
    'max_count' => 100,
]);

// Retrieve settings
$settings = $role->getPermissionSettings('view_projects');
// ['scope' => 'global', 'max_count' => 100, 'regions' => ['us', 'eu']]

// Use in application logic
if ($user->hasPermissionTo('view_projects')) {
    $settings = $user->getPermissionSettings('view_projects');
    
    if ($settings['scope'] === 'team_only') {
        $projects = Project::where('team_id', $user->team_id)->get();
    }
}

High-Performance Caching

This package implements a dual-layer caching system for optimal performance:

  1. Database JSON Cache: Permissions and roles are cached as JSON in columns on the users table
  2. In-Memory Cache: Parsed once per request, all subsequent checks use RAM

Setup

Add cache columns to your users table:

Schema::table('users', function (Blueprint $table) {
    $table->json('permissions_cache')->nullable();
    $table->json('roles_cache')->nullable();
});

Performance Impact

Scenario Without Cache With Cache
Dashboard with 20 permission checks 20+ DB queries 1 DB query
Kanban board with 50 items 50+ DB queries 1 DB query
API endpoint with multiple checks N DB queries 0 DB queries*

*After first request loads cache into memory.

Cache Configuration

// config/multi-tenant-permissions.php
'cache' => [
    'expiration_time' => 60 * 24, // minutes
    'key' => 'multi_tenant_permissions.cache',
    'store' => 'default',
],

Cache Invalidation

Cache is automatically cleared when:

  • Roles are assigned/removed from user
  • Permissions are assigned/removed from role
  • Role or permission is deleted

Manual cache clear:

php artisan permission:cache-reset

Middleware

Permission & Role Middleware

// Single role
Route::get('/admin', [AdminController::class, 'index'])
    ->middleware('role:admin');

// Multiple roles (any)
Route::get('/manage', [ManageController::class, 'index'])
    ->middleware('role:admin|manager');

// Single permission
Route::post('/posts', [PostController::class, 'store'])
    ->middleware('permission:create posts');

// Multiple permissions (any)
Route::put('/posts/{id}', [PostController::class, 'update'])
    ->middleware('permission:edit posts|manage posts');

// Role OR permission
Route::delete('/posts/{id}', [PostController::class, 'destroy'])
    ->middleware('role_or_permission:admin|delete posts');

Registering Middleware

Laravel 11+ (bootstrap/app.php):

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'role' => \Ubxty\MultiTenantLaravelPermissions\Middleware\RoleMiddleware::class,
        'permission' => \Ubxty\MultiTenantLaravelPermissions\Middleware\PermissionMiddleware::class,
        'role_or_permission' => \Ubxty\MultiTenantLaravelPermissions\Middleware\RoleOrPermissionMiddleware::class,
    ]);
})

Laravel 10 (app/Http/Kernel.php):

protected $middlewareAliases = [
    'role' => \Ubxty\MultiTenantLaravelPermissions\Middleware\RoleMiddleware::class,
    'permission' => \Ubxty\MultiTenantLaravelPermissions\Middleware\PermissionMiddleware::class,
    'role_or_permission' => \Ubxty\MultiTenantLaravelPermissions\Middleware\RoleOrPermissionMiddleware::class,
];

Blade Directives

{{-- Check single role --}}
@role('admin')
    <a href="/admin">Admin Panel</a>
@endrole

{{-- Check any role --}}
@hasanyrole('admin|editor')
    <button>Edit Content</button>
@endhasanyrole

{{-- Check all roles --}}
@hasallroles('editor|writer')
    <p>You can both write and edit!</p>
@endhasallroles

{{-- Negation --}}
@unlessrole('admin')
    <p>You need admin access to see more options.</p>
@endunlessrole

{{-- Laravel's native @can works too --}}
@can('edit posts')
    <button>Edit Post</button>
@endcan

@canany(['edit posts', 'delete posts'])
    <div class="post-actions">...</div>
@endcanany

Commands

# Clear permission cache for all users
php artisan permission:cache-reset

# Show current permission cache status
php artisan permission:show

Upgrading from Existing Tenancy

If you're migrating from a custom tenancy implementation (like CompanyContext), here's how:

Option 1: Use Aliases (Backward Compatible)

// app/Providers/AppServiceProvider.php
public function register()
{
    // Alias the package TenantContext as your old class
    $this->app->alias(
        \Ubxty\MultiTenantLaravelPermissions\Tenancy\Services\TenantContext::class,
        \App\Services\Tenancy\CompanyContext::class
    );
}

Option 2: Search & Replace

  1. Replace imports:

    use App\Services\Tenancy\CompanyContext;
    →
    use Ubxty\MultiTenantLaravelPermissions\Tenancy\Services\TenantContext;
    
  2. Replace method calls:

    // Old
    CompanyContext::get();
    CompanyContext::set($company);
    
    // New (facade)
    Tenant::get();
    Tenant::set($company);
    
    // Or helpers
    tenant();

Option 3: Use Compatibility Helper

The package includes a company() helper for backward compatibility:

// These are equivalent:
$company = company();
$company = tenant();

Configuration

The full configuration file allows customization of:

  • Models: Custom Role and Permission classes
  • Table Names: Custom database table names
  • Column Names: Custom pivot keys, tenant column, cache columns
  • Cache Settings: Expiration time, cache key, cache store
  • Tenancy: Full tenancy configuration (see Tenancy Configuration)

See the published config/multi-tenant-permissions.php for all options.

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email info@ubxty.com or open an issue on GitHub.

Credits

License

The MIT License (MIT). Please see License File for more information.

ubxty/multi-tenant-laravel-permissions 适用场景与选型建议

ubxty/multi-tenant-laravel-permissions 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 24 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 01 月 23 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「authorization」 「security」 「acl」 「laravel」 「subdomain」 「saas」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 ubxty/multi-tenant-laravel-permissions 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 ubxty/multi-tenant-laravel-permissions 我们能提供哪些服务?
定制开发 / 二次开发

基于 ubxty/multi-tenant-laravel-permissions 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 24
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 28
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-01-23