目录
0 前言
上一篇博客介绍了前端工程化实践的第一部分内容——搭建含有单元测试的脚手架工具,本篇博客继续前端工程化的实践,实现一个项目发布系统,以便代码编写测试完成后能够方便地将目标代码部署到线上服务系统中
1 实现一个线上Web服务
1.1 发布系统的组成
- 线上服务系统:Web服务器
- 发布系统:用于将项目发布到线上服务系统中
- 发布工具:和发布系统相连接的发布工具
1.2 初始化Server
找一台服务器(可以是真实的或者利用虚拟机代替)依次安装上node,npm,express等环境
1.3 利用Express框架编写服务器
- 初始化express项目:
npx express-generator
- 安装依赖:
npm install
- 将项目代码远程拷贝到server,然后执行
npm start
,开发环境是linux系统可以用scp命令(注意利用scp将项目代码拷贝到虚拟机服务器时需要设置虚拟机的端口转发规则),如果是Windows系统可以用SecureCRT软件
2 实现一个发布系统
2.1 用node.js启动一个简单的server
const http = require("http")
http.createServer((req, res)=>{
console.log(req)
res.end("Hello world")
}).listen("8082")
2.2 编写简单的发送请求功能
const http = require("http")
let request = http.request({
hostname: "127.0.0.1",
port: 8082
}, response =>{
console.log(response)
})
request.end()
2.3 了解Node.js流
- readableStream
- fs.createReadStream():创建可读的文件流
- Event:“data”,可读流有数据可读
- Event:“end”,可读流数据已读完
- Event:“close”,可读流数据被关闭
let file = fs.createReadStream("package.json")
file.on("data", chunk=>{
console.log(chunk.toString())
})
file.on("close", ()=>{
console.log("read finished")
})
- writableStream
- fs.createWriteStream(): 创建可写的文件流。
- writable.write(chunk[,encoding][,callback])
- writable.end()
2.4 改造server
- publish-server与线上服务系统server部署在同一台服务器上,用于接收publish-tool的文件上传请求并存储到线上服务系统server项目路径下
- publish-server和server都可以在package.json文件中配置一个
publish
命令,用于将本地代码拷贝到远端
"scripts": {
"publish": "scp -r ./* ygj@192.168.43.145:/home/ygj/server",
}
2.5 实现多文件发布
- 上传部分关键代码
const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
})
archive.directory('./resources', false); // 从resources目录中追加文件,false表示追加时不改变文件夹名称
archive.finalize() // 结束文件追加
archive.pipe(request)
- 接收解压
req.pipe(unzipper.Extract({ path: '../server/public/resources' }))
2.6 用github OAuth做一个具有鉴权功能的登录实例
目的是为了实现发布系统的鉴权功能,具体包括以下几步:
- 创建github app获取client-id,client-secret
- 在github创建页面依次将GitHub App name、Home Page URL(http://localhost)、Callback URL(http://localhost:8082/auth)、Webhook URL(https://github.com)填写上
- 然后点击“Create Github app"按钮
- 成功创建后查看client-id,client-secret
- 在publish-tool端,打开https://github.com/login/oauth/authorize?client_id=步骤1中的client-id(该链接有时无法访问)
- 在publish-server端,auth路由:接受github返回的code,用code+client_id+client_secret跟github换认证token,然后向publish-tool端返回一个发布链接
function auth(req, res) {
let query = querystring.parse(req.url.match(/^\/auth\?([\s\S]+)$/)[1])
getToken(query.code, function(info){
console.log(info)
//res.write(JSON.stringify(info))
// res.write(`<html><head></head><body><a href='http://localhost:8083/?token=${info.access_token}'>publish</a></body></html>`)
res.write(`<html><head></head><body><a href='http://localhost:8083/?token=${info.access_token}'>publish</a></body></html>`)
res.end()
})
}
function getToken(code, callback) {
let request = https.request({
hostname: "github.com",
path: `/login/oauth/access_token?code=${code}&client_id=Iv1.28dda5cca6020d5e&client_secret=394ae94b88c4b165c1fadfef1b10a06012a9a366`,
port: 443,
method: "POST",
}, function(response){
let body = ""
response.on("data", chunk => {
body += chunk.toString()
})
response.on("end", chunk => {
callback(querystring.parse(body))
})
})
request.end() // 将请求发出
}
- 在publish-tool端,创建server,接受发布链接请求中的token,并向publish-server发送带token的publish请求
http.createServer((req, res)=>{
let query = querystring.parse(req.url.match(/^\/\?([\s\S]+)$/)[1])
console.log(query)
publish(query.token)
}).listen(8083)
function publish(token) {
let request = http.request({
// hostname: "192.168.43.145",
hostname: "127.0.0.1",
port: 8082,
method: "POST",
path: "/publish?token="+token,
headers: {
"Content-Type": "application/octet-stream",
}
}, response =>{
console.log(response)
})
const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
})
archive.directory('resources', false);
archive.finalize() // 为压缩工具填好了压缩的内容
archive.pipe(request)
archive.on("close", chunk=>{
request.end(chunk)
})
- 在publish-server端,publish路由:向github发送带token的请求获取用户信息,检查权限,接受发布
function publish(req, res) {
let query = querystring.parse(req.url.match(/^\/publish\?([\s\S]+)$/)[1])
getUser(query.token, info=>{
if(info.login === "blateyang") {
let fws = fs.createWriteStream("../server/public/resources.zip")
req.pipe(fws)
// req.pipe(unzipper.Extract({ path: '../server/public/resources' }))
req.on("end", function(){
res.end("success!")
fws.close()
// 下面两句需要放在req.on("end")的回调中,而不能放在req.on("end")的后面,因为是异步的
let fsReadable = fs.createReadStream("../server/public/resources.zip")
fsReadable.pipe(unzipper.Extract({ path: '../server/public/resources' }))
})
}
})
}
function getUser(token, callback) {
let request = https.request({
hostname: "api.github.com",
path: `/user`,
port: 443,
method: "GET",
headers: {
Authorization: `token ${token}`,
"User-Agent": "blateyang-toy-publish-auth"
}
}, function(response){
let body = ""
response.on("data", chunk => {
body += chunk.toString()
})
response.on("end", chunk => {
console.log(body)
callback(JSON.parse(body))
})
})
request.end() // 将请求发出
}
详细代码请参考我的github
3 小结
本篇博客主要介绍了如何搭建一个项目发布系统,首先需要搭建一个线上服务系统,然后编写项目发布系统和发布工具,最后利用一个github OAuth,实现项目发布系统的授权登录。整个过程还介绍了node可读、可写流的使用、多文件压缩库archiver和解压库unzipper以及github OAauth鉴权API的使用。
ps:如果觉得此文对你有帮助或启发,请不要吝惜你的点赞和分享,你的支持就是对我最大的鼓励。如有疑问,请留言或私信交流