From bf648a0a433b4c7037f65cd33920bd5263000132 Mon Sep 17 00:00:00 2001 From: Jing Li Date: Tue, 30 Apr 2024 15:17:51 +0800 Subject: [PATCH] =?UTF-8?q?App=E7=89=88=E6=9C=AC=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AppVersionController.php | 132 ++++++++++ app/Admin/Filters/AppVersionFilter.php | 9 + app/Admin/Services/AppVersionService.php | 235 ++++++++++++++++++ app/Admin/routes.php | 3 + app/Enums/AppOs.php | 30 +++ app/Enums/AppUpdateStrategy.php | 30 +++ app/Models/AppVersion.php | 53 ++++ ...04_29_152806_create_app_versions_table.php | 37 +++ database/seeders/AdminPermissionSeeder.php | 9 +- lang/zh_CN/app_version.php | 17 ++ 10 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 app/Admin/Controllers/AppVersionController.php create mode 100644 app/Admin/Filters/AppVersionFilter.php create mode 100644 app/Admin/Services/AppVersionService.php create mode 100644 app/Enums/AppOs.php create mode 100644 app/Enums/AppUpdateStrategy.php create mode 100644 app/Models/AppVersion.php create mode 100644 database/migrations/2024_04_29_152806_create_app_versions_table.php create mode 100644 lang/zh_CN/app_version.php diff --git a/app/Admin/Controllers/AppVersionController.php b/app/Admin/Controllers/AppVersionController.php new file mode 100644 index 0000000..86fdd9a --- /dev/null +++ b/app/Admin/Controllers/AppVersionController.php @@ -0,0 +1,132 @@ +baseCRUD()->tableLayout('fixed') + ->headerToolbar([ + $this->createTypeButton('drawer', 'lg')->visible(Admin::user()->can('admin.app_versions.create')), + amis('reload')->align('right'), + ]) + ->bulkActions([]) + ->columns([ + amis()->TableColumn('name', __('app_version.name')), + amis()->TableColumn('version', __('app_version.version')), + amis()->TableColumn('title', __('app_version.title')), + amis()->TableColumn('update_strategy', __('app_version.update_strategy'))->type('mapping')->map(AppUpdateStrategy::labelMap()), + amis()->TableColumn('is_force', __('app_version.is_force'))->type('static-mapping')->map([ + '0' => '', + '1' => '', + ]), + amis()->TableColumn('os', __('app_version.os'))->type('mapping')->map(AppOs::labelMap()), + amis()->TableColumn('is_release', __('app_version.is_release'))->type('static-mapping')->map([ + '0' => '', + '1' => '', + ]), + amis()->TableColumn('release_at', __('app_version.release_at')), + amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime'), + Operation::make()->label(__('admin.actions'))->buttons([ + $this->rowEditTypeButton('drawer', 'lg')->visible(Admin::user()->can('admin.app_versions.update')), + $this->rowDeleteButton()->visible(Admin::user()->can('admin.app_versions.delete')), + $this->rowShowButton()->visible(Admin::user()->can('admin.app_versions.view')), + ]), + ]); + + return $this->baseList($crud); + } + + public function form($isEdit = false): Form + { + return $this->baseForm()->body([ + amis()->TextControl('name', __('app_version.name')) + ->placeholder(__('app_version.name')) + ->required(), + amis()->NumberControl('version', __('app_version.version')) + ->placeholder(__('app_version.version')) + ->precision(0) + ->required(), + amis()->TextControl('title', __('app_version.title')) + ->placeholder(__('app_version.title')) + ->required(), + amis()->TextareaControl('description', __('app_version.description')) + ->placeholder(__('app_version.description')) + ->minRows(10) + ->required(), + amis()->RadiosControl('update_strategy', __('app_version.update_strategy')) + ->options(AppUpdateStrategy::options()) + ->value(AppUpdateStrategy::Wgt->value) + ->required(), + amis()->RadiosControl('is_force', __('app_version.is_force')) + ->options([ + ['label' => '是', 'value' => true], + ['label' => '否', 'value' => false], + ]) + ->value(false) + ->required(), + amis()->RadiosControl('os', __('app_version.os'))->options(AppOs::options())->value(AppOs::Android->value)->required(), + + amis()->FileControl('android[wgt_url]', __('app_version.wgt_url')) + ->accept('.wgt') + ->useChunk(false) + ->visibleOn('${AND(os == "'.AppOs::Android->value.'", update_strategy == "wgt")}'), + amis()->FileControl('android[apk_url]', __('app_version.apk_url')) + ->accept('.apk') + ->useChunk(false) + ->visibleOn('${os == "'.AppOs::Android->value.'"}'), + + amis()->FileControl('ios[wgt_url]', __('app_version.wgt_url')) + ->accept('.wgt') + ->useChunk(false) + ->visibleOn('${AND(os == "'.AppOs::Ios->value.'", update_strategy == "wgt")}'), + amis()->TextControl('ios[apk_url]', '苹果应用地址') + ->placeholder('苹果应用地址') + ->visibleOn('${os == "'.AppOs::Ios->value.'"}'), + + amis()->DateTimeControl('release_at', __('app_version.release_at')) + ->valueFormat('YYYY-MM-DD HH:mm:ss') + ->description('必须设置发布时间,才可更新'), + ]); + } + + public function detail(): Form + { + return $this->baseDetail()->body([ + amis()->Property()->items([ + ['label' => __('app_version.name'), 'content' => '${name}'], + ['label' => __('app_version.version'), 'content' => '${version}'], + ['label' => __('app_version.os'), 'content' => amis()->Mapping()->name('os')->map(AppOs::labelMap())], + ['label' => __('app_version.title'), 'content' => '${title}', 'span' => 3], + ['label' => __('app_version.description'), 'content' => amis()->TextareaControl()->name('description')->static()->labelClassName('hidden'), 'span' => 3], + ['label' => __('app_version.update_strategy'), 'content' => amis()->Mapping()->name('update_strategy')->map(AppUpdateStrategy::labelMap())], + [ + 'label' => __('app_version.is_force'), + 'content' => amis()->Mapping()->name('${is_force}')->static()->map([ + '0' => '', + '1' => '', + ]), + ], + ['label' => __('app_version.apk_url'), 'content' => amis()->Link()->href('${apk_url}')->body('下载')->blank()->visibleOn('${apk_url != null && apk_url != ""}')], + ['label' => __('app_version.wgt_url'), 'content' => amis()->Link()->href('${wgt_url}')->body('下载')->blank()->visibleOn('${wgt_url != null && wgt_url != ""}')], + ['label' => __('app_version.release_at'), 'content' => '${release_at}'], + ['label' => __('app_version.created_at'), 'content' => '${created_at}'], + ]), + ]); + } +} diff --git a/app/Admin/Filters/AppVersionFilter.php b/app/Admin/Filters/AppVersionFilter.php new file mode 100644 index 0000000..f256ffe --- /dev/null +++ b/app/Admin/Filters/AppVersionFilter.php @@ -0,0 +1,9 @@ +os) { + case AppOs::Android: + $instance->setAttribute('android', [ + 'apk_url' => $instance->apk_url, + 'wgt_url' => $instance->wgt_url, + ]); + break; + + case AppOs::Ios: + $instance->setAttribute('ios', [ + 'apk_url' => $instance->apk_url, + 'wgt_url' => $instance->wgt_url, + ]); + break; + } + }); + } + + public function store($data): bool + { + Validator::validate( + data: $data, + rules: [ + 'name' => ['bail', 'required', 'max:255'], + 'version' => ['bail', 'required', 'int'], + 'title' => ['bail', 'required', 'max:255'], + 'description' => ['bail', 'required'], + 'update_strategy' => ['bail', 'required', Rule::enum(AppUpdateStrategy::class)], + 'is_force' => ['bail', 'required', 'bool'], + 'os' => ['bail', 'required', Rule::enum(AppOs::class)], + 'release_at' => ['bail', 'nullable', 'date_format:Y-m-d H:i:s'], + ], + attributes: [ + 'name' => __('app_version.name'), + 'version' => __('app_version.version'), + 'title' => __('app_version.title'), + 'description' => __('app_version.description'), + 'update_strategy' => __('app_version.update_strategy'), + 'is_force' => __('app_version.is_force'), + 'os' => __('app_version.os'), + 'release_at' => __('app_version.release_at'), + ], + ); + + // 全量包 + $apkUrl = ''; + // 增量包 + $wgtUrl = ''; + // 更新策略 + $aus = AppUpdateStrategy::from($data['update_strategy']); + + switch (AppOs::from($data['os'])) { + case AppOs::Android: + $apkUrl = (string) Arr::get($data, 'android.apk_url'); + $wgtUrl = (string) Arr::get($data, 'android.wgt_url'); + if ($aus === AppUpdateStrategy::Apk) { + admin_abort_if($apkUrl === '', '请上传全量包'); + } else { + admin_abort_if($wgtUrl === '', '请上传增量包'); + } + break; + + case AppOs::Ios: + $apkUrl = (string) Arr::get($data, 'ios.apk_url'); + $wgtUrl = (string) Arr::get($data, 'ios.wgt_url'); + if ($aus === AppUpdateStrategy::Apk) { + admin_abort_if($apkUrl === '', '苹果应用地址 不能为空。'); + } else { + admin_abort_if($wgtUrl === '', '请上传增量包'); + } + break; + + default: + admin_abort('未知操作系统'); + break; + } + + $model = $this->getModel(); + + $instance = new $model($data); + + try { + $filename = $data['name'].'_'.time(); + if ($extension = pathinfo($apkUrl, PATHINFO_EXTENSION)) { + $filename .= '.'.$extension; + } + $instance->apk_url = $this->saveUploadedFile($apkUrl, "app-versions/{$filename}"); + } catch (FileNotFoundException $e) { + admin_abort('全量包未找到,请重新上传'); + } + + try { + $filename = $data['name'].'_'.time(); + if ($extension = pathinfo($wgtUrl, PATHINFO_EXTENSION)) { + $filename .= '.'.$extension; + } + $instance->wgt_url = $this->saveUploadedFile($wgtUrl, "app-versions/{$filename}"); + } catch (FileNotFoundException $e) { + admin_abort('增量包未找到,请重新上传'); + } + + return $instance->save(); + } + + public function update($primaryKey, $data): bool + { + Validator::validate( + data: $data, + rules: [ + 'name' => ['bail', 'required', 'max:255'], + 'version' => ['bail', 'required', 'int'], + 'title' => ['bail', 'required', 'max:255'], + 'description' => ['bail', 'required'], + 'update_strategy' => ['bail', 'required', Rule::enum(AppUpdateStrategy::class)], + 'is_force' => ['bail', 'required', 'bool'], + 'os' => ['bail', 'required', Rule::enum(AppOs::class)], + 'release_at' => ['bail', 'nullable', 'date_format:Y-m-d H:i:s'], + ], + attributes: [ + 'name' => __('app_version.name'), + 'version' => __('app_version.version'), + 'title' => __('app_version.title'), + 'description' => __('app_version.description'), + 'update_strategy' => __('app_version.update_strategy'), + 'is_force' => __('app_version.is_force'), + 'os' => __('app_version.os'), + 'release_at' => __('app_version.release_at'), + ], + ); + + // 全量包 + $apkUrl = ''; + // 增量包 + $wgtUrl = ''; + // 更新策略 + $aus = AppUpdateStrategy::from($data['update_strategy']); + + switch (AppOs::from($data['os'])) { + case AppOs::Android: + $apkUrl = (string) Arr::get($data, 'android.apk_url'); + $wgtUrl = (string) Arr::get($data, 'android.wgt_url'); + if ($aus === AppUpdateStrategy::Apk) { + admin_abort_if($apkUrl === '', '请上传全量包'); + } else { + admin_abort_if($wgtUrl === '', '请上传增量包'); + } + break; + + case AppOs::Ios: + $apkUrl = (string) Arr::get($data, 'ios.apk_url'); + $wgtUrl = (string) Arr::get($data, 'ios.wgt_url'); + if ($aus === AppUpdateStrategy::Apk) { + admin_abort_if($apkUrl === '', '苹果应用地址 不能为空。'); + } else { + admin_abort_if($wgtUrl === '', '请上传增量包'); + } + break; + + default: + admin_abort('未知操作系统'); + break; + } + + $instance = $this->query()->whereKey($primaryKey)->firstOrFail(); + $instance->fill($data); + + try { + $filename = $data['name'].'_'.time(); + if ($extension = pathinfo($apkUrl, PATHINFO_EXTENSION)) { + $filename .= '.'.$extension; + } + $instance->apk_url = $this->saveUploadedFile($apkUrl, "app-versions/{$filename}"); + } catch (FileNotFoundException $e) { + admin_abort('全量包未找到,请重新上传'); + } + + try { + $filename = $data['name'].'_'.time(); + if ($extension = pathinfo($wgtUrl, PATHINFO_EXTENSION)) { + $filename .= '.'.$extension; + } + $instance->wgt_url = $this->saveUploadedFile($wgtUrl, "app-versions/{$filename}"); + } catch (FileNotFoundException $e) { + admin_abort('增量包未找到,请重新上传'); + } + + return $instance->save(); + } + + protected function saveUploadedFile(string $from, string $to): string + { + if ($from === '' || preg_match('/^https?:\/\//', $from)) { + return $from; + } + + $disk = Storage::disk(Admin::config('admin.upload.disk')); + + if (! $disk->exists($from)) { + throw new FileNotFoundException(); + } + + $disk->move($from, $to); + + return $disk->url($to); + } +} diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 6a110c3..315943f 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -1,6 +1,7 @@ post('agreement/download', [AgreementController::class, 'download'])->name('agreement.download'); $router->resource('agreement', AgreementController::class); + $router->resource('app-versions', AppVersionController::class); + // 修改上传 $router->post('upload_file', [\App\Admin\Controllers\IndexController::class, 'uploadFile']); $router->post('upload_image', [\App\Admin\Controllers\IndexController::class, 'uploadImage']); diff --git a/app/Enums/AppOs.php b/app/Enums/AppOs.php new file mode 100644 index 0000000..0375e64 --- /dev/null +++ b/app/Enums/AppOs.php @@ -0,0 +1,30 @@ +options()[$this->value]; + } + + public static function options() + { + return [ + self::Android->value => '安卓', + self::Ios->value => '苹果', + ]; + } + + public static function labelMap(): array + { + return [ + self::Android->value => ''.self::Android->text().'', + self::Ios->value => ''.self::Ios->text().'', + ]; + } +} diff --git a/app/Enums/AppUpdateStrategy.php b/app/Enums/AppUpdateStrategy.php new file mode 100644 index 0000000..b925a63 --- /dev/null +++ b/app/Enums/AppUpdateStrategy.php @@ -0,0 +1,30 @@ +options()[$this->value]; + } + + public static function options(): array + { + return [ + self::Apk->value => '全量包', + self::Wgt->value => '热更新', + ]; + } + + public static function labelMap(): array + { + return [ + self::Apk->value => ''.self::Apk->text().'', + self::Wgt->value => ''.self::Wgt->text().'', + ]; + } +} diff --git a/app/Models/AppVersion.php b/app/Models/AppVersion.php new file mode 100644 index 0000000..031997a --- /dev/null +++ b/app/Models/AppVersion.php @@ -0,0 +1,53 @@ + false, + ]; + + protected $casts = [ + 'os' => AppOs::class, + 'update_strategy' => AppUpdateStrategy::class, + 'is_force' => 'bool', + 'release_at' => 'datetime', + ]; + + protected $fillable = [ + 'os', + 'name', + 'version', + 'title', + 'description', + 'update_strategy', + 'is_force', + 'apk_url', + 'wgt_url', + 'release_at', + ]; + + protected function isRelease(): Attribute + { + return Attribute::make( + get: function (mixed $value, $attributes) { + return (bool) $attributes['release_at']; + }, + ); + } +} diff --git a/database/migrations/2024_04_29_152806_create_app_versions_table.php b/database/migrations/2024_04_29_152806_create_app_versions_table.php new file mode 100644 index 0000000..178075d --- /dev/null +++ b/database/migrations/2024_04_29_152806_create_app_versions_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('os')->comment('操作系统: android, ios'); + $table->string('name')->comment('版本名称'); + $table->integer('version')->comment('版本号'); + $table->string('title')->nullable()->comment('更新标题'); + $table->text('description')->nullable()->comment('更新描述'); + $table->string('apk_url')->nullable()->comment('全量包地址'); + $table->string('wgt_url')->nullable()->comment('增量包地址'); + $table->boolean('is_force')->default(false)->comment('是否强制更新'); + $table->string('update_strategy')->comment('更新策略: apk 全量包, wgt 热更新'); + $table->timestamp('release_at')->nullable()->comment('发布时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('app_versions'); + } +}; diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index dbb036b..da38bc7 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -15,8 +15,6 @@ class AdminPermissionSeeder extends Seeder */ public function run() { - // AdminMenu::truncate(); - // AdminPermission::truncate(); $data = [ /* |-------------------------------------------------------------------------- @@ -359,6 +357,13 @@ class AdminPermissionSeeder extends Seeder 'download' => '打包下载', ] ], + 'app_versions' => [ + 'name' => 'App版本管理', + 'icon' => 'flowbite:inbox-full-outline', + 'uri' => '/app-versions', + 'resource' => true, + 'children' => [] + ], /* |-------------------------------------------------------------------------- diff --git a/lang/zh_CN/app_version.php b/lang/zh_CN/app_version.php new file mode 100644 index 0000000..a2c7295 --- /dev/null +++ b/lang/zh_CN/app_version.php @@ -0,0 +1,17 @@ + 'ID', + 'name' => '版本名称', + 'version' => '版本号', + 'title' => '更新标题', + 'description' => '更新描述', + 'is_force' => '强制更新', + 'update_strategy' => '更新策略', + 'os' => '操作系统', + 'apk_url' => '全量包', + 'wgt_url' => '增量包', + 'is_release' => '是否发布', + 'release_at' => '发布时间', + 'created_at' => '创建时间', +];