vue+uikit3+laravel快速建站
vue+uikit3+laravel快速建站
Table of Contents
目前站已初步建好,待完善中,https://recallsufuture.space,源码已放到 github
1 前言
很早因为折腾科学上网之术,入了vps的坑,然而那个配置只是作为梯子的话实在是浪费资源, 在加上做了一阵子的web开发,却还没自己的一个站,自己的小程序做不了,网站总可以的嘛, 那么下面就开始吧。
2 需求
实际上还真不知到要做个什么样的网站,经过一番艰难的思考,还是决定先做已经被我做烂了的fa爬虫, 如果有其他的想法,就做成新的板块,这样就可以慢慢填坑啦。
2.1 页面
- 主页面,展示本站作用以及现有板块
- fa板块,批量爬取原图链接
3 环境
3.1 开发环境
- php7.2
- npm
- composer
- vscode
- redis
- chrome & firefox
3.2 生产环境
- oneinstack一键环境,包括php,nginx,redis。
- npm
- composer
3.3 框架
- 前端用vue+uikit3制作足够模块化且现代化的页面,并用vue-router来提供路由支持
- 后端用laravel,优雅且强大
4 Let's code
4.1 主页面
4.1.1 界面构思
这个页面需要有顶部导航栏,主体需要有足够大而鲜明的说明标题文字来介绍本站, 标题下面是几个card来展示现有的板块。
4.1.2 实现
首先是路由,默认的laravel用闭包写了一个welcome路由,这个我们不需要了,删掉routes/web.php内的这行路由, 并删掉对应的blade文件。
因为我用了vue-router来管理路由,所以只需要在web.php中写一个拦截所有请求的路由即可,所有页面请求都导向vue页面, 即:
Route::get('/{view?}', 'HomeController@index')->where('view', '.*')->name('home');
接着修改上面的HomeController的index方法,使它返回app.blade.php模板
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>夙的小站</title> <!-- Styles --> <link href="{{ mix('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"></div> <!-- Scripts --> <script src="{{ mix('js/app.js') }}"></script> </body> </html>
因为要用到uikit,所以去找了下脚手架,在网站根目录执行下面四条命令可以获得vue+uikit3脚手架:
composer require laravel-frontend-presets/uikit3 php artisan preset uikit3 php artisan preset vue npm install
添加vue-router路由配置文件router.js:
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router); export default new Router({ mode: 'history', routes: [ { path: '/', redirect: '/home', }, { path: '/home', component: require('./pages/Home.vue'), }, ], })
修改下app.js保证编译顺利通过,然后就可以开始在resources/assets/js/pages/Home.vue里写第一个vue页面啦。
此处省略一千字。。。直接看结果吧
4.2 fa页面
最初的想法是先将图片下到服务器,完成后打包发给用户,但实验过后发现等待时间实在太长, 一旦关了浏览器就啥都没有了,而且这样做很费服务器空间,没办法,只能退而求其次,获取来所有的链接让用户自己去下载。
那么这个页面一共有如下几个内容:
- 界面内需要一个表单来获取一些下载信息
- 后台暴露出一个接口,由前端ajax访问此接口,这样可以有比较好的体验
- 界面上要有一个公告窗口,记录当前的下载日志
- 后台需要配合实现websocket广播功能
前台界面的编写就不再多说,直接看下成品图
后台部分,首先要实现那个接口,也就是接受表单数据,处理,然后返回状态码。
当然,还是得先把路由写好:
<?php Route::prefix('api')->group(function () { // 批量获取fa链接 Route::get('/fa', 'FaController@index')->name('fa.index'); }); // 全部拦截到此页 Route::get('/{view?}', 'HomeController@index')->where('view', '.*')->name('home');
因为可能还会有其他的api,所以我在这里分组并设置了统一的路径前缀(web.php参考了Laravel Horizon)
然后是编写FaController,用php artisan命令创建controller,并修改其中的index方法:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Events\FaDownloadEvent; class FaController extends Controller { /** * 返回是否成功新建任务 * * @return \Illuminate\Http\Response */ public function index(Request $request) { // 验证表单 $validatedData = $request->validate([ 'id' => "filled", 'name' => "required|max:255", 'type' => [ "required", "regex:/gallery|scraps/" ], 'pagenum' => "min:1", 'picnum' => "min:1|max:72", 'maxnum' => "min:0" ]); // 保存表单数据 $id = $validatedData['id']; $name = trim($validatedData['name']); $type = $validatedData['type']; $pagenum = $validatedData['pagenum']; $picnum = $validatedData['picnum']; $maxnum = $validatedData['maxnum']; event(new FaDownloadEvent($id, $name, $type, $pagenum, $picnum, $maxnum)); return [ 'status' => 'ok' ]; } }
可以看到我先是验证了表单数据,然后将数据保存下来并触发了一个事件,验证表单数据是因为作为api, 有可能会被其他地方的代码访问,所以必须要验证数据有效性,而不直接下载改为触发事件是因为需要异步执行下载, 将下载任务放到队列里,通过广播来通知客户进度。
这里涉及到了事件,队列和广播,通过触发事件,预先设定好的的监听器会接受事件并处理,这样可以让代码更加解耦。 队列是用来处理需要长时间运行的任务,比如发送邮件。广播是为了和让后台和浏览器即使的沟通, 利用 websocket 建立长连接,免除了ajax轮询的麻烦,具体的内容请查询laravel中文文档
下面一个个说这些功能的实现:
4.2.1 事件
当前有一个FaDownloadEvent事件,这个事件接收所有表单参数,至于代码部分,其实十分的简单:
<?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Queue\SerializesModels; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class FaDownloadEvent { use Dispatchable, InteractsWithSockets, SerializesModels; /** * Client id * * @var string */ public $id; /** * Author name * * @var string */ public $name; /** * Gallery or scraps * * @var string */ public $type; /** * Start page number * * @var string */ public $pagenum; /** * Start pic number * * @var string */ public $picnum; /** * Max download num * * @var string */ public $maxnum; /** * Create a new event instance. * * @return void */ public function __construct($id, $name, $type, $pagenum, $picnum, $maxnum) { $this->id = $id; $this->name = $name; $this->type = $type; $this->pagenum = $pagenum; $this->picnum = $picnum; $this->maxnum = $maxnum; } }
只是通过构造函数将参数保存到成员变量里而已,接下来看看接收这个事件的监听器:
4.2.2 监听器
<?php namespace App\Listeners; use App\Events\FaDownloadEvent; use App\Events\DownloadStateEvent; use App\Models\File; use App\Utility\Vendor\Fa\FurAffinityAPI; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class FaDownloadListener implements ShouldQueue { /** * The number of seconds the job can run before timing out. * * @var int */ public $timeout = 1000; /** * Handle the event. * * @param FaDownloadEvent $event * @return void */ public function handle(FaDownloadEvent $event) { // 触发一个状态事件,此事件会向浏览器广播下面的内容 event(new DownloadStateEvent($id, 'info', '正在下载'.$name.'/'.$type.'的第'.$nowpage.'页第'. $nowpic .'个链接')); // 下面是具体的下载逻辑,在此省略 } }
监听器类接受到事件后,就可以进行下载任务了,但是如果直接开始的话,下载过程还会是同步的, 也就是会阻塞浏览器,让它变成异步只需要简单的添加一个接口就可以啦,
class FaDownloadListener implements ShouldQueu
只要加上这个接口,任务的执行就会自动放到队列里,至于是哪个队列呢?当然是默认队列啦
4.2.3 队列
现在需要配置队列,队列可以看作是一个单独的进程,这个进程就是个死循环,有任务放进来就执行,没有就睡眠等待
配置起来也不难,首先修改.env,将队列设置为redis驱动: QUEUEDRIVER=redis
然后在网站根目录下执行: php artisan queue:worker 即可,这样就开启了一个默认的队列。 想要开启更多的不一样的队列?用 –queue 参数试试。
不过这个进程会一直堵塞着终端,毕竟是个死循环嘛, 不想看到一直堵塞着的话可以让它后台执行: php artisan queue:worker –daemon >> /dev/null &
这个队列配置好后,监听器就可以正常工作了,每接收到一个FaDownloadEvent事件,就会向默认队列中添加一个下载任务。
4.2.4 广播
现在只剩广播部分没有完成