老菜雞陪你一塊兒從需求到上線搞出一個Chinese版的Screenlapse(大佬忽略該條)

前言

目的

掘金裏大佬太多了,分享各類牛批技術、代碼、經驗,看的美滋滋,可是在身爲一隻菜雞的我也在思考,你們看了那麼多文章,瞭解了那麼多前沿技術,真正落地使用的又有多少?仍是須要實戰一下才能知道,何時須要用到什麼技術手段,怎麼合適的運用各類技術來解決問題。所以本篇文章並非分享哪些技術多666,而是跟各位老哥一塊兒根據一個具體需求,完成分析、設計編碼、上線的全流程,造成一個真正可用的產品,以此來造成一種爲了解決問題而選取相應技術的思惟(牛批要先吹好)。php

需求

Screenlapse也算是個比較有意思的產品了,它主要的功能就是幫助用戶按期對指定網頁進行截圖,能夠設置天天、每週、每個月某一個特定時間點去作截圖,截圖隨時預覽,有了這麼個服務你就能夠給你的網站造成一個歷史檔案庫,或者默默的視奸競爭對手網站網頁的變化html

然而!!它是個PC站沒有移動端,並且他好像被牆了,百度也不索引它 那麼機會來了,咱們本身作一個本土化的移動版的Screenlase,順便把他作到公衆號裏前端

先放個成品

分析

冷靜睿智機敏的分析一下問題vue

初步分析

用戶功能:

  1. 微信用戶登錄
  2. 用戶添加爬取任務
  3. 用戶刪除爬取任務
  4. 用戶修改爬取任務
  5. 用戶查看全部任務
  6. 用戶查看某一個任務的截圖

後端功能:

  1. 定時把須要爬取的連接抓取個截圖存下來

這麼一看功能簡單的一批,不就是增刪改查麼,辣雞! 可是定眼一看,發現事情並不簡單。node

詳細分析

1. 關於微信用戶登錄

相比於小程序,公衆號的微信用戶登錄更麻煩一些,須要經過連接的跳轉來獲取到openid,然而坑並不在這!!!,由於個人公衆號是個弱雞的我的訂閱號!連獲取openid的權限都沒有!!若是是原來,基本上就全劇終了,然而因爲前端事件開通了個PayJs的我的微信支付接口,而裏面恰好有個獲取openid的接口(不知道用了什麼黑科技),所以直接用這個接口來替代官方的接口獲取openidlinux

2. 用戶登錄與驗證

有了openid,後端直接根據openid在用戶表裏生成一條用戶記錄便可,關於如何生成,路子有不少,各位大佬都是明白人,根據具體狀況選擇合適的方法插一條就行ios

而後驗證的話固然用爛大街的JWT方式,後臺在用戶登錄後驗證完用戶信息,返回個token給前端,前端存着token,每次調用的時候都帶着,老套路了redis

3. 關於後臺爬取

因爲個人後端使用php寫的,所以引伸出了一些問題,vuex

爬取頁面截圖

這種事情php雖然有辦法但仍是弱雞一些沒有nodejs使用puppeteer來的方便,既然nodejs能很方便的解決,那麼天然就選擇nodejs來爬取頁面截圖,然而這就會涉及到php的後端和nodejs爬取程序交互的問題,這裏也有不少方式能夠解決,能夠經過接口,也能夠經過其餘方式,可是考慮到爬取任務的特色,在這裏我選擇了用redis作中間層(其餘中間件作任務隊列也都OJBK),利用redis的發佈訂閱模式,php將待爬取的任務放到redis裏,nodejs的程序也做爲訂閱者,獲取到任務,爬取完圖片後經過接口將相關信息返回給php的後臺chrome

圖片存放位置

考慮到手上是個乞丐版的服務器,若是把圖片放到服務器上,佔用空間不說,每次客戶端訪問從服務器加載圖片會佔用大量帶寬,影響網站性能,所以確定要找個圖牀啦,個人服務器是騰訊雲的給提供免費50G的對象存儲,有了這個就好說了,puppeteer爬取完圖片後把圖片先暫存到服務器上,而後把圖片上傳到圖牀,得到圖片連接,而後把服務器上的圖片刪掉,給php的後端返回的時候也只是返回一下圖片連接就行啦

定時任務

系統須要一個定時任務,定時從數據庫中取出須要進行爬取的數據,而後放入redis,然而php在這方面也是比較雞肋,雖然有Swoole這種牛批的框架,可是爲了解決一個小問題,不值當引入這麼大的框架,所以選個這種方案,作個接口,而後利用BT面板配置個linux的定時任務,每隔幾分鐘就訪問一下這個接口,而後接口就去搞一些列操做就完事啦

流程圖

流程仍是比較簡單的

設計

數據字段設計

根據上面的分析大概也知道了,核心功能區就是把用戶須要爬取的連接存下來,按期取出來爬一下,而後把爬取到的圖片連接存一下和用戶的任務關聯上便可,所以設計兩張表就OJBK,簡單的一批

  1. 任務表
字段名 含義
id 主鍵
user_id 用戶id
url 任務連接
start_time 開始時間
next_time 下次爬取時間
period 週期(以秒來存取)
period_type 週期類型 天、周、月
status 狀態
create_time 建立時間
update_time 更新時間
more 擴展屬性
  1. 任務圖片表
字段名 含義
id 主鍵
task_id 用戶id
url 任務連接
plan_time 計劃爬取時間
create_time 建立時間
update_time 更新時間

接口設計

數據表設計完了,如今來識別一下,看看須要哪些接口

用戶方面:

  • 獲取任務列表的接口(task/getByPage)
  • 增長任務的接口(task/add)
  • 刪除任務的接口(task/delete)
  • 修改任務的接口(task/update)
  • 獲取任務詳細的接口(task/get)
  • 查看任務已爬取圖片的接口(task/getScreenshotByPage)
  • 用戶登錄接口
  • 驗證用戶登陸狀態的接口

後臺管理方面:

各位看官根據狀況自行發揮啦

其餘接口:

  • 定時任務調用的接口(用來取處任務放入redis)
  • 接收爬取圖片信息的接口(供nodejs程序調用)

核心的接口就這些,足以撐起整個業務功能的閉環了

實現

實現分三部分分別實現,移動端、後臺、nodeJS爬取程序

採起的一些工具以下:

  • 後端:PHP/Thinkphp5.1 Nodejs
  • 數據庫:MySQL Redis
  • 移動端:Vue+Vant
  • 其餘:服務器、BT面板(懶人專用、誰用誰知道)、公衆號、對象存儲

後端:

都是普通的增刪改查,須要注意的是再添加任務的時候,須要在添加完成後當即生成一個爬取任務,爬一張圖做爲初始圖片

public function add(Request $request){
       //驗證輸入
        (new TaskAddValidate())->goCheck();
        //根據token獲取用戶id
        $userId=TokenService::getCurrentVars(TokenService::currentToken(),'id');
        $startTime=$request->param('start_time');
        $period=$request->param('period');
        $periodType=$request->param('period_type',0);
        $url=$request->param('url');
        //轉換一下時間戳,php裏是10位時間戳,js裏是精確到毫秒的13位
        if (strlen($startTime)>10){
            $startTime=substr($startTime,0,10);
        }
        $nextTime=$startTime+$period;
        $more=[];
        if($request->has('more')){
            $more=json_decode(htmlspecialchars_decode($request->param('more')),true);
        }
        $task=TaskModel::create([
            'start_time'=>$startTime,
            'next_time'=>$nextTime,
            'period'=>$period,
            'period_type'=>$periodType,
            'url'=>$url,
            'user_id'=>$userId,
            'more'=>$more
        ]);
        //直接生成一個爬取任務,發佈到redis
        RedisService::pushTask('tasks',$task);
        return ResultService::success('',$task);
    }
複製代碼

爬取圖片服務(使用puppeteer):

爬取並上傳到騰訊雲的對象存儲

const puppeteer = require('puppeteer');
const redis=require('redis');
const redisConfig=require('./redis.config');
var COS = require('cos-nodejs-sdk-v5');
const axios=require('axios')
const fs=require('fs');
const config=require('./config');

//訂閱redis,接收任務
const redisClient=redis.createClient(redisConfig.port,redisConfig.host);
redisClient.auth(redisConfig.password);
redisClient.subscribe('tasks');
redisClient.on('message',(channel,msg)=>{
    if(channel=='tasks'){
        let task=JSON.parse(msg);
       
            getScreenshot(task);
        
        
    }
});
//爬取截圖
async function getScreenshot(task){
    const browser = await puppeteer.launch({ headless: true,args: ['--no-sandbox', '--disable-setuid-sandbox'] })
    const page = await browser.newPage()
    page.setViewport({ 
        width: 1280, 
        height: 960,
        });
    await page.goto(task.url, {
        waitUntil: 'networkidle2' //等待頁面不動了,說明加載完畢了
    })
    let basePath=config.basePath;
    let filenName=`${task.id}-${Date.now()}.png`;
    let filePath=basePath+filenName;
    await page.screenshot({ path: filePath ,fullPage:true})
    //上傳圖片到雲對象存儲
    let imgRes=await uploadScreenshot(filePath,filenName);
    //將圖片連接回傳給php服務端
    let res=await axios.post(config.apiUrl,{
        id:task.id,
        url:'http://'+imgRes.Location,
        plan_time:task.next_time
    });
    //刪除圖片
    fs.unlink(filePath,(err)=>{
        if(err){
            console.log(`刪除文件不成功,緣由${err}`);
        }
        
    });
}

// 使用永久密鑰建立實例
var cos = new COS({
SecretId: '***',
SecretKey: '***'
});
//上傳圖片到雲對象存儲
function uploadScreenshot(filePath,filenName){
    return new Promise((resolve,reject)=>{
        cos.sliceUploadFile({
            Bucket: '***',
            Region: '***',
            Key: filenName,
            FilePath: filePath
            },(err,data)=>{
                if(!err){
                   resolve(data);
                }
                else{
                    reject(err);
                }
            });
    })
}
複製代碼

移動端:

用的是vue+vant進行移動端開發,因爲本菜雞沒什麼審美,基本上也就是直接對vant組件進行使用拼拼湊湊就完事了,

項目目錄以下(常規的一批,弱雞)

想分享的一點是,看了許多文章,對於在vue項目中進行網絡接口調用的方式,一部分人偏心於將全部的接口調用都以action的形式放到vuex裏,全部的數據基本上都集中在了vuex中,然而以我得理解, vuex是爲了全局數據共享而存在的,將全部數據都集中在vuex中可能會使vuex變得龐大,有違初衷,所以,我仍是將全部的接口按業務區分都放在了api文件夾中,在須要的時候直接調用便可

其中http.js是對axios進行的一個小封裝,添加了一些攔截器,

每一個業務文件夾中的接口均作成promise形式的方便並導出

import http from './../http'

let register=(params=null)=>http.post('/api/front/user/register',params);
let login=(params=null)=>http.post('/api/front/user/login',params);
let checkLogin=()=>http.get('/api/front/user/checkLogin');
let logout=()=>http.post('/api/front/user/logout');
let getUser=()=>http.get('api/front/user/get');
export default {
    register,
    login,
    logout,
    checkLogin,
    getUser
}

複製代碼

apis則是用來將不一樣業務的接口進行集中,在使用的時候直接引用apis就能夠了

import user from './user'
import portal from './portal'
import screenlapse from './screenlapse'

let exportApi={
    getSetting
};
Object.assign(exportApi,user,portal,screenlapse)
export default exportApi

複製代碼

上線:

本地測試沒問題,開始上線工做,結合上面的分析,咱們須要對三個部分進行上線

系統是Centos7,使用的是BT面板一鍵搭建lnmp環境,進行平常運維與站點管理

後端服務上線(php)

使用bt面板直接建一個站點便可,使用面板自帶的僞靜態配置下隱藏index.php

移動端

本地打包完成後,上傳到服務器,利用bt面板新建一個站點,該站點的目錄指向移動端的文件,而後須要配置一下反向代理來解決一下跨域,基本上就是把某一個標識開頭的請求轉發一下

location ^~ /apis/ 
    {
          proxy_pass https://api.yizhisamoye.cn/;
    }
    location ^~ /upload/ 
    {
          proxy_pass https://api.yizhisamoye.cn;
    }
複製代碼

爬取服務

注意兩點

  1. 爬取服務因爲只是一個簡單的小程序,並無使用node的http server相關的東西,所以無法使用pm2來啓動和管理,不過也不要緊,只要讓程序跑起來在後臺一直運行便可,使用命令來啓動便可,定位到爬蟲文件夾,使用命令nohup node spider.js便可

  2. 安裝完相關依賴啓動後,爬蟲報找不到chrome的錯誤,這時候須要對centos系統安裝一下插件和依賴來保障puppeteer的運行

yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y
複製代碼

最後

上線完後測試沒什麼問題,至此,完成了一個項目從需求到上線的所有過程,雖然沒有用什麼牛批技術,項目管理也基本沒有,可是畢竟是菜雞啊哈哈哈,主要仍是給一些我的開發者提供一個思路,以解決問題的目標去應用技術,而不是爲了應用技術而應用技術。 最後慣例仍是要放出二維碼供你們直接使用(小弟服務器辣雞,若反應慢了請你們諒解)~稍後整理好後會放出gayhub地址

相關文章
相關標籤/搜索