小文字 吃饭,睡觉,遛狗头

低成本“建站”回忆录

img

“建站”是一个很古老的话题了,互联网有各种快速建站的小广告。

相信每位开发者都有过各种倒腾的经历,随着大量平台工具的兴起,建个开发者博客什么的,早已旧时王谢堂前燕,飞入寻常百姓家。而移动端开发者学习/掌握大前端技术体系是趋势所在,了解前后端/运维等知识也多有裨益。

在Android部门内,开发维护了一些平台工具,目前已经基本形成一套完整的工具链,既有在线工具集,也有AVM平台58APP协议平台。在参与开发这些项目的过程中,有幸为这些不同框架体系的项目添砖加瓦。

那么如何以较低的开发/学习成本,开发一个完整站点并上线是这篇文章将要介绍的。

本文基于笔者的一些倒腾案例总结而来,包教不会,不正确的地方欢迎交流/指正。

扫码预览:

cli_300px

0x1 项目构思

作为一枚宅男,看影视剧是一个难以割舍的爱好,英剧/美剧/韩剧都有很多吸引人的地方。

作为一枚技术宅,是不是可以自己搞一个小电影的网站/客户端?

学习研究一个东西肯定是要做一些平时做的少的东西,才有满足感;我们跳过客户端开发,定个小目标:

做一个适配手机浏览器的小网站,能展示电影列表信息,并且点击下载资源。

完成这个一句话目标,我们需要考虑的事情可以用一张图来概括:

小站电影-脑图

下面我们顺着思维脑图,逐一介绍下整个开发历程。

0x2 前端开发

首先整个网站的前端是一个独立项目。我们走的是React技术路线,通过create-react-app这个脚手架工具来创建前端工程。不要问我为什么,作为新手不用这个的话,大量新的框架概念术语,会让你的脑容量瞬间不够用。

这个脚手架的说明介绍也很干净利落:

  • Less to Learn
  • Only One Dependency
  • No Lock-In

create-react-app

0x2.1 模板工程

成熟的框架,都会有一套模板工程,React当然不例外。它的发车姿势比较简单,真的是一个命令搞定:npx create-react-app my-app

create-project

有了模板工程以后,就可以开始写你的业务界面了。

为了让我们的页面看起来更美观&专业,强力建议选择一个UI组件库,Bootstrap,Material UI都可以,毕竟这是一个看脸的世界。这里我们选择的是Semantic UI React, 长得好看还好用

semantic-react-logo

0x2.2 页面开发

用react写界面,大部分工作都在构造Component,一个Component写法如下:

class SegmentExamplePlaceholderGrid extends React.Component {
  render(){
    return (<div></div>)
  }
}
export default  SegmentExamplePlaceholderGrid

如果你的Component非常简单,也不需要实现任何生命周期,只有一个基本的render,也可以简写。举个例子,如果要实现下面这个效果,利用Sementic UI组件,可以这么写:

react-sample-1

import React from 'react'
import { Button, Divider, Grid, Header, Icon, Search, Segment } from 'semantic-ui-react'

const SegmentExamplePlaceholderGrid = () => (
  <Segment placeholder>
    <Grid columns={2} stackable textAlign='center'>
      <Divider vertical>Or</Divider>

      <Grid.Row verticalAlign='middle'>
        <Grid.Column>
          <Header icon>
            <Icon name='search' />
            Find Country
          </Header>

          <Search placeholder='Search countries...' />
        </Grid.Column>

        <Grid.Column>
          <Header icon>
            <Icon name='world' />
            Add New Country
          </Header>
          <Button primary>Create</Button>
        </Grid.Column>
      </Grid.Row>
    </Grid>
  </Segment>
)

export default SegmentExamplePlaceholderGrid

“小站电影”项目,这里这里写的是一个List,展示一个电影列表:

web-preview

回顾整个案例的开发工作,前端页面和问题解决是最耗时间的,特别是对前端知识体系并不全面的初学者:

  • 长列表加载,无限上拉与分页衔接
  • 侧滑菜单后滑动导致菜单页留白
  • 弱网图片加载的占位图&异常图处理
  • 页面按需加载
  • 适配Mobile和大屏PC

phone-home

0x3 后端开发

前端页面有了,我们需要为页面提供数据。那么我们的服务器用什么来做呢?

为了上手快,降低门槛,可以使用Node作为后端服务器的运行环境,这样可以直接使用Node的http模块,也可以使用express之类的框架。

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello Express!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

类似的,我们可以开发真实的API,并且测试接口的数据返回情况。这里我们通过curl来简单测试下:

aven-mac-pro-2:~ aven$curl localhost:3001/api/movie/list/9/0 |jq .

我门的API是以json格式返回的,为了格式化看得清晰点,可以使用jq做输出。

api-sample-1

前后端工程分离的时候,在请求一个接口的时候需要写上接口全路径,通过proxy可以简化这个问题,只写对路径,然后后期部署到同一路径即可。这样做出来书写剪片,还可以可以免去跨域的问题。

"proxy": "http://localhost:3001/",

注意: proxy配置仅对开发模式生效。

0x4 数据采集

按部就班,我们的接口要返回数据,但是数据又从哪里来?

接下来就是如何去找数据。互联网上资源非常多,随便找找就有一堆,这里可以考虑下pv比较大的电影天堂。

0x4.1 xposed

电影天堂上有很多免费的良心资源,还有客户端app,忽略页面比较渣,还是能用的。抓包一下可以看到他的请求情况:

api-sample-3

api-sample-2

如果我们直接请求模拟这个接口请求,会失败的,通过观察可以发现header里面有两个字段比较可疑:

x-header-request-imei: 89014103211118510720
x-header-request-key: e62641ff809149a261ac7db336deb399

进一步反编译app,可以知道这个x-header-request-key是个加密串,

 public final native String headerRequestKey(Context context, String str, String str2);

下面我们就模拟一下,加入加密的自定义头:

api-sample-4

同时需要开发两个xposed模块,对应用进行签名拦截,hook做两件事:

  1. 获取正版应用的签名字节数据
  2. 替换测试应用的签名

hook-1

hook-2

接下来就可以开始起飞了,要注意,接口调用不要太频繁,不然ip会被封的,也不要问我是怎么知道:)

为了方便测试,和更新电影数据,我们简单完善采集流程,做一个采集的app,支持批量间隔采集和断点续传采集:

device-2018-12-26-104411

关于如何安装xposed框架,开发module不在我们的讨论范围之内,感兴趣可以去深入学习下:

de.robv.android.xposed.installer

做采集其实做好的还是通过后端来做,这样做定时采集和数据更新会比较方便。毕竟服务器是24小时在线的,但是你的app主要是靠人操作。这里我们只是为了拉取一批样本数据,所以倒也无所谓那种方式。

0x4.2 网页爬虫

除了客户端破解,也可以考虑去扒一下它的网页,这样不需要反编译什么的,只需要好写好脚本去分析目标html。在实际测试中,构建后端爬虫不单要分析页面,还要考虑数据容错(静态分析的页面只是抽样,存在差异的情况),还需要构建一整套电影数据的存储结构,各种字段的映射,这个过程比较繁琐。

crawler-api

其实电影天堂的网页爬虫,这个网上已经有人做过,不再介绍。除了构建自己的爬虫程序,其实还有一个办法,去找一些资源采集器,用别人开发好的采集工具批量拉取数据。

0x5 服务器搭建

让我们回顾一下,现在数据也有了,需要考虑下一步,怎么把我们的服务和网站提供给外网用户访问,总不可能一直的本机跑吧。

如果手上有服务器的话可以直接用,不需要单独再买一个。没有的话,可以采购一台,国内的阿里云也不错,配置不用太好,选个便宜的,单核1G内存就够玩了。

1 CPU, 25G Storage, 1G RAM

为了好好利用和管理手上的机器,可以使用docker来部署应用,这样可以保证服务迁移和主机环境相互不影响。避免直接部署在主机上后,需要安装一堆的运行环境和依赖配置。

我们内部的平台工具目前使用的是Portainer来管理docker实例,使用比较简单,功能也足够强大,我的主机也采用它来管理服务。下面是portainer的官方使用案例:

$ docker volume create portainer_data
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

启动一个docker镜像实例,一般考虑下磁盘映射,端口号就差不多,具体参数可以查阅API文档;

部署完服务,就可以登陆后台管理。新增,删除,重启docker镜像都可以直接操作。

portainer-login

这里我们新增了一个Node镜像,由于没有什么特殊依赖,直接使用了官方的镜像。启动镜像后,远程登陆然后可以开始你的骚操作,比如更新代码,启动脚本什么的。当然也可以把这些所有流程写入Dockerfile,构建我们自己的镜像,这样每次实例化对象,启动后不需要单独操作。

portainer-manager

0x6 部署上线

现在万事具备只欠东风!

分配一个域名和我们的主机ip绑定,这样广大人民群众就可以统一域名访问我们的主机。

这一步一般是在自己的域名提供商那边操作,比如万网后台页面就可以完成配置。域名和ip绑定后,还需要将服务部署到具体端口才可以对外服务,端口指定在服务器上配置。

这里需要确定docker的端口映射,让主机80端口映射到docker实例的8080端口,在docker内部,我们的网站只需要监听8080端口就可以对外提供服务了。

为么是80而不是443或者其他接口呢?

这主要是因为我没有提供https接口,80端口是http协议默认端口,不明写的话浏览器默认如此,也就是所有请求http://io.hacktons.cn的等同于http://io.hacktons.cn:80

平时开发都是在本地,执行npm start就能跑起项目,如今要上线了,自然后准备一下脚本和配置,确保本地开发使用开发端口,上线使用上线端口,并且前端代码是上线的模式:

react-script为我们做了很好的封装,只需执行不同命令,带上参数配置:

"scripts": {
  "start": "react-scripts start",
  "build": "GENERATE_SOURCEMAP=false react-scripts build",
  "test": "react-scripts test --env=jsdom",
  "eject": "react-scripts eject"
}

发布的时候走build命令,不生成source map: GENERATE_SOURCEMAP=false react-scripts build

我们看到所有源码经过webpack打包之后全部混淆,集中到了几个chunk/bundle文件内置:

deploy-1

后端代码由于不会暴露给前端,暂时没有什么特殊操作,只需要考虑端口号:

"scripts": {
  "server": "node server.js",
  "prod":"PORT=8080 NODE_ENV=production npm run server"
}

我们可以吧所有要做的配置固化后,写成脚本,自动/手动执行:

deploy-2

0x7 总结

现在点击访问io.hacktons.cn就可以看到我们的小网页了。

讲一个段子:JavaScript是最好的语言,我不是针对PHP

⚠️本项目仅做技术研究,涉及的电影资源严禁非法下载传播。


参考

  • https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/
  • https://facebook.github.io/create-react-app/docs/proxying-api-requests-in-development

  • https://github.com/facebook/create-react-app/issues/1004

  • https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md