老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

栏目: Node.js · 发布时间: 5年前

内容简介:掘金里大佬太多了,分享各种牛批技术、代码、经验,看的美滋滋,但是在身为一只菜鸡的我也在思考,大家看了那么多文章,了解了那么多前沿技术,真正落地使用的又有多少?还是需要实战一下才能知道,什么时候需要用到什么技术手段,怎么合适的运用各种技术来解决问题。因此本篇文章并不是分享哪些技术多666,而是跟各位老哥一起根据一个具体需求,完成分析、设计编码、上线的全流程,形成一个真正可用的产品,以此来形成一种为了解决问题而选取相应技术的思维(牛批要先吹好)。Screenlapse也算是个比较有意思的产品了,它主要的功能就是

掘金里大佬太多了,分享各种牛批技术、代码、经验,看的美滋滋,但是在身为一只菜鸡的我也在思考,大家看了那么多文章,了解了那么多前沿技术,真正落地使用的又有多少?还是需要实战一下才能知道,什么时候需要用到什么技术手段,怎么合适的运用各种技术来解决问题。因此本篇文章并不是分享哪些技术多666,而是跟各位老哥一起根据一个具体需求,完成分析、设计编码、上线的全流程,形成一个真正可用的产品,以此来形成一种为了解决问题而选取相应技术的思维(牛批要先吹好)。

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

需求

Screenlapse也算是个比较有意思的产品了,它主要的功能就是帮助用户定期对指定网页进行截图,可以设置每天、每周、每月某一个特定时间点去做截图,截图随时预览,有了这么个服务你就可以给你的网站形成一个历史档案库,或者 默默的视奸竞争对手网站网页的变化

然而!!它是个PC站没有移动端,而且他好像被墙了,百度也不索引它 那么机会来了,我们自己做一个 本土化的移动版的Screenlase ,顺便把他做到公众号里

先放个成品

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)
老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)
老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)
老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

分析

冷静睿智机敏的分析一下问题

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

初步分析

用户功能:

  1. 微信用户登陆
  2. 用户添加爬取任务
  3. 用户删除爬取任务
  4. 用户修改爬取任务
  5. 用户查看所有任务
  6. 用户查看某一个任务的截图

后端功能:

  1. 定时把需要爬取的链接抓取个截图存下来

这么一看功能简单的一批,不就是增删改查么,辣鸡! 但是定眼一看,发现事情并不简单。

详细分析

1. 关于微信用户登陆

相比于小程序,公众号的微信用户登陆更麻烦一些,需要通过链接的跳转来获取到openid,然而坑并不在这!!!,因为我的公众号是个弱鸡的个人订阅号!连获取openid的权限都没有!!如果是原来,基本上就全剧终了,然而由于前端事件开通了个PayJs的个人微信支付接口,而里面刚好有个获取openid的接口(不知道用了什么黑科技),因此直接用这个接口来替代官方的接口获取openid

2. 用户登陆与验证

有了openid,后端直接根据openid在用户表里生成一条用户记录即可,关于如何生成,路子有很多,各位大佬都是明白人,根据具体情况选择合适的方法插一条就行

然后验证的话当然用烂大街的JWT方式,后台在用户登陆后验证完用户信息,返回个token给前端,前端存着token,每次调用的时候都带着,老套路了

3. 关于后台爬取

由于我的后端使用 php 写的,因此引申出了一些问题,

爬取页面截图

这种事情php虽然有办法但还是弱鸡一些 没有nodejs使用puppeteer来的方便 ,既然nodejs能很方便的解决,那么自然就选择nodejs来爬取页面截图,然而这就会涉及到php的后端和nodejs爬取程序交互的问题,这里也有很多方式可以解决,可以通过接口,也可以通过其他方式,但是考虑到爬取任务的特点,在这里我选择了用 redis 做中间层(其他中间件做任务队列也都OJBK), 利用redis的发布订阅模式,php将待爬取的任务放到redis里,nodejs的程序也作为订阅者,获取到任务,爬取完图片后通过接口将相关信息返回给php的后台

图片存放位置

考虑到手上是个乞丐版的服务器,如果把图片放到服务器上,占用空间不说,每次客户端访问从服务器加载图片会占用大量带宽,影响网站性能,因此肯定要找个图床啦,我的服务器是腾讯云的给提供免费50G的对象存储,有了这个就好说了, puppeteer爬取完图片后把图片先暂存到服务器上,然后把图片上传到图床,获得图片链接,然后把服务器上的图片删掉 ,给php的后端返回的时候也只是返回一下图片链接就行啦

定时任务

系统需要一个定时任务,定时从数据库中取出需要进行爬取的数据,然后放入redis,然而php在这方面也是比较鸡肋,虽然有Swoole这种牛批的框架,但是为了解决一个小问题,不值当引入这么大的框架,因此选个这种方案,做个接口, 然后利用BT面板配置个 linux 的定时任务,每隔几分钟就访问一下这个接口 ,然后接口就去搞一些列操作就完事啦

流程图

流程还是比较简单的

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

设计

数据字段设计

根据上面的分析大概也知道了,核心功能区就是把用户需要爬取的链接存下来,定期取出来爬一下,然后把爬取到的图片链接存一下和用户的任务关联上即可, 因此设计两张表就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组件进行使用拼拼凑凑就完事了,

项目目录如下(常规的一批,弱鸡)

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)
想分享的一点是,看了许多文章,对于在vue项目中进行网络接口调用的方式,一部分人偏爱于将所有的接口调用都以action的形式放到vuex里,所有的数据基本上都集中在了vuex中,然而以我得理解, vuex是为了全局数据共享而存在的

,将所有数据都集中在vuex中可能会使vuex变得庞大,有违初衷,因此,我还是将所有的接口按业务区分都放在了api文件夹中,在需要的时候直接调用即可

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

其中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地址

老菜鸡陪你一起从需求到上线搞出一个Chinese版的Screenlapse(大佬忽略该条)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

We Are the Nerds

We Are the Nerds

Christine Lagorio-Chafkin / Hachette Books / 2018-10-2 / USD 18.30

Reddit hails itself as "the front page of the Internet." It's the third most-visited website in the United States--and yet, millions of Americans have no idea what it is. We Are the Nerds is an eng......一起来看看 《We Are the Nerds》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具