From 1c228d21e16f3bad7c06d5976e3098758a02735d Mon Sep 17 00:00:00 2001 From: liutk <961510893@qq.com> Date: Sun, 22 Feb 2026 11:58:01 +0800 Subject: [PATCH] 0.6 --- .../Controllers/FriendLinkController.php | 2 + .../Controllers/ProjectCateController.php | 2 + app/Exceptions/BizException.php | 42 ++++++++++ .../Controllers/Api/CaptchaController.php | 72 ++++++++++++++++ .../Api/ProjectChildrenController.php | 27 ++++++ .../Controllers/Api/ProjectController.php | 23 ++++++ .../Resources/ProjectChildrenResource.php | 24 ++++++ app/Http/Resources/ProjectResource.php | 22 +++++ app/Models/FriendLink.php | 10 +++ app/Models/ProjectArticle.php | 5 ++ app/Models/ProjectCate.php | 10 +++ app/Services/CaptchaService.php | 82 +++++++++++++++++++ app/Traits/UploadTrait.php | 6 +- composer.json | 1 + composer.lock | 62 +++++++++++++- ...2_01_173657_create_project_cates_table.php | 1 + ...02_21_200536_create_friend_links_table.php | 3 +- lang/zh_CN/admin.php | 4 +- routes/api.php | 11 ++- 19 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 app/Exceptions/BizException.php create mode 100644 app/Http/Controllers/Api/CaptchaController.php create mode 100644 app/Http/Controllers/Api/ProjectChildrenController.php create mode 100644 app/Http/Controllers/Api/ProjectController.php create mode 100644 app/Http/Resources/ProjectChildrenResource.php create mode 100644 app/Http/Resources/ProjectResource.php create mode 100644 app/Services/CaptchaService.php diff --git a/app/Admin/Controllers/FriendLinkController.php b/app/Admin/Controllers/FriendLinkController.php index e5c75df..dd732d7 100644 --- a/app/Admin/Controllers/FriendLinkController.php +++ b/app/Admin/Controllers/FriendLinkController.php @@ -40,6 +40,7 @@ class FriendLinkController extends AdminController amis()->TableColumn('link', __('admin.friend_links.link')), amis()->TableColumn('cover_url', __('admin.friend_links.cover'))->type('image')->height('50px')->width('250px')->enlargeAble(true), // amis()->TableColumn('description', __('admin.project_cates.description')), + amis()->TableColumn('is_enable', __('admin.friend_links.is_enable'))->type('switch'), amis()->TableColumn('created_at', __('admin.created_at'))->set('type', 'datetime')->sortable()->width('150px'), amis()->Operation()->label(__('admin.actions'))->buttons([ $this->rowEditTypeButton('drawer', 'md'), @@ -60,6 +61,7 @@ class FriendLinkController extends AdminController Components::make()->cropImageControl('cover', __('admin.friend_links.cover'))->required(true), amis()->TextareaControl('description', __('admin.friend_links.description')), Components::make()->sortControl('sort', __('admin.friend_links.sort')), + amis()->SwitchControl('is_enable', __('admin.friend_links.is_enable'))->value(false), ]) ]), ]); diff --git a/app/Admin/Controllers/ProjectCateController.php b/app/Admin/Controllers/ProjectCateController.php index 9e2b553..2b481ce 100644 --- a/app/Admin/Controllers/ProjectCateController.php +++ b/app/Admin/Controllers/ProjectCateController.php @@ -39,6 +39,7 @@ class ProjectCateController extends AdminController amis()->TableColumn('title', __('admin.project_cates.title')), amis()->TableColumn('cover_url', __('admin.project_cates.cover'))->type('image')->height('50px')->width('250px')->enlargeAble(true), // amis()->TableColumn('description', __('admin.project_cates.description')), + amis()->TableColumn('is_enable', __('admin.project_cates.is_enable'))->type('switch'), amis()->TableColumn('created_at', __('admin.created_at'))->set('type', 'datetime')->sortable()->width('150px'), amis()->Operation()->label(__('admin.actions'))->buttons([ $this->rowEditTypeButton('drawer', 'md'), @@ -58,6 +59,7 @@ class ProjectCateController extends AdminController Components::make()->cropImageControl('cover', __('admin.project_cates.cover'), 1.68)->required(true), amis()->TextareaControl('description', __('admin.project_cates.description')), Components::make()->sortControl('sort', __('admin.project_cates.sort')), + amis()->SwitchControl('is_enable', __('admin.project_cates.is_enable'))->value(false), ]) ]), ]); diff --git a/app/Exceptions/BizException.php b/app/Exceptions/BizException.php new file mode 100644 index 0000000..576d28b --- /dev/null +++ b/app/Exceptions/BizException.php @@ -0,0 +1,42 @@ +status = $status; + + return $this; + } + + /** + * 报告异常 + * + * @return mixed + */ + public function report() + { + } +} diff --git a/app/Http/Controllers/Api/CaptchaController.php b/app/Http/Controllers/Api/CaptchaController.php new file mode 100644 index 0000000..422ab64 --- /dev/null +++ b/app/Http/Controllers/Api/CaptchaController.php @@ -0,0 +1,72 @@ +validate([ + 'key' => ['bail', 'filled', 'string', 'max:32'], + 'w' => ['bail', 'int'], + 'h' => ['bail', 'int'], + ]); + + $builder = $this->builder(); + $builder->build($request->input('w', 150), $request->input('h', 40)); + + $captchaService->put( + $key = $request->input('key', Str::random(16)), + $builder->getPhrase() + ); + + return response()->json([ + 'key' => $key, + 'img' => $builder->inline(), + ]); + } + + /** + * 查看图形验证码 + * + * @param string $key + * @param \Illuminate\Http\Request $request + * @param \App\Services\CaptchaService $captchaService + * @return \Illuminate\Http\Response + */ + public function show($key, Request $request, CaptchaService $captchaService) + { + $builder = $this->builder(); + $builder->build( + (int) $request->query('w', 150), + (int) $request->query('h', 40), + ); + + if (strlen($key) <= 32) { + $captchaService->put($key, $builder->getPhrase()); + } + + return response($builder->get()) + ->header('Content-Type', 'image/jpeg') + ->header('Cache-Control', 'no-cache'); + } + + /** + * 图形验证码生成器 + * + * @return \Gregwar\Captcha\CaptchaBuilder + */ + protected function builder(): CaptchaBuilder + { + return new CaptchaBuilder(Str::random(5)); + } +} diff --git a/app/Http/Controllers/Api/ProjectChildrenController.php b/app/Http/Controllers/Api/ProjectChildrenController.php new file mode 100644 index 0000000..254615c --- /dev/null +++ b/app/Http/Controllers/Api/ProjectChildrenController.php @@ -0,0 +1,27 @@ +all(), ProjectArticleFilter::class)->sort(); + $list = $query->childrens()->show()->sort()->paginate($this->resolvePerPage('per_page', 20)); + + return $this->json(ProjectChildrenResource::collection($list)); + } + + public function show(ProjectArticle $children){ + request()->merge(['include_content' => true]); + return $this->json(ProjectChildrenResource::make($children)); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php new file mode 100644 index 0000000..9935886 --- /dev/null +++ b/app/Http/Controllers/Api/ProjectController.php @@ -0,0 +1,23 @@ +all(), ProjectCateFilter::class)->sort(); + $list = $query->show()->sort()->paginate($this->resolvePerPage('per_page', 20)); + + return $this->json(ProjectResource::collection($list)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/ProjectChildrenResource.php b/app/Http/Resources/ProjectChildrenResource.php new file mode 100644 index 0000000..343125b --- /dev/null +++ b/app/Http/Resources/ProjectChildrenResource.php @@ -0,0 +1,24 @@ + $this->id, + 'title' => $this->title, + 'cover' => $this->cover, + 'description' => $this->description, + 'content' => $this->when($request->boolean('include_content'), $this->content), + ]; + } +} diff --git a/app/Http/Resources/ProjectResource.php b/app/Http/Resources/ProjectResource.php new file mode 100644 index 0000000..7b7df3e --- /dev/null +++ b/app/Http/Resources/ProjectResource.php @@ -0,0 +1,22 @@ + $this->id, + 'title' => $this->title, + 'cover' => $this->cover, + ]; + } +} diff --git a/app/Models/FriendLink.php b/app/Models/FriendLink.php index 2ca191f..05d113c 100644 --- a/app/Models/FriendLink.php +++ b/app/Models/FriendLink.php @@ -32,4 +32,14 @@ class FriendLink extends Model get: fn($value) => $this->cover ? (Str::startsWith($this->cover, ['http://', 'https://']) ? $this->cover : Storage::url($this->cover)) : null, ); } + + public function scopeShow($q){ + $q->where('is_enable', true); + } + + public function scopeSort($q) + { + $q->orderBy('sort', 'asc') + ->orderBy('created_at', 'desc'); + } } diff --git a/app/Models/ProjectArticle.php b/app/Models/ProjectArticle.php index f047079..acbdc1e 100644 --- a/app/Models/ProjectArticle.php +++ b/app/Models/ProjectArticle.php @@ -73,6 +73,11 @@ class ProjectArticle extends Model ->orderBy('created_at', 'desc'); } + public function scopeChildrens($q) + { + $q->where('type', self::TYPE_ARTICLE); + } + protected function tags():Attribute { return Attribute::make( diff --git a/app/Models/ProjectCate.php b/app/Models/ProjectCate.php index 40e02ff..cc1ec02 100644 --- a/app/Models/ProjectCate.php +++ b/app/Models/ProjectCate.php @@ -32,4 +32,14 @@ class ProjectCate extends Model get: fn($value) => $this->cover ? (Str::startsWith($this->cover, ['http://', 'https://']) ? $this->cover : Storage::url($this->cover)) : null, ); } + + public function scopeShow($q){ + $q->where('is_enable', true); + } + + public function scopeSort($q) + { + $q->orderBy('sort', 'asc') + ->orderBy('created_at', 'desc'); + } } diff --git a/app/Services/CaptchaService.php b/app/Services/CaptchaService.php new file mode 100644 index 0000000..fa58a02 --- /dev/null +++ b/app/Services/CaptchaService.php @@ -0,0 +1,82 @@ +cache->put($this->cacheKey($key), $phrase, $ttl); + } + + /** + * 校验验证码是否正确 + * + * @param string $key + * @param string $phrase + * @return bool + */ + public function testPhrase(string $key, string $phrase): bool + { + if ($phrase === '') { + return false; + } + + $value = (string) $this->cache->pull( + $this->cacheKey($key) + ); + + return strtolower($value) === strtolower($phrase); + } + + /** + * 校验验证码是否正确 + * + * @param string $key + * @param string $phrase + * @return void + * + * @throws \App\Exceptions\BizException + */ + public function validatePhrase(string $key, string $phrase): void + { + if (! $this->testPhrase($key, $phrase)) { + throw new BizException(__('Invalid captcha')); + } + } + + /** + * 生成验证码缓存的 key + * + * @param string $key + * @return string + */ + protected function cacheKey(string $key): string + { + return $this->cachePrefix.$key; + } +} diff --git a/app/Traits/UploadTrait.php b/app/Traits/UploadTrait.php index 05a7e91..300efbd 100644 --- a/app/Traits/UploadTrait.php +++ b/app/Traits/UploadTrait.php @@ -96,7 +96,11 @@ trait UploadTrait switch($file['state']){ case 'init': if(strpos($file['value'], 'temporary') !== false){ - $filePath = $path.'/'.($file['name'] ?? $file['id']); + if(!isset($file['name'])){ + $filePathInfo = pathinfo($file['value']); + $file['name'] = $filePathInfo['basename']; + } + $filePath = $path.'/'.$file['name']; Storage::disk(Admin::config('admin.upload.disk'))->move($file['value'], $filePath); $fileArr[] = Storage::disk(Admin::config('admin.upload.disk'))->url($filePath); }else{ diff --git a/composer.json b/composer.json index 6548345..bcf5918 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "require": { "php": "^8.1", "alphasnow/aliyun-oss-laravel": "^4.7", + "gregwar/captcha": "^1.3", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", "laravel/sanctum": "^3.3", diff --git a/composer.lock b/composer.lock index 341a526..efd932c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6ce90738fadc40a447dfead6b131ec62", + "content-hash": "097769e6f2cd7e3bab775e47a9cce358", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -599,6 +599,64 @@ ], "time": "2023-11-12T22:16:48+00:00" }, + { + "name": "gregwar/captcha", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Gregwar/Captcha.git", + "reference": "4edbcd09fde4353b94ce550f43460eba73baf2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4edbcd09fde4353b94ce550f43460eba73baf2cc", + "reference": "4edbcd09fde4353b94ce550f43460eba73baf2cc", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.3.0", + "symfony/finder": "*" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6.4 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gregwar\\": "src/Gregwar" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "homepage": "https://github.com/Gregwar/Captcha", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "support": { + "issues": "https://github.com/Gregwar/Captcha/issues", + "source": "https://github.com/Gregwar/Captcha/tree/v1.3.0" + }, + "time": "2025-06-23T12:25:54+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.8.1", @@ -6575,5 +6633,5 @@ "php": "^8.1" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/database/migrations/2026_02_01_173657_create_project_cates_table.php b/database/migrations/2026_02_01_173657_create_project_cates_table.php index 947f265..d00e64e 100644 --- a/database/migrations/2026_02_01_173657_create_project_cates_table.php +++ b/database/migrations/2026_02_01_173657_create_project_cates_table.php @@ -18,6 +18,7 @@ return new class extends Migration $table->string('cover')->nullable()->comment('封面'); $table->unsignedInteger('sort')->default(0)->comment('排序'); $table->text('content')->nullable()->comment('内容'); + $table->unsignedTinyInteger('is_enable')->default(1)->comment('显示开关'); $table->timestamps(); }); } diff --git a/database/migrations/2026_02_21_200536_create_friend_links_table.php b/database/migrations/2026_02_21_200536_create_friend_links_table.php index 2cf250f..8172611 100644 --- a/database/migrations/2026_02_21_200536_create_friend_links_table.php +++ b/database/migrations/2026_02_21_200536_create_friend_links_table.php @@ -17,7 +17,8 @@ return new class extends Migration $table->text('description')->nullable()->comment('简介'); $table->string('cover')->nullable()->comment('封面'); $table->unsignedInteger('sort')->default(0)->comment('排序'); - $table->string('link')->nullable()->comment('链接地址'); + $table->string('link')->nullable()->comment('链接地址'); + $table->unsignedTinyInteger('is_enable')->default(1)->comment('显示开关'); $table->timestamps(); }); } diff --git a/lang/zh_CN/admin.php b/lang/zh_CN/admin.php index 0d3b3d0..d0183db 100644 --- a/lang/zh_CN/admin.php +++ b/lang/zh_CN/admin.php @@ -324,7 +324,8 @@ return [ 'description'=> '简介', 'content' => '内容', 'cover' =>'封面', - 'sort' => '排序' + 'sort' => '排序', + 'is_enable' => '显示', ], 'project_articles' => [ 'id' => '主键ID', @@ -350,6 +351,7 @@ return [ 'content' => '内容', 'cover' =>'封面', 'sort' => '排序', + 'is_enable' => '显示', 'link'=>'链接地址' ] ]; diff --git a/routes/api.php b/routes/api.php index e8357ae..0184a5a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,7 @@ get('/user', function (Request $request) { }); Route::middleware('api')->group(function () { - Route::get('/ads', [App\Http\Controllers\Api\AdController::class, 'index']); + Route::post('captchas', [CaptchaController::class, 'store']); + Route::get('captchas/{captcha}', [CaptchaController::class, 'show']); + + Route::get('/ads', [AdController::class, 'index']); + Route::get('/project_cates', [ProjectController::class, 'index']); + Route::get('/project_childrens', [ProjectChildrenController::class, 'index']); + Route::get('/project_childrens/{children}', [ProjectChildrenController::class, 'show']); + }); \ No newline at end of file