低成本“建站”回忆录
2018-12-7 小文字“建站”是一个很古老的话题了,互联网有各种快速建站的小广告。
相信每位开发者都有过各种倒腾的经历,随着大量平台工具的兴起,建个开发者博客什么的,早已旧时王谢堂前燕,飞入寻常百姓家。而移动端开发者学习/掌握大前端技术体系是趋势所在,了解前后端/运维等知识也多有裨益。
在Android部门内,开发维护了一些平台工具,目前已经基本形成一套完整的工具链,既有在线工具集,也有AVM平台,58APP协议平台。在参与开发这些项目的过程中,有幸为这些不同框架体系的项目添砖加瓦。
那么如何
以较低的开发/学习成本,开发一个完整站点并上线
是这篇文章将要介绍的。
本文基于笔者的一些倒腾案例总结而来,包教不会,不正确的地方欢迎交流/指正。
扫码预览:
0x1 项目构思
作为一枚宅男,看影视剧是一个难以割舍的爱好,英剧/美剧/韩剧都有很多吸引人的地方。
作为一枚技术宅,是不是可以自己搞一个小电影的网站/客户端?
学习研究一个东西肯定是要做一些平时做的少的东西,才有满足感;我们跳过客户端开发,定个小目标:
做一个适配手机浏览器的小网站,能展示电影列表信息,并且点击下载资源。
完成这个一句话目标,我们需要考虑的事情可以用一张图来概括:
下面我们顺着思维脑图,逐一介绍下整个开发历程。
0x2 前端开发
首先整个网站的前端是一个独立项目。我们走的是React技术路线,通过create-react-app这个脚手架工具来创建前端工程。不要问我为什么,作为新手不用这个的话,大量新的框架概念术语,会让你的脑容量瞬间不够用。
这个脚手架的说明介绍也很干净利落:
- Less to Learn
- Only One Dependency
- No Lock-In
0x2.1 模板工程
成熟的框架,都会有一套模板工程,React当然不例外。它的发车姿势比较简单,真的是一个命令搞定:npx create-react-app my-app
有了模板工程以后,就可以开始写你的业务界面了。
为了让我们的页面看起来更美观&专业,强力建议选择一个UI组件库,Bootstrap,Material UI
都可以,毕竟这是一个看脸的世界。这里我们选择的是Semantic UI React, 长得好看还好用
0x2.2 页面开发
用react写界面,大部分工作都在构造Component,一个Component写法如下:
class SegmentExamplePlaceholderGrid extends React.Component {
render(){
return (<div></div>)
}
}
export default SegmentExamplePlaceholderGrid
如果你的Component非常简单,也不需要实现任何生命周期,只有一个基本的render
,也可以简写。举个例子,如果要实现下面这个效果,利用Sementic UI组件,可以这么写:
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,展示一个电影列表:
回顾整个案例的开发工作,前端页面和问题解决是最耗时间的,特别是对前端知识体系并不全面的初学者:
- 长列表加载,无限上拉与分页衔接
- 侧滑菜单后滑动导致菜单页留白
- 弱网图片加载的占位图&异常图处理
- 页面按需加载
- 适配Mobile和大屏PC
- …
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做输出。
前后端工程分离的时候,在请求一个接口的时候需要写上接口全路径,通过proxy可以简化这个问题,只写对路径,然后后期部署到同一路径即可。这样做出来书写剪片,还可以可以免去跨域的问题。
"proxy": "http://localhost:3001/",
注意: proxy配置仅对开发模式生效。
0x4 数据采集
按部就班,我们的接口要返回数据,但是数据又从哪里来?
接下来就是如何去找数据。互联网上资源非常多,随便找找就有一堆,这里可以考虑下pv比较大的电影天堂。
0x4.1 xposed
电影天堂上有很多免费的良心资源,还有客户端app,忽略页面比较渣,还是能用的。抓包一下可以看到他的请求情况:
如果我们直接请求模拟这个接口请求,会失败的,通过观察可以发现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);
下面我们就模拟一下,加入加密的自定义头:
同时需要开发两个xposed模块,对应用进行签名拦截,hook做两件事:
- 获取正版应用的签名字节数据
- 替换测试应用的签名
接下来就可以开始起飞了,要注意,接口调用不要太频繁,不然ip会被封的,也不要问我是怎么知道:)
为了方便测试,和更新电影数据,我们简单完善采集流程,做一个采集的app,支持批量间隔采集和断点续传采集:
关于如何安装xposed框架,开发module不在我们的讨论范围之内,感兴趣可以去深入学习下:
做采集其实做好的还是通过后端来做,这样做定时采集和数据更新会比较方便。毕竟服务器是24小时在线的,但是你的app主要是靠人操作。这里我们只是为了拉取一批样本数据,所以倒也无所谓那种方式。
0x4.2 网页爬虫
除了客户端破解,也可以考虑去扒一下它的网页,这样不需要反编译什么的,只需要好写好脚本去分析目标html。在实际测试中,构建后端爬虫不单要分析页面,还要考虑数据容错(静态分析的页面只是抽样,存在差异的情况),还需要构建一整套电影数据的存储结构,各种字段的映射,这个过程比较繁琐。
其实电影天堂的网页爬虫,这个网上已经有人做过,不再介绍。除了构建自己的爬虫程序,其实还有一个办法,去找一些资源采集器,用别人开发好的采集工具批量拉取数据。
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镜像都可以直接操作。
这里我们新增了一个Node镜像,由于没有什么特殊依赖,直接使用了官方的镜像。启动镜像后,远程登陆然后可以开始你的骚操作,比如更新代码,启动脚本什么的。当然也可以把这些所有流程写入Dockerfile,构建我们自己的镜像,这样每次实例化对象,启动后不需要单独操作。
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文件内置:
后端代码由于不会暴露给前端,暂时没有什么特殊操作,只需要考虑端口号:
"scripts": {
"server": "node server.js",
"prod":"PORT=8080 NODE_ENV=production npm run server"
}
我们可以吧所有要做的配置固化后,写成脚本,自动/手动执行:
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