7 |
8 |
9 |
15 |
16 | 操作成功!
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | get('/market-manager', function (Request $request) {
25 | // return $request->user();
26 | // });
27 |
28 | Route::prefix('market-manager')->group(function() {
29 | Route::post('/', [ApiController\MarketManagerApiController::class, 'install'])->name('app.install');
30 | Route::post('app-update', [ApiController\MarketManagerApiController::class, 'update'])->name('app.update');
31 | Route::post('app-uninstall', [ApiController\MarketManagerApiController::class, 'uninstall'])->name('app.uninstall');
32 | });
33 |
--------------------------------------------------------------------------------
/app/Models/Traits/FsidTrait.php:
--------------------------------------------------------------------------------
1 | {$model->getFsidKey()} = $model->{$model->getFsidKey()} ?? static::generateFsid(8);
14 | }
15 | });
16 | }
17 |
18 | // generate fsid
19 | public static function generateFsid($digit): string
20 | {
21 | $fsid = Str::random($digit);
22 |
23 | $checkFsid = static::fsid($fsid)->first();
24 |
25 | if (! $checkFsid) {
26 | return $fsid;
27 | } else {
28 | $newFsid = Str::random($digit);
29 | $checkNewFsid = static::fsid($newFsid)->first();
30 | if (! $checkNewFsid) {
31 | return $newFsid;
32 | }
33 | }
34 |
35 | return static::generateFsid($digit + 1);
36 | }
37 |
38 | public function scopeFsid($query, string $fsid): mixed
39 | {
40 | return $query->where($this->getFsidKey(), $fsid);
41 | }
42 |
43 | /**
44 | * fsid
45 | */
46 | // public function getFsidKey()
47 | // {
48 | // return 'fsid';
49 | // }
50 | }
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | group(function() {
24 | Route::get('/', [WebController\MarketManagerController::class, 'index'])->name('market-manager.index');
25 | Route::get('setting', [WebController\MarketManagerController::class, 'showSettingView'])->name('market-manager.setting');
26 | Route::post('setting', [WebController\MarketManagerController::class, 'saveSetting']);
27 | });
28 |
29 | // without VerifyCsrfToken
30 | // Route::prefix('market-manager')->withoutMiddleware([
31 | // \App\Http\Middleware\EncryptCookies::class,
32 | // \App\Http\Middleware\VerifyCsrfToken::class,
33 | // ])->group(function() {
34 | // Route::get('/', [WebController\MarketManagerController::class, 'index']);
35 | // });
--------------------------------------------------------------------------------
/app/Providers/ExceptionServiceProvider.php:
--------------------------------------------------------------------------------
1 | reportable($this->reportable());
27 | }
28 |
29 | if (method_exists($handler, 'renderable')) {
30 | $handler->renderable($this->renderable());
31 | }
32 | }
33 |
34 | /**
35 | * Register a reportable callback.
36 | *
37 | * @param callable $reportUsing
38 | * @return \Illuminate\Foundation\Exceptions\ReportableHandler
39 | */
40 | public function reportable()
41 | {
42 | return function (\Throwable $e) {
43 | //
44 | };
45 | }
46 |
47 | /**
48 | * Register a renderable callback.
49 | *
50 | * @param callable $renderUsing
51 | * @return $this
52 | */
53 | public function renderable()
54 | {
55 | return function (\Throwable $e) {
56 | //
57 | };
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/Providers/CommandServiceProvider.php:
--------------------------------------------------------------------------------
1 | load($commandsDirectory);
29 | }
30 | }
31 |
32 | /**
33 | * Register all of the commands in the given directory.
34 | *
35 | * @param array|string $paths
36 | *
37 | * @return void
38 | */
39 | protected function load($paths)
40 | {
41 | $paths = array_unique(Arr::wrap($paths));
42 |
43 | $paths = array_filter($paths, function ($path) {
44 | return is_dir($path);
45 | });
46 |
47 | if (empty($paths)) {
48 | return;
49 | }
50 |
51 | $commands = [];
52 | foreach ((new Finder)->in($paths)->files() as $command) {
53 | $commandClass = Str::before(self::class, 'Providers\\') . 'Console\\Commands\\' . str_replace('.php', '', $command->getBasename());
54 | if (class_exists($commandClass)) {
55 | $commands[] = $commandClass;
56 | }
57 | }
58 |
59 | $this->commands($commands);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plugins-world/market-manager",
3 | "description": "MarketManager plugin made by fresns",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Jarvis Tang",
8 | "email": "jarvis.okay@gmail.com",
9 | "homepage": "https://github.com/jarvis-tang",
10 | "role": "Creator"
11 | },
12 | {
13 | "name": "mouyong",
14 | "email": "my24251325@gmail.com",
15 | "homepage": "https://github.com/mouyong",
16 | "role": "Developer"
17 | }
18 | ],
19 | "autoload": {
20 | "psr-4": {
21 | "Plugins\\MarketManager\\": "app",
22 | "Plugins\\MarketManager\\Database\\Factories\\": "database/factories/",
23 | "Plugins\\MarketManager\\Database\\Seeders\\": "database/seeders/"
24 | }
25 | },
26 | "autoload-dev": {
27 | "psr-4": {
28 | "Plugins\\MarketManager\\Tests\\": "tests/"
29 | }
30 | },
31 | "require": {
32 | "php": ">= 8.0",
33 | "plugins-world/laravel-config": "^2.0",
34 | "fresns/cmd-word-manager": "^1.0 | dev-master",
35 | "fresns/market-manager": "^4.0 | 4.x-dev",
36 | "fresns/plugin-manager": "^3.0 | 3.x-dev",
37 | "plugins-world/php-support": "dev-master"
38 | },
39 | "require-dev": {
40 | "orangehill/iseed": "^3.0"
41 | },
42 | "extra": {
43 | "laravel": {
44 | "providers": [
45 | "Plugins\\MarketManager\\Providers\\MarketManagerServiceProvider",
46 | "Plugins\\MarketManager\\Providers\\ExceptionServiceProvider"
47 | ]
48 | }
49 | },
50 | "repositories": {
51 | "cmd-word-manager": {
52 | "type": "vcs",
53 | "url": "git@gitee.com:fresns/cmd-word-manager.git"
54 | },
55 | "plugin-manager": {
56 | "type": "vcs",
57 | "url": "git@gitee.com:fresns/plugin-manager.git"
58 | },
59 | "market-manager": {
60 | "type": "vcs",
61 | "url": "git@gitee.com:fresns/market-manager.git"
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/app/Http/Controllers/MarketManagerController.php:
--------------------------------------------------------------------------------
1 | has('status')) { // 1-active, 0-inactive
25 | $where['is_enabled'] = \request('status') == 'active' ? 1 : 0;
26 | }
27 |
28 | $apps = App::query()->where($where)->get();
29 |
30 | return view('MarketManager::index', [
31 | 'configs' => $configs,
32 | 'apps' => $apps,
33 | ]);
34 | }
35 |
36 | public function showSettingView()
37 | {
38 | $itemKeys = [
39 | 'market_server_host',
40 | 'system_url',
41 | 'settings_path',
42 | 'install_datetime',
43 | 'build_type',
44 | 'github_token',
45 | ];
46 |
47 | $configs = ConfigHelper::fresnsConfigByItemKeys($itemKeys);
48 |
49 | return view('MarketManager::setting', [
50 | 'configs' => $configs,
51 | ]);
52 | }
53 |
54 | public function saveSetting()
55 | {
56 | \request()->validate([
57 | 'market_server_host' => 'required|url',
58 | 'system_url' => 'nullable|url',
59 | 'settings_path' => 'required|string',
60 | 'github_token' => 'nullable|string',
61 | ]);
62 |
63 | $itemKeys = [
64 | 'market_server_host',
65 | 'system_url',
66 | 'settings_path',
67 | 'github_token',
68 | ];
69 |
70 | ConfigUtility::updateConfigs($itemKeys, 'market_manager');
71 |
72 | return redirect(route('market-manager.setting'));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | where('fskey', $fskey)->first();
47 |
48 | // Cache::put($cacheKey, $pluginModel, now()->addMinutes(30));
49 | // }
50 |
51 | // $pluginHost = $pluginModel?->plugin_host ?? '';
52 |
53 | // $host = str_replace(['http://', 'https://'], '', rtrim($pluginHost, '/'));
54 | // }
55 | // } catch (\Throwable $e) {
56 | // info("get plugin host failed: " . $e->getMessage());
57 | // }
58 |
59 | Route::group([
60 | 'domain' => $host,
61 | ], function () {
62 | $this->mapApiRoutes();
63 |
64 | $this->mapWebRoutes();
65 | });
66 | }
67 |
68 | /**
69 | * Define the "web" routes for the application.
70 | *
71 | * These routes all receive session state, CSRF protection, etc.
72 | *
73 | * @return void
74 | */
75 | protected function mapWebRoutes()
76 | {
77 | Route::middleware(['web', 'market-manager.auth'])
78 | ->group(dirname(__DIR__, 2) . '/routes/web.php');
79 | }
80 |
81 | /**
82 | * Define the "api" routes for the application.
83 | *
84 | * These routes are typically stateless.
85 | *
86 | * @return void
87 | */
88 | protected function mapApiRoutes()
89 | {
90 | Route::prefix('api', 'market-manager.auth')
91 | ->middleware('api')
92 | ->group(dirname(__DIR__, 2) . '/routes/api.php');
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/Providers/MarketManagerServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
27 | dirname(__DIR__, 2) . '/resources/assets/' => public_path('assets/plugins/MarketManager'),
28 | ]);
29 |
30 | $this->registerTranslations();
31 | $this->registerConfig();
32 | $this->registerViews();
33 |
34 | $this->loadMigrationsFrom(dirname(__DIR__, 2) . '/database/migrations');
35 |
36 | Route::aliasMiddleware('market-manager.auth', MarketManagerAuthenticate::class);
37 | Route::aliasMiddleware('plugin.auth', PluginAuthenticate::class);
38 | $this->app->register(RouteServiceProvider::class);
39 |
40 | MarketUtility::macroMarketHeaders();
41 |
42 | // Event::listen(UserCreated::class, UserCreatedListener::class);
43 | }
44 |
45 | /**
46 | * Register the service provider.
47 | *
48 | * @return void
49 | */
50 | public function register()
51 | {
52 | // if ($this->app->runningInConsole()) {
53 | $this->app->register(CommandServiceProvider::class);
54 | // }
55 | }
56 |
57 | /**
58 | * Register config.
59 | *
60 | * @return void
61 | */
62 | protected function registerConfig()
63 | {
64 | $this->mergeConfigFrom(
65 | dirname(__DIR__, 2) . '/config/market-manager.php', 'market-manager'
66 | );
67 |
68 | config([
69 | 'plugins.composer' => config('market-manager.composer'),
70 | ]);
71 | }
72 |
73 | /**
74 | * Register views.
75 | *
76 | * @return void
77 | */
78 | public function registerViews()
79 | {
80 | $this->loadViewsFrom(dirname(__DIR__, 2) . '/resources/views', 'MarketManager');
81 | }
82 |
83 | /**
84 | * Register translations.
85 | *
86 | * @return void
87 | */
88 | public function registerTranslations()
89 | {
90 | $this->loadTranslationsFrom(dirname(__DIR__, 2) . '/resources/lang', 'MarketManager');
91 | }
92 |
93 | /**
94 | * Get the services provided by the provider.
95 | *
96 | * @return array
97 | */
98 | public function provides()
99 | {
100 | return [];
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/Utilities/MarketUtility.php:
--------------------------------------------------------------------------------
1 | static::version,
19 | 'versionInt' => static::versionInt,
20 | ];
21 | }
22 |
23 | public static function macroMarketHeaders()
24 | {
25 | Http::macro('market', function () {
26 | $httpProxy = config('app.http_proxy');
27 | $http = Http::baseUrl(MarketUtility::getApiHost())
28 | ->withHeaders(MarketUtility::getMarketHeaders())
29 | ->withOptions([
30 | 'proxy' => [
31 | 'http' => $httpProxy,
32 | 'https' => $httpProxy,
33 | ],
34 | ]);
35 |
36 | return $http;
37 | });
38 | }
39 |
40 | public static function getApiHost()
41 | {
42 | $apiHost = ConfigHelper::fresnsConfigByItemKey('market_server_host');
43 |
44 | if (!$apiHost) {
45 | return static::defaultMarketHost;
46 | }
47 |
48 | return $apiHost;
49 | }
50 |
51 | public static function getMarketHeaders(): array
52 | {
53 | $appConfig = ConfigHelper::fresnsConfigByItemKeys([
54 | 'install_datetime',
55 | 'build_type',
56 | 'site_url',
57 | 'site_name',
58 | 'site_desc',
59 | 'site_copyright',
60 | 'default_timezone',
61 | 'default_language',
62 | ]);
63 |
64 | $isHttps = \request()->getScheme() === 'https';
65 |
66 | $systemUrl = $appConfig['system_url'] ?? config('app.url');
67 |
68 | config([
69 | 'app.url' => trim($systemUrl, '/'),
70 | ]);
71 |
72 | return [
73 | 'panelLangTag' => App::getLocale(),
74 | 'installDatetime' => $appConfig['install_datetime'],
75 | 'buildType' => $appConfig['build_type'],
76 | 'version' => static::currentVersion()['version'],
77 | 'versionInt' => static::currentVersion()['versionInt'],
78 | 'httpSsl' => $isHttps ? 1 : 0,
79 | 'httpHost' => \request()->getHost(),
80 | 'httpPort' => \request()->getPort(),
81 | 'systemUrl' => config('app.url'),
82 | 'siteUrl' => $appConfig['site_url'],
83 | 'siteName' => base64_encode($appConfig['site_name']),
84 | 'siteDesc' => base64_encode($appConfig['site_desc']),
85 | 'siteCopyright' => base64_encode($appConfig['site_copyright']),
86 | 'siteTimezone' => $appConfig['default_timezone'],
87 | 'siteLanguage' => $appConfig['default_language'],
88 | ];
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/Utils/LaravelCache.php:
--------------------------------------------------------------------------------
1 | LaravelCache::NULL_KEY_NUM) {
42 | return null;
43 | }
44 |
45 | // 使用默认缓存时间
46 | if (is_callable($cacheTime)) {
47 | $callable = $cacheTime;
48 |
49 | // 防止缓存雪崩,对不同数据随机缓存时间。从半小时到 1天
50 | $index = rand(0, 100) % count(LaravelCache::DEFAULT_CACHE_TIME);
51 | $cacheSeconds = LaravelCache::DEFAULT_CACHE_TIME[$index];
52 | $cacheTime = now()->addSeconds($cacheSeconds);
53 | }
54 |
55 | if (!is_callable($callable)) {
56 | return null;
57 | }
58 |
59 | if ($forever) {
60 | $data = Cache::rememberForever($cacheKey, fn () => $callable($cacheTime, $cacheKey));
61 | } else {
62 | $data = Cache::remember($cacheKey, $cacheTime, fn() => $callable($cacheTime, $cacheKey));
63 | }
64 |
65 | if (!$data) {
66 | Cache::pull($cacheKey);
67 |
68 | $currentCacheKeyNullNum = (int) Cache::get($nullCacheKey);
69 |
70 | Cache::put($nullCacheKey, ++$currentCacheKeyNullNum, now()->addSeconds(LaravelCache::NULL_KEY_CACHE_TIME));
71 | }
72 |
73 | return $data;
74 | }
75 |
76 | /**
77 | * 执行指定函数并永久缓存
78 | *
79 | * @param string $cacheKey
80 | * @param callable|Carbon|null $cacheTime
81 | * @param callable|null $callable
82 | * @return mixed
83 | */
84 | public static function rememberForever(string $cacheKey, callable|Carbon|null $cacheTime = null, ?callable $callable = null)
85 | {
86 | return LaravelCache::remember($cacheKey, $cacheTime, $callable, true);
87 | }
88 |
89 | /**
90 | * 转发调用
91 | *
92 | * @param mixed $method
93 | * @param mixed $args
94 | * @return mixed
95 | */
96 | public static function __callStatic(mixed $method, mixed $args)
97 | {
98 | return Cache::$method(...$args);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/resources/views/setting.blade.php:
--------------------------------------------------------------------------------
1 | @extends('MarketManager::layouts.master')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
Market Manage 设置
8 |
9 |
56 |
57 |
58 |
59 | @endsection
--------------------------------------------------------------------------------
/app/MarketManager.php:
--------------------------------------------------------------------------------
1 | checkAuth([
39 | 'request' => $request,
40 | 'next' => $next,
41 | ]);
42 |
43 | return $resp->isSuccessResponse();
44 | }
45 | }
46 |
47 | if (app()->environment(['local', 'develop'])) {
48 | return $next($request);
49 | }
50 |
51 | return abort(403);
52 | })($request, $next);
53 | }
54 |
55 | /**
56 | * Determine if the given request can access the MarketManager dashboard.
57 | *
58 | * @param \Illuminate\Http\Request $request
59 | * @return bool
60 | */
61 | public static function checkPluginAuth($request, $next)
62 | {
63 | return (static::$pluginAuthUsing ?: function ($request, $next) {
64 | $pluginsCmds = \FresnsCmdWord::all();
65 | if (array_key_exists('Manager', $pluginsCmds)) {
66 | $marketManagerCmds = $pluginsCmds['Manager'];
67 |
68 | if (array_key_exists('checkPluginAuth', $marketManagerCmds)) {
69 | /** @var \Fresns\CmdWordManager\CmdWordResponse */
70 | $resp = \FresnsCmdWord::plugin('Manager')->checkPluginAuth([
71 | 'request' => $request,
72 | 'next' => $next,
73 | ]);
74 |
75 | return $resp->isSuccessResponse();
76 | }
77 | }
78 |
79 | return $next($request);
80 | })($request, $next);
81 | }
82 |
83 | /**
84 | * Set the callback that should be used to authenticate MarketManager users.
85 | *
86 | * @param \Closure $callback
87 | * @return static
88 | */
89 | public static function auth(Closure $callback)
90 | {
91 | static::$authUsing = $callback;
92 |
93 | return new static;
94 | }
95 |
96 | /**
97 | * Set the callback that should be used to authenticate MarketManager users.
98 | *
99 | * @param \Closure $callback
100 | * @return static
101 | */
102 | public static function pluginAuth(Closure $callback)
103 | {
104 | static::$pluginAuthUsing = $callback;
105 |
106 | return new static;
107 | }
108 | }
--------------------------------------------------------------------------------
/app/Utilities/StrUtility.php:
--------------------------------------------------------------------------------
1 | str_repeat('*', 3),
23 | $len > 3 => str_repeat('*', bcsub($len, 3)),
24 | };
25 |
26 | $offset = match (true) {
27 | default => 1,
28 | $len > 3 => 3,
29 | };
30 |
31 | $maskUser = substr_replace($user, $mask, $offset);
32 |
33 | return "{$maskUser}{$domain}";
34 | }
35 |
36 | // number
37 | public static function maskNumber(?int $number = null): ?string
38 | {
39 | if (empty($number)) {
40 | return null;
41 | }
42 |
43 | $len = mb_strlen($number);
44 | if ($len <= 4) {
45 | return $number;
46 | }
47 |
48 | $head = substr($number, 0, 2);
49 | $tail = substr($number, -2);
50 | $starCount = strlen($number) - 4;
51 | $star = str_repeat('*', $starCount);
52 |
53 | return $head.$star.$tail;
54 | }
55 |
56 | // name
57 | public static function maskName(?string $name = null): ?string
58 | {
59 | if (empty($name)) {
60 | return null;
61 | }
62 |
63 | $len = mb_strlen($name);
64 | if ($len < 1) {
65 | return $name;
66 | }
67 |
68 | $last = mb_substr($name, -1, 1);
69 | $lastName = str_repeat('*', $len - 1);
70 |
71 | return $lastName.$last;
72 | }
73 |
74 | // qualify table name
75 | public static function qualifyTableName(mixed $model): string
76 | {
77 | $modelName = $model;
78 |
79 | if (class_exists($model)) {
80 | $model = new $model;
81 |
82 | if (! ($model instanceof Model)) {
83 | throw new \LogicException("unknown table name of $model");
84 | }
85 |
86 | $modelName = $model->getTable();
87 | }
88 |
89 | return str_replace(config('database.connections.mysql.prefix'), '', $modelName);
90 | }
91 |
92 | // qualify url
93 | public static function qualifyUrl(?string $uri = null, ?string $domain = null): ?string
94 | {
95 | if (empty($uri)) {
96 | return null;
97 | }
98 |
99 | if (str_contains($uri, '://')) {
100 | return $uri;
101 | }
102 |
103 | if (! $domain) {
104 | return sprintf('%s/%s', config('app.url'), ltrim($uri, '/'));
105 | }
106 |
107 | return sprintf('%s/%s', rtrim($domain, '/'), ltrim($uri, '/'));
108 | }
109 |
110 | // Whether it is a pure number
111 | public static function isPureInt(mixed $variable): bool
112 | {
113 | return preg_match('/^\d+?$/', $variable);
114 | }
115 |
116 | public static function slug(?string $text): ?string
117 | {
118 | if (!$text) {
119 | return null;
120 | }
121 |
122 | if (preg_match("/^[A-Za-z\s]+$/", $text)) {
123 | $slug = Str::slug($text, '-');
124 | } else {
125 | $slug = rawurlencode($text);
126 | }
127 |
128 | $slug = Str::lower($slug);
129 |
130 | return $slug;
131 | }
132 | }
--------------------------------------------------------------------------------
/database/migrations/init_market_manager_config.php:
--------------------------------------------------------------------------------
1 | SubscribeUtility::TYPE_USER_ACTIVITY,
20 | // 'fskey' => 'market_manager',
21 | // 'cmdWord' => 'stats',
22 | ];
23 |
24 | protected $fresnsConfigItems = [
25 | // [
26 | // 'item_tag' => 'market_manager',
27 | // 'item_key' => 'access_key',
28 | // 'item_type' => 'string',
29 | // 'item_value' => null,
30 | // ],
31 | [
32 | 'item_tag' => 'market_manager',
33 | 'item_key' => 'market_server_host',
34 | 'item_type' => 'string',
35 | 'item_value' => 'https://marketplace.plugins-world.cn',
36 | ],
37 | [
38 | 'item_tag' => 'market_manager',
39 | 'item_key' => 'system_url',
40 | 'item_type' => 'string',
41 | 'item_value' => null,
42 | ],
43 | [
44 | 'item_tag' => 'market_manager',
45 | 'item_key' => 'settings_path',
46 | 'item_type' => 'string',
47 | 'item_value' => null,
48 | ],
49 | [
50 | 'item_tag' => 'market_manager',
51 | 'item_key' => 'install_datetime',
52 | 'item_type' => 'string',
53 | 'item_value' => null,
54 | ],
55 | [
56 | 'item_tag' => 'market_manager',
57 | 'item_key' => 'build_type',
58 | 'item_type' => 'number',
59 | 'item_value' => 1,
60 | ],
61 | [
62 | 'item_tag' => 'market_manager',
63 | 'item_key' => 'github_token',
64 | 'item_type' => 'string',
65 | 'item_value' => null,
66 | ],
67 | ];
68 |
69 | /**
70 | * Run the migrations.
71 | */
72 | public function up(): void
73 | {
74 | foreach ($this->fresnsConfigItems as $index => $configItem) {
75 | if ($configItem['item_key'] == 'install_datetime') {
76 | $configItem['item_value'] = date('Y-m-d H:i:s');
77 | }
78 |
79 | if ($configItem['item_key'] == 'settings_path') {
80 | $contents = file_get_contents(dirname(__DIR__, 2) . '/plugin.json');
81 | $data = json_decode($contents, true);
82 |
83 | $configItem['item_value'] = $data['settingsPath'];
84 | }
85 |
86 | $this->fresnsConfigItems[$index] = $configItem;
87 | }
88 |
89 | // addSubscribeItem
90 | // \FresnsCmdWord::plugin()->addSubscribeItem($this->fresnsWordBody);
91 |
92 | // addKeyValues to Config table
93 | ConfigUtility::addFresnsConfigItems($this->fresnsConfigItems);
94 | }
95 |
96 | /**
97 | * Reverse the migrations.
98 | */
99 | public function down(): void
100 | {
101 | // removeSubscribeItem
102 | // \FresnsCmdWord::plugin()->removeSubscribeItem($this->fresnsWordBody);
103 |
104 | // removeKeyValues from Config table
105 | ConfigUtility::removeFresnsConfigItems($this->fresnsConfigItems);
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/resources/views/commons/head.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ $title ?? '' }}
7 |
8 | @stack('headcss')
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
100 |
101 | @stack('headjs')
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 插件管理器
2 |
3 | [](https://packagist.org/packages/plugins-world/market-manager)
4 | [](https://packagist.org/packages/plugins-world/market-manager)
5 | [](https://packagist.org/packages/plugins-world/market-manager) [](https://packagist.org/packages/plugins-world/market-manager)
6 | [](https://packagist.org/packages/plugins-world/market-manager)
7 |
8 | - 应用市场:https://marketplace.plugins-world.cn
9 |
10 |
11 | ## 安装
12 |
13 | 你可以通过 composer 安装这个扩展包,与应用插件不同的是,此扩展会安装到 `vendor/` 目录下。
14 |
15 | ⚠️注意,安装的时候,会询问是以下内容,请输入: `y`
16 | > wikimedia/composer-merge-plugin contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
17 | >
18 | > Do you trust "wikimedia/composer-merge-plugin" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?]
19 |
20 |
21 | 下面是初始化项目并引入插件管理器的操作步骤:
22 |
23 | 1. 创建项目
24 | ```bash
25 | # 创建新项目 laravel-test
26 | composer create-project --prefer-dist laravel/laravel laravel-test
27 | # 进入项目目录
28 | cd laravel-test
29 | # 初始化 git 仓库
30 | git init
31 | git add .
32 | git commit -m "feat: Init."
33 | # 配置应用市场管理器, 插件管理器, 命令字管理器的安装源(此步骤仅在需要最新管理器功能时配置)
34 | # composer config repositories.plugin-manager vcs https://gitee.com/fresns/plugin-manager
35 | # composer config repositories.market-manager vcs https://gitee.com/fresns/market-manager
36 | # composer config repositories.cmd-word-manager vcs https://gitee.com/fresns/cmd-word-manager
37 | ```
38 |
39 | 2. 修改依赖包约束
40 | ⚠️注意:需要确保项目 `composer.json` 允许安装稳定性依赖为 `dev` 的扩展包
41 | ```js
42 | {
43 | ...
44 | "minimum-stability": "dev",
45 | "prefer-stable": true,
46 | ...
47 | }
48 | ```
49 |
50 | 3. 安装插件管理器,并完成初始化。
51 | ```bash
52 | # 安装 Laravel 的应用市场管理器
53 | composer require plugins-world/market-manager
54 | # 配置 .env 中的数据库与项目信息
55 | APP_NAME=
56 | APP_URL=
57 |
58 | DB_HOST=
59 | DB_DATABASE=
60 | DB_USERNAME=
61 | DB_PASSWORD=
62 |
63 | # 执行迁移,增加 plugins 表
64 | php artisan migrate
65 |
66 | # 提交仓库变动。方便查看 saas 初始化的文件
67 | git add .
68 | git commit -m "feat: Install laravel market-manager."
69 | ```
70 |
71 | 4. 正确配置项目权限
72 | 宝塔:`chown www:www -R /path/to/laravel-test`
73 |
74 | 5. 访问:`http://域名/market-manager`,查看安装结果
75 |
76 |
77 | ## 挑选插件
78 |
79 | 访问 `http://域名/market-manager`,打开左侧的插件市场菜单,并在其中查找需要的插件。进入插件详情页后点击安装。
80 |
81 | ⚠️注意:独立打开插件市场,不会显示安装按钮。
82 |
83 |
84 | ## 插件管理页的访问限制
85 |
86 | ⚠️注意:
87 | - MarketManager 默认只允许 `local` 与 `develop` 环境访问。
88 | - Plugin 默认全部放行访问。
89 | - 如果需要限制访问权限,可以在 `app/Providers/AppServiceProvider.php` 的 `boot` 函数中,通过指定 MarketManager 如何进行认证来完成限制,参考如下:
90 |
91 |
92 | 操作步骤:
93 | 1. 安装 SanctumAuth 插件
94 | 2. 通过 artisan 命令 app:user-add 创建一个初始账号
95 | 3. 正确配置主程序。下面是配置参考
96 |
97 | - 通过 `AppServiceProvider` 授权
98 | ```php
99 | \Plugins\MarketManager\MarketManager::auth(function ($request, $next) {
100 | // return \Illuminate\Support\Facades\Auth::onceBasic() ?: $next($request);
101 | });
102 |
103 |
104 | \Plugins\MarketManager\MarketManager::pluginAuth(function ($request, $next) {
105 | // return \Illuminate\Support\Facades\Auth::onceBasic() ?: $next($request);
106 | });
107 |
108 | # 配置首页默认路由,并进行 basic 认证(需要在数据库创建 users 信息,通过 email, password 登录)
109 | Route::domain(parse_url(config('app.url'), PHP_URL_HOST))->get('/', function () {
110 | return redirect('/market-manager');
111 | return view('welcome');
112 | })->middleware('auth.basic');
113 | ```
114 |
115 | - 通过命令字 `\FresnsCmdWord::plugin('Manager')->checkAuth([])` 授权 MarketManager 访问,需要自行实现 `Manager` 的 `checkAuth` 命令字。
116 | - 通过命令字 `\FresnsCmdWord::plugin('Manager')->checkPluginAuth([])` 授权 Plugin 访问,需要自行实现 `Manager` 的 `checkPluginAuth` 命令字。
117 |
118 |
119 | ## 说明
120 |
121 | 1. 符合插件管理器开发规范的插件可以被安装
122 | 2. 插件安装方式:
123 | 1. 从 url 安装 zip 插件
124 | 2. 从 github url 安装私有插件
125 | 3. 从 github url 安装公开插件
126 | 4. 从 下载站获取 url 安装插件:https://apps.plugins-world.cn
127 | 5. 从 https://packagist.org 通过安命令完成插件安装
128 | 6. 上传 zip 插件到服务器进行安装
129 | 7. 从指定目录安装
130 | 8. 从插件市场安装插件(开发中)
131 | 3. 目前,官方插件代码仓库为:https://github.com/plugins-world/plugins
132 | 4. 项目需要配置好权限,避免插件无法下载,解压,安装。插件的安装需要 web 程序的用户读取、创建目录。
133 | 5. 每次安装后,插件默认关闭,需要进行启用操作。
134 |
135 |
136 | ## 遇到问题
137 |
138 | 通过此处联系我:https://plugins-world.cn/contributing/feedback.html
139 |
--------------------------------------------------------------------------------
/app/Utilities/PluginUtility.php:
--------------------------------------------------------------------------------
1 | value('plugin_host');
28 | });
29 | }
30 |
31 | // Get the plugin access url
32 | public static function fresnsPluginUrlByFskey(?string $fskey = null): ?string
33 | {
34 | if (empty($fskey)) {
35 | return null;
36 | }
37 |
38 | $cacheKey = "fresns_plugin_url_{$fskey}";
39 |
40 | return LaravelCache::remember($cacheKey, function () use ($fskey) {
41 | $plugin = App::where('fskey', $fskey)->first();
42 |
43 | $pluginUrl = null;
44 | if ($plugin) {
45 | $url = empty($plugin->plugin_host) ? config('app.url') : $plugin->plugin_host;
46 |
47 | $pluginUrl = StrUtility::qualifyUrl($plugin->access_path, $url);
48 | }
49 |
50 | return $pluginUrl;
51 | });
52 | }
53 |
54 | // Get the url of the plugin that has replaced the custom parameters
55 | public static function fresnsPluginUsageUrl(string $fskey, ?string $parameter = null): ?string
56 | {
57 | $url = PluginUtility::fresnsPluginUrlByFskey($fskey);
58 |
59 | if (empty($parameter) || empty($url)) {
60 | return $url;
61 | }
62 |
63 | return str_replace('{parameter}', $parameter, $url);
64 | }
65 |
66 | // get plugin callback
67 | public static function fresnsPluginCallback(string $fskey, string $ulid): array
68 | {
69 | $callbackArr = [
70 | 'code' => 0,
71 | 'data' => [],
72 | ];
73 |
74 | $plugin = App::where('fskey', $fskey)->first();
75 |
76 | if (empty($plugin)) {
77 | $callbackArr['code'] = 32101;
78 |
79 | return $callbackArr;
80 | }
81 |
82 | if (! $plugin->is_enabled) {
83 | $callbackArr['code'] = 32102;
84 |
85 | return $callbackArr;
86 | }
87 |
88 | $callback = TempCallbackContent::where('ulid', $ulid)->first();
89 |
90 | if (empty($callback)) {
91 | $callbackArr['code'] = 32303;
92 |
93 | return $callbackArr;
94 | }
95 |
96 | if ($callback->is_used) {
97 | $callbackArr['code'] = 32204;
98 |
99 | return $callbackArr;
100 | }
101 |
102 | if (empty($callback->content)) {
103 | $callbackArr['code'] = 32206;
104 |
105 | return $callbackArr;
106 | }
107 |
108 | $timeDifference = time() - strtotime($callback->created_at);
109 | // 30 minutes
110 | if ($timeDifference > 1800) {
111 | $callbackArr['code'] = 32203;
112 |
113 | return $callbackArr;
114 | }
115 |
116 | $callback->is_used = 1;
117 | $callback->used_plugin_fskey = $fskey;
118 | $callback->save();
119 |
120 | $data = [
121 | 'ulid' => $callback->ulid,
122 | 'type' => $callback->type,
123 | 'content' => $callback->content,
124 | ];
125 |
126 | $callbackArr['data'] = $data;
127 |
128 | return $callbackArr;
129 | }
130 |
131 | // get plugin version
132 | public static function fresnsPluginVersionByFskey(string $fskey): ?string
133 | {
134 | $cacheKey = "fresns_plugin_version_{$fskey}";
135 |
136 | $version = LaravelCache::remember($cacheKey, function () use ($fskey) {
137 | return App::where('fskey', $fskey)->value('version');
138 | });
139 |
140 | return $version;
141 | }
142 |
143 | // get plugin upgrade code
144 | public static function fresnsPluginUpgradeCodeByFskey(string $fskey): ?string
145 | {
146 | $upgradeCode = App::where('fskey', $fskey)->value('upgrade_code');
147 |
148 | if (empty($upgradeCode)) {
149 | return null;
150 | }
151 |
152 | return $upgradeCode;
153 | }
154 | }
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000000_create_apps_table.php:
--------------------------------------------------------------------------------
1 | comment('应用表');
17 |
18 | $table->integer('id', true);
19 | $table->string('fskey', 64)->unique('plugin_fskey');
20 | $table->unsignedTinyInteger('type')->default(1);
21 | $table->string('name', 64);
22 | $table->string('description');
23 | $table->string('version', 16);
24 | $table->string('author', 64);
25 | $table->string('author_link', 128)->nullable();
26 | $table->json('panel_usages')->nullable();
27 | $table->string('app_host', 128)->nullable();
28 | $table->string('access_path')->nullable();
29 | $table->string('settings_path', 128)->nullable();
30 | $table->boolean('is_upgrade')->default(0);
31 | $table->string('upgrade_code', 32)->nullable();
32 | $table->string('upgrade_version', 16)->nullable();
33 | $table->unsignedTinyInteger('is_enabled')->default(0);
34 | $table->timestamp('created_at')->useCurrent();
35 | $table->timestamp('updated_at')->nullable();
36 | $table->softDeletes();
37 | });
38 |
39 | Schema::create('app_usages', function (Blueprint $table) {
40 | $table->comment('插件关联使用表');
41 |
42 | $table->increments('id');
43 | $table->unsignedTinyInteger('usage_type')->index('app_usage_type');
44 | $table->string('app_fskey', 64);
45 | $table->string('name', 128);
46 | $table->unsignedBigInteger('icon_file_id')->nullable();
47 | $table->string('icon_file_url')->nullable();
48 | $table->string('scene', 16)->nullable();
49 | $table->unsignedTinyInteger('editor_toolbar')->default(0);
50 | $table->unsignedTinyInteger('editor_number')->nullable();
51 | $table->unsignedTinyInteger('is_group_admin')->nullable()->default(0);
52 | $table->unsignedInteger('group_id')->nullable()->index('plugin_usage_group_id');
53 | $table->string('roles', 128)->nullable();
54 | $table->string('parameter', 128)->nullable();
55 | $table->unsignedSmallInteger('sort_order')->default(9);
56 | $table->unsignedTinyInteger('can_delete')->default(1);
57 | $table->unsignedTinyInteger('is_enabled')->default(1);
58 | $table->timestamp('created_at')->useCurrent();
59 | $table->timestamp('updated_at')->nullable();
60 | $table->softDeletes();
61 | });
62 |
63 | Schema::create('app_badges', function (Blueprint $table) {
64 | $table->comment('插件徽标数据表');
65 |
66 | $table->bigIncrements('id');
67 | $table->string('app_fskey', 64);
68 | $table->unsignedBigInteger('user_id');
69 | $table->unsignedTinyInteger('display_type')->default(1)->comment('1.红点 / 2.数字 / 3.文字');
70 | $table->unsignedSmallInteger('value_number')->nullable();
71 | $table->string('value_text', 8)->nullable();
72 | $table->timestamp('created_at')->useCurrent();
73 | $table->timestamp('updated_at')->nullable();
74 | $table->softDeletes();
75 |
76 | $table->unique(['app_fskey', 'user_id'], 'app_badge_user_id');
77 | });
78 |
79 | Schema::create('temp_callback_contents', function (Blueprint $table) {
80 | $table->comment('回调内容表');
81 |
82 | $table->bigIncrements('id');
83 | $table->string('app_fskey', 64);
84 | $table->string('key', 64);
85 | $table->unsignedSmallInteger('type')->default(1);
86 | switch (config('database.default')) {
87 | case 'pgsql':
88 | $table->jsonb('content')->nullable();
89 | break;
90 |
91 | case 'sqlsrv':
92 | $table->nvarchar('content', 'max')->nullable();
93 | break;
94 |
95 | default:
96 | $table->json('content')->nullable();
97 | }
98 | $table->unsignedTinyInteger('retention_days')->default(1);
99 | $table->unsignedTinyInteger('is_enabled')->default(0);
100 | $table->timestamp('created_at')->useCurrent();
101 | $table->timestamp('updated_at')->nullable();
102 | $table->softDeletes();
103 | });
104 | }
105 |
106 | /**
107 | * Reverse the migrations.
108 | */
109 | public function down(): void
110 | {
111 | Schema::dropIfExists('apps');
112 | Schema::dropIfExists('app_usages');
113 | Schema::dropIfExists('app_badges');
114 | Schema::dropIfExists('temp_callback_contents');
115 | }
116 | };
117 |
--------------------------------------------------------------------------------
/app/Http/Controllers/MarketManagerApiController.php:
--------------------------------------------------------------------------------
1 | validate([
25 | 'install_type' => 'nullable', // plugin, theme
26 | 'install_method' => 'required|in:app_fskey,app_package,app_url,app_directory,app_zipball',
27 |
28 | 'app_fskey' => 'required_if:install_method,app_fskey',
29 | 'app_package' => 'required_if:install_method,app_package',
30 | 'app_url' => 'required_if:install_method,app_url',
31 | 'app_directory' => 'required_if:install_method,app_directory',
32 | 'app_zipball' => 'required_if:install_method,app_zipball',
33 | ]);
34 |
35 | $install_type = \request('install_type', 'plugin');
36 | $install_method = \request('install_method');
37 | $installValue = \request($install_method);
38 |
39 | switch ($install_method) {
40 | // fskey
41 | case 'app_fskey':
42 | case 'app_package':
43 | case 'app_url':
44 | if ($install_method == 'app_url') {
45 | $configs = ConfigHelper::fresnsConfigByItemKeys([
46 | 'github_token',
47 | ]);
48 |
49 | // 下载 github 私有插件
50 | if (str_starts_with($installValue, 'https://github.com')) {
51 | $installValue = str_replace('https://', 'https://' . $configs['github_token'] . ':@', $installValue);
52 |
53 | if (!str_ends_with($installValue, 'zip')) {
54 | $installValue = $installValue . '/archive/master.zip';
55 | }
56 | }
57 | }
58 |
59 | // market-manager
60 | $exitCode = Artisan::call('market:require', [
61 | 'fskey' => $installValue,
62 | ]);
63 | $output = Artisan::output();
64 | break;
65 |
66 | // directory
67 | case 'app_directory':
68 | $pluginDirectory = $installValue;
69 |
70 | // plugin-manager or theme-manager
71 | $exitCode = Artisan::call("{$install_type}:install", [
72 | 'path' => $pluginDirectory,
73 | '--is_dir' => true,
74 | ]);
75 | $output = Artisan::output();
76 | break;
77 |
78 | // app_zipball
79 | case 'app_zipball':
80 | $pluginZipball = null;
81 | $file = $installValue;
82 |
83 | if ($file && $file->isValid()) {
84 | $dir = storage_path('extensions');
85 | $filename = $file->hashName();
86 | $file->move($dir, $filename);
87 |
88 | $pluginZipball = "$dir/$filename";
89 | }
90 |
91 | if (empty($pluginZipball)) {
92 | return $this->fail('插件安装失败,请选择插件压缩包');
93 | }
94 |
95 | // plugin-manager or theme-manager
96 | $exitCode = Artisan::call("{$install_type}:install", [
97 | 'path' => $pluginZipball,
98 | ]);
99 | $output = Artisan::output();
100 | break;
101 | }
102 |
103 | if ($exitCode != 0) {
104 | if ($output == '') {
105 | $output = "请查看安装日志 storage/logs/laravel.log";
106 | }
107 |
108 | return \response($output . "\n 安装失败");
109 | }
110 |
111 | return \response($output . "\n 安装成功");
112 | }
113 |
114 | public function update()
115 | {
116 | \request()->validate([
117 | 'plugin' => 'required|string',
118 | 'is_enabled' => 'required|boolean'
119 | ]);
120 |
121 | $fskey = \request('plugin');
122 |
123 | if (\request()->get('is_enabled') != 0) {
124 | $exitCode = Artisan::call('plugin:activate', ['fskey' => $fskey]);
125 | } else {
126 | $exitCode = Artisan::call('plugin:deactivate', ['fskey' => $fskey]);
127 | }
128 |
129 | if ($exitCode !== 0) {
130 | return $this->fail(Artisan::output());
131 | }
132 |
133 | $app = App::where('fskey', $fskey)->first();
134 | if ($app) {
135 | $app->update([
136 | 'is_enabled' => !$app->is_enabled,
137 | ]);
138 | }
139 |
140 | return \response()->json([
141 | 'err_code' => 0,
142 | 'err_msg' => 'success',
143 | 'data' => null,
144 | ]);
145 | }
146 |
147 | public function uninstall()
148 | {
149 | \request()->validate([
150 | 'plugin' => 'required|string',
151 | 'clearData' => 'nullable|bool',
152 | ]);
153 |
154 | $fskey = \request('plugin');
155 | if (\request()->get('clearData') == 1) {
156 | $exitCode = Artisan::call('plugin:uninstall', ['fskey' => $fskey, '--cleardata' => true]);
157 | } else {
158 | $exitCode = Artisan::call('plugin:uninstall', ['fskey' => $fskey, '--cleardata' => false]);
159 | }
160 |
161 | if ($exitCode == 0) {
162 | App::where('fskey', $fskey)->delete();
163 | }
164 |
165 | $message = '卸载成功';
166 | if ($exitCode != 0) {
167 | $message = Artisan::output() . "\n卸载失败";
168 | }
169 |
170 | return \response($message);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/app/Traits/DataTime.php:
--------------------------------------------------------------------------------
1 | 'feature',
24 | 'feature' => 'feature',
25 | 'past' => 'past',
26 | };
27 |
28 | // 获取当前日期
29 | $currentDate = new \DateTime();
30 |
31 | // 循环获取接下来的一周日期
32 | $data = [];
33 | if ($direction == 'past') {
34 | if ($excludeToday) {
35 | $subDayNum = $dayNum;
36 | } else {
37 | $subDayNum = $dayNum - 1;
38 | }
39 |
40 | $dateInterval = new \DateInterval('P' . $subDayNum . 'D'); // 创建一个新的日期间隔
41 | $currentDate = $currentDate->sub($dateInterval);
42 | } else {
43 | if ($excludeToday) {
44 | $addDayNum = 1;
45 | } else {
46 | $addDayNum = 0;
47 | }
48 |
49 | $dateInterval = new \DateInterval('P' . $addDayNum . 'D'); // 创建一个新的日期间隔
50 | $currentDate = $currentDate->add($dateInterval);
51 | }
52 |
53 | for ($i = 0; $i < $dayNum; $i++) {
54 | $dateInterval = new \DateInterval('P' . $i . 'D'); // 创建一个新的日期间隔
55 | $day = $currentDate->add($dateInterval); // 获取未来的日期
56 |
57 | $item = DateUtility::getDateInfo($day->format('Y-m-d'));
58 | if (!$item) {
59 | continue;
60 | }
61 |
62 | // 为下一次迭代重设$currentDate
63 | $currentDate = $currentDate->sub($dateInterval);
64 |
65 | $data[] = $item;
66 | }
67 |
68 | // 按日期排序
69 | array_multisort(array_column($data, 'date'), SORT_ASC, $data);
70 |
71 | return $data;
72 | }
73 |
74 | public static function getDateInfo(?string $date)
75 | {
76 | if (!$date) {
77 | return null;
78 | }
79 |
80 | $day = new \DateTime($date);
81 |
82 | $yesterdayDateStr = date('Y-m-d', strtotime('yesterday'));
83 | $currentDateStr = date('Y-m-d');
84 | $tomorrowDateStr = date('Y-m-d', strtotime('tomorrow'));
85 |
86 | $item['is_yesterday_day'] = false;
87 | $item['is_today'] = false;
88 | $item['is_tomorrow_day'] = false;
89 | $item['date_desc'] = '';
90 | if ($day->format('Y-m-d') == $yesterdayDateStr) {
91 | $item['is_yesterday_day'] = true;
92 | $item['date_desc'] = '昨天';
93 | }
94 | if ($day->format('Y-m-d') == $currentDateStr) {
95 | $item['is_today'] = true;
96 | $item['date_desc'] = '今天';
97 | }
98 | if ($day->format('Y-m-d') == $tomorrowDateStr) {
99 | $item['is_tomorrow_day'] = true;
100 | $item['date_desc'] = '明天';
101 | }
102 | $item['date'] = $day->format('Y-m-d');
103 | $item['day'] = DateUtility::getDateDay($item['date']);
104 | $item['zhou'] = DateUtility::getChineseWeekday($item['day']);
105 | $item['xingqi'] = str_replace('周', '星期', $item['zhou']);
106 |
107 | return $item;
108 | }
109 |
110 | // 定义一个函数将星期数字转化为中文
111 | public static function getChineseWeekday($weekdayNum)
112 | {
113 | $weekdayNumIndex = $weekdayNum - 1;
114 |
115 | $weekdays = array("周一", "周二", "周三", "周四", "周五", "周六", "周日");
116 | return $weekdays[$weekdayNumIndex];
117 | }
118 |
119 | public static function getDateDay(?string $date)
120 | {
121 | if (!$date) {
122 | return null;
123 | }
124 | try {
125 | $date = new \DateTime($date);
126 | } catch (\Throwable $e) {
127 | info("{$date} 数据不是正确的日期格式, 错误信息: " . $e->getMessage());
128 | return null;
129 | }
130 | $dayOfWeek = intval($date->format('w') ?: '7');
131 |
132 | return $dayOfWeek;
133 | }
134 |
135 | public static function getYearMonth($year = null, $limitToCurrentMonth = true)
136 | {
137 | $currentYear = $year;
138 |
139 | // 获取当前年份
140 | if (!$currentYear) {
141 | $currentYear = date("Y");
142 | }
143 |
144 | // 创建一个数组来保存12个月份
145 | $monthsList = [];
146 | for ($i = 1; $i <= 12; $i++) {
147 | // 数字月份前面补零,比如 '01','02',...,'12'
148 | $month = str_pad($i, 2, '0', STR_PAD_LEFT);
149 |
150 | // 将年份和月份结合,格式为 'YYYY-MM'
151 | $monthsList[] = "{$currentYear}-{$month}";
152 | }
153 |
154 | $months = [];
155 | foreach ($monthsList as $monthItem) {
156 | $currentMonthItem = \Carbon\Carbon::createFromDate($monthItem);
157 |
158 | if ($limitToCurrentMonth) {
159 | if ($currentMonthItem->gt(now())) {
160 | continue;
161 | }
162 | }
163 |
164 | $months[] = $monthItem;
165 | }
166 |
167 | return $months;
168 | }
169 |
170 | public static function getYearMonthRange(array $monthRange)
171 | {
172 | $data = [];
173 | foreach ($monthRange as $item) {
174 | $startDay = static::getYearMonthDay($item, 'first');
175 | $endDay = static::getYearMonthDay($item, 'last');
176 |
177 | $data[] = [
178 | $startDay,
179 | $endDay,
180 | ];
181 | }
182 |
183 | return $data;
184 | }
185 |
186 | public static function getYearMonthAndMonthRange($year, $limitToCurrentMonth = true)
187 | {
188 | $months = DateUtility::getYearMonth($year, $limitToCurrentMonth);
189 | $monthRangeList = DateUtility::getYearMonthRange($months);
190 |
191 | return [
192 | 'months' => $months,
193 | 'monthRangeList' => $monthRangeList,
194 | ];
195 | }
196 |
197 | public static function getYearMonthDay($monthDay = null, $type = 'first')
198 | {
199 | $type = match ($type) {
200 | default => 'first',
201 | 'first' => 'first',
202 | 'last' => 'last',
203 | };
204 |
205 | $date = new \DateTime($monthDay);
206 | $date->modify("{$type} day of this month");
207 |
208 | $dateString = $date->format('Y-m-d');
209 |
210 | $dateTimeString = match ($type) {
211 | default => $dateString . ' 00:00:00',
212 | 'first' => $dateString . ' 00:00:00',
213 | 'last' => $dateString . ' 23:59:59',
214 | };
215 |
216 | return $dateTimeString;
217 | }
218 |
219 | /**
220 | * 仪表盘日志使用
221 | * 根据类型获取开始结束时间戳数组
222 | *
223 | *
224 | * @param string $type today-今天;yesterday-昨天;week-本周;lastWeek-上一周;month-本月;lastMonth-上一个月;quarter-本季度;lastQuarter-上季度;year-本年;lastYear-去年;recent60-最近60天;recent30-最近30天;
225 | *
226 | * @return array [0 => "start_time timestamp", 1 => "end_time timestamp", "last_time" => [0 => "previous cycle start_time", 1 => "previous cycle end_time"]]
227 | */
228 | public static function getDateTimeInfoByDateType($type = 'today')
229 | {
230 | switch ($type) {
231 | case 'yesterday':
232 | $timeArr = DataTime::yesterday();
233 | $timeArr['last_time'] = DataTime::yesterday(1);
234 | break;
235 | case 'week':
236 | $timeArr = DataTime::week();
237 | $timeArr['last_time'] = DataTime::lastWeek();
238 | break;
239 | case 'lastWeek':
240 | $timeArr = DataTime::lastWeek();
241 | $timeArr['last_time'] = DataTime::lastWeek(1);
242 | break;
243 | case 'month':
244 | $timeArr = DataTime::month();
245 | $timeArr['last_time'] = DataTime::lastMonth();
246 | break;
247 | case 'lastMonth':
248 | $timeArr = DataTime::lastMonth();
249 | $timeArr['last_time'] = DataTime::lastMonth(1);
250 | break;
251 | case 'quarter':
252 | //本季度
253 | $month = date('m');
254 | if ($month == 1 || $month == 2 || $month == 3) {
255 | $daterange_start_time = strtotime(date('Y-01-01 00:00:00'));
256 | $daterange_end_time = strtotime(date("Y-03-31 23:59:59"));
257 | } elseif ($month == 4 || $month == 5 || $month == 6) {
258 | $daterange_start_time = strtotime(date('Y-04-01 00:00:00'));
259 | $daterange_end_time = strtotime(date("Y-06-30 23:59:59"));
260 | } elseif ($month == 7 || $month == 8 || $month == 9) {
261 | $daterange_start_time = strtotime(date('Y-07-01 00:00:00'));
262 | $daterange_end_time = strtotime(date("Y-09-30 23:59:59"));
263 | } else {
264 | $daterange_start_time = strtotime(date('Y-10-01 00:00:00'));
265 | $daterange_end_time = strtotime(date("Y-12-31 23:59:59"));
266 | }
267 |
268 | //上季度
269 | $month = date('m');
270 | if ($month == 1 || $month == 2 || $month == 3) {
271 | $year = date('Y') - 1;
272 | $daterange_start_time_last_time = strtotime(date($year . '-10-01 00:00:00'));
273 | $daterange_end_time_last_time = strtotime(date($year . '-12-31 23:59:59'));
274 | } elseif ($month == 4 || $month == 5 || $month == 6) {
275 | $daterange_start_time_last_time = strtotime(date('Y-01-01 00:00:00'));
276 | $daterange_end_time_last_time = strtotime(date("Y-03-31 23:59:59"));
277 | } elseif ($month == 7 || $month == 8 || $month == 9) {
278 | $daterange_start_time_last_time = strtotime(date('Y-04-01 00:00:00'));
279 | $daterange_end_time_last_time = strtotime(date("Y-06-30 23:59:59"));
280 | } else {
281 | $daterange_start_time_last_time = strtotime(date('Y-07-01 00:00:00'));
282 | $daterange_end_time_last_time = strtotime(date("Y-09-30 23:59:59"));
283 | }
284 | $timeArr = array($daterange_start_time, $daterange_end_time);
285 | $timeArr['last_time'] = array($daterange_start_time_last_time, $daterange_end_time_last_time);
286 | break;
287 | case 'lastQuarter':
288 | //上季度
289 | $month = date('m');
290 | if ($month == 1 || $month == 2 || $month == 3) {
291 | $year = date('Y') - 1;
292 | $daterange_start_time = strtotime(date($year . '-10-01 00:00:00'));
293 | $daterange_end_time = strtotime(date($year . '-12-31 23:59:59'));
294 | } elseif ($month == 4 || $month == 5 || $month == 6) {
295 | $daterange_start_time = strtotime(date('Y-01-01 00:00:00'));
296 | $daterange_end_time = strtotime(date("Y-03-31 23:59:59"));
297 | } elseif ($month == 7 || $month == 8 || $month == 9) {
298 | $daterange_start_time = strtotime(date('Y-04-01 00:00:00'));
299 | $daterange_end_time = strtotime(date("Y-06-30 23:59:59"));
300 | } else {
301 | $daterange_start_time = strtotime(date('Y-07-01 00:00:00'));
302 | $daterange_end_time = strtotime(date("Y-09-30 23:59:59"));
303 | }
304 | //上季度
305 | $month = date('m');
306 | if ($month == 1 || $month == 2 || $month == 3) {
307 | $year = date('Y') - 2;
308 | $daterange_start_time_last_time = strtotime(date($year . '-10-01 00:00:00'));
309 | $daterange_end_time_last_time = strtotime(date($year . '-12-31 23:59:59'));
310 | } elseif ($month == 4 || $month == 5 || $month == 6) {
311 | $daterange_start_time_last_time = strtotime(date('Y-01-01 00:00:00'));
312 | $daterange_end_time_last_time = strtotime(date("Y-03-31 23:59:59"));
313 | } elseif ($month == 7 || $month == 8 || $month == 9) {
314 | $daterange_start_time_last_time = strtotime(date('Y-04-01 00:00:00'));
315 | $daterange_end_time_last_time = strtotime(date("Y-06-30 23:59:59"));
316 | } else {
317 | $daterange_start_time_last_time = strtotime(date('Y-07-01 00:00:00'));
318 | $daterange_end_time_last_time = strtotime(date("Y-09-30 23:59:59"));
319 | }
320 | $timeArr = array($daterange_start_time, $daterange_end_time);
321 | $timeArr['last_time'] = array($daterange_start_time_last_time, $daterange_end_time_last_time);
322 | break;
323 | case 'year':
324 | $timeArr = DataTime::year();
325 | $timeArr['last_time'] = DataTime::lastYear();
326 | break;
327 | case 'lastYear':
328 | $timeArr = DataTime::lastYear();
329 | $timeArr['last_time'] = DataTime::lastYear(1);
330 | break;
331 | case 'recent60':
332 | $timeArr = DataTime::recent60();
333 | break;
334 | case 'recent30':
335 | $timeArr = DataTime::recent30();
336 | break;
337 | case 'today':
338 | default:
339 | $timeArr = DataTime::today();
340 | $timeArr['last_time'] = DataTime::yesterday();
341 | break;
342 | }
343 | return $timeArr;
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/resources/views/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('MarketManager::layouts.master')
2 |
3 | @php
4 | use \Plugins\MarketManager\Utilities\PluginUtility;
5 | @endphp
6 |
7 | @section('content')
8 |
17 |
18 |
57 |
58 |
59 |
60 |
87 |
88 |
89 | @if(!str_contains($configs['market_server_host'], 'packagist.org'))
90 |
91 |
92 |
93 | @endif
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
扩展插件
107 |
这里展示的是系统中已安装的所有插件。
108 |
109 |
116 |
117 |
118 |
119 |
120 |
121 | | 名称 |
122 | 描述 |
123 | 开发者 |
124 | 操作 |
125 |
126 |
127 |
128 | @foreach($apps as $app)
129 |
130 | |
131 |
132 | {{ $app['name'] }}
133 |
134 | {{ $app['version'] }}
135 | |
136 | {{ Str::limit($app['description'] ?? '', 100) }} |
137 | {{ $app['author'] }} |
138 |
139 | @if($app['is_enabled'] == false)
140 |
141 | @elseif($app['fskey'] !== 'MarketManager')
142 |
143 | @if($app['is_enabled'] && $app['access_path'])
144 |
145 | @endif
146 | @if($app['is_enabled'] && $app['settings_path'])
147 |
148 | @endif
149 |
150 |
151 | @endif
152 | @if($app['is_enabled'] == false)
153 |
154 | @endif
155 | |
156 |
157 | @endforeach
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
209 |
210 |
230 |
231 |
232 |
233 |
234 |
238 |
239 |
247 |
248 |
249 |
250 |
251 |
1. 打开插件列表页
252 |
253 |
点击左侧菜单中的插件市场,查找需要使用的插件。
254 |
255 |
256 |
257 |
2. 复制链接
258 |
259 |
选择需要的插件,右键复制链接备用。
260 |
261 |
262 |
263 |
3. 安装插件
264 |
265 |
点击左侧菜单中的安装插件,选择安装方式输入插件下载地址,并在输入框中粘贴上一步的链接。
266 |
267 |
268 |
269 |
4. 开始安装
270 |
271 |
点击确认按钮,开始安装,并等待安装完成,预计时常 1~5 分钟,取决于您的项目网络环境。
272 |
273 |
274 |
275 |
278 |
279 |
280 |
281 |
282 |
415 |
416 |
540 | @endsection
--------------------------------------------------------------------------------