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 广播
现在只剩广播部分没有完成