更新说明:对文章目录排版做了调整。
更新时间:2022-05-04

第一章 本周导学

1-1 本周介绍和学习方法

  • 云发布原理、架构和实现
  • OSS API(OSS接入指南)
  • 上线发布流程(支持Hash和History模式发布)
  • 附赠:Node高分库分享(awesome-nodejs)

第二章 云发布模块架构设计

2-1 前端发布OSS架构设计

  • CloudBuild实例添加参数:prod(是否为正式版本)
  • 添加准备阶段 :获取OSS文件,询问是否覆盖

2-2 云发布架构和流程设计
点击查看【processon】

第三章 云发布功能开发

本章内容更新代码为分支 lesson31

3-1 实现云发布前的预检查逻辑

在上一节中,mdoels/git/lib/index.js中,有preparePublish方法,之前检查命令是否为npm或cnpm
在上一节基础上,这里添加了,执行构建命令是否为 build,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// models/git/lib/index.js


async preparePublish(){
log.info('开始进行云构建前代码检查')
…………
const pkg = this.getPackageJson()
const buildCmdArray = this.buildCmd.split('')
const buildLastCmd = buildCmArrat.slice(-1).toString()
if(!pkg.scripts || !Object.keys(pkg.scripts).includes(lastCmd)){
throw new Error(`${this.buildCmd}命令不存在`)
}
log.success('云构建代码预检查通过')
}

getPackageJson(){
const pkgPath = path.resolve(this.dir,'package.json')
if(!fs.existSync(pkgPath)){
throw new Error(`package.json不存在,源码目录:${this.dir}`)
}
return fse.readJsonSync(pkgPath)
}

3-2 静态资源服务器类型选择逻辑开发

  • 上一节我们检查了build这个命令
  • 接着,我们需要选择上传资源服务器的类型,也就是OSS
    • 这里的代码是为了后续如果要修改资源服务器类型,可以进行代码的再开发–添加其他资源服务器类型。
    • 与检查git_token或者git_server等一样,本节需要去新建或者检查.git_publish文件,代码实现如下
  • 最后,cloudscope-cli publish这个命令添加了 --prod这个参数,以及.git_publish文件内容,传入到 Cloudbuild类中去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// models/git/lib/index.js
const GIT_PUBLISH_FILE='.git_publish'
const GIT_PUBLISH_TYPE=[{
name:'OSS',
value:'oss'
}]

async preparePublish(){
log.info('开始进行云构建前代码检查')
………………
log.success('云构建代码预检查通过')

const gitPublishPath = this.createPath(GIT_PUBLISH_FILE)
let gitPublish = readFile(gitPublishPath)
if(!gitPublish){ // 如果没有读取到.git-publish文件中的内容
gitPublish = await this.choicePublish(gitPublishPath)
log.success('git publish 写入成功',`${gitPublish} -> ${gitPublishPath}`)
}else{ // 如果读取到了 内容
log.success('git publish 文件读取成功!')
}
this.gitPublish = gitPublish
}

async choicePublish(gitPublishPath){
const gitPublish = (await inquirer.prompt({
type:'list',
name:'gitPublish',
message:'请选择你想要上传代码的平台',
choices:GIT_PUBLISH_TYPE
})).gitPublish;
writeFile(gitPublishPath,gitPublish)
return gitPublish
}

关于 --prod参数的传入,与–refreshToken等一样,这里就不做演示了。

3-3 云发布服务端预检查逻辑实现

本节是服务端相关的代码实现,同样代码为分支 lesson31
主要是拿到云构建结果的dist或者build目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// app/io/controller/build.js
await prePublish(cloudBuildTask,socket,helper)

async function prePublish(cloudBuildTask,socket,helper){
socket.emit('build',helper.parseMsg('pre-publish',{
message:'开始发布前检查'
}))
const prePublishRes = await cloudBuildTask.prePublish()
if(!prePublishRes || Object.is(prePublishRes.code,FAILED)){
socket.emit('build',helper.parseMsg('pre-publish failed',{
message:'发布前检查失败,失败原因:' + (prePublishRes && prePublishRes.message ? prePublishRes.message:'未知')
}))
throw new Error('发布终止')
}else{
socket.emit('build',helper.parseMsg('pre-publish',{
message:'发布前检查通过'
}))
}
}

//app/models/cloudBuildTask.js
async prePublish(){
//获取构建结果
const buildPath = this.findBuildPath()
//检查构建结果
if(!buildPath){
return this.failed('未找到构建结果,请检查')
}
this._buildPath = buildPath
return this.success()
}



findBuildPath(){
const buildDir =['build','dist']
const buildPath = buildDir.find(dir=>fs.existsSync(path.resolve(this._sourceCodeDir,dir)))
this._ctx.logger.info('buildPath',buildPath)
if(buildPath){
return path.resolve(this._sourceCodeDir,buildPath)
}
return null
}

3-4 创建OSS bucket+OSS实例化开发(服务端)

  • cnpm install -S ali-oss
  • 客户端传递prod参数到服务端,服务端根据prod参数,获取不同环境的OSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// app/models/CloudBuildTask.js
const OSS = require('./OSS')
const config = require('../../config/db')
class CloudBuildTask(options,ctx){
constructor(){
this._prod = options.prod === 'true' ? true : false
}
}

async prepare(){
fse.ensureDirSync(this._dir)
fse.emptyDirSync(this._dir)
this._git = new Git(this._dir)
if(this._prod){ //生产准备OSS
this.oss =new OSS(config.OSS_PROD_BUCKET)
}else{//测试
this.oss =new OSS(config.OSS_DEV_BUCKET)
}
console.log(this.oss)
return this.success()
}

// app/models/OSS.js
'use strict';
const OSS = require('ali-oss');
const config = require('../../config/db')
class OSS{
constructor(bucket){
this.oss = new OSS({
region:config.OSS_REGION,
accessKeyId: config.OSS_ACCESS_KEY,
accessKeySecret: config.OSS_ACCESS_SECRET_KEY,
bucket,
})
}
}

module.exports = OSS

//app/config/db.js
const fs = require('fs')
const path = require('path')
const userHome = require('user-home')
const OSS_ACCESS_KEY = ''
const OSS_ACCESS_SECRET_KEY = fs.readFileSync(path.resolve(userHome,'.cloudscope-cli','oss_access_secret_key')).toString()
const OSS_PROD_BUCKET=''
const OSS_DEV_BUCKET=''
const OSS_REGION=''

以上关于OSS Key等的配置,可去阿里云免费试用一个月OSS对象存储功能,填入相应的值。

3-5 云发布核心流程:上传OSS功能开发
代码逻辑如下

  • npm i -S glob
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// app/io/controller/build.js

await publish(cloudBuildTask,socket,helper)

async function publish(cloudBuildTask,socket,helper){
socket.emit('build',helper.parseMsg('publish',{
message:'开始发布'
}))
const publishRes = await cloudBuildTask.publish()
if(!publishRes || Object.is(publishRes.code,FAILED)){ // downlod下载失败
socket.emit('build',helper.parseMsg('publish failed',{
message:'发布失败'
}))
return
}else{
socket.emit('build',helper.parseMsg('publish',{
message:'发布成功'
}))
}
}

// app/models/CloudBuildTask.js
async publish(){
return new Promise((resolve,reject) =>{
glob('**',{
cwd:this._buildPath,
nodir:true,
ignore:'**/node_modules/**'
},(err,files) =>{
if(err){
resolve(false)
}else{
Promise.all(files.map(async file=>{
console.log(file)
const filePath = path.resolve(this._buildPath,file)
const uploadOSSRes = await this.oss.put(`${this._name}/${file}`,filePath)
return uploadOSSRes
})).then(()=>{
resolve(true)
}).catch(err=>{
this._ctx.logger.error(err)
resolve(false)
})
}
})
})
}

// app/models/OSS.js

async put(object, localPath, options = {}) {
await this.oss.put(object, localPath, options);
}

最后经过测试,在阿里云控制台看到相关的OSS文件已上传。

3-6 OSS域名绑定 + CDN绑定

  • 域名绑定
  • CDN绑定

第四章 云发布流程完善

4-1 获取OSS API开发
服务端

  • router.js中添加路由 router.get(‘/project/oss’, controller.project.getOSSProject);

本节主要是获取OSS上传的文件,使用oss的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// app/controller/project.js
const { failed } = require("../utils/request")
const OSS = require('../models/OSS')
const config = require('../../config/db')

async getOSSProject(){
const { ctx } = this
const ossProjectType = ctx.query.type
const ossProjectName = ctx.query.name
if(!ossProjectName){
ctx.body = failed('项目名称不存在)
return
}
if(!ossProjectType){
ossProjectType = 'prod'
}
let oss;
if(Object.is(ossProjectType,'prod')){
oss = new OSS(config.OSS_DEV_BUCKET)
}else{
oss = new OSS(config.OSS_PROD_BUCKET)
}
const ossList = await oss.list(ossProjectName)
ctx.body = ossList
}


//app/utils/request.js
'use strict'
function success(message,data){
return {
code:0,
message,
data
}
}
function failed(message,data){
return {
code:-1,
message,
data
}
}

module.exports = {
success,
failed
}


//app/models/OSS.js
async list({prefix}){
const ossFileList = await this.oss.list({prefix})
if(ossFileList && ossFileList.objects){
return ossFileList.objects
}
return []
}

4-2 覆盖发布逻辑开发

服务端提供了获取OSS文件列表的接口,接着要在客户端去请求OSS上是否有文件,且是否发布。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// models/cloudbuild/index.js
const getProjectOSS = require('./getProjectOSS')
async prepare(){
// 判断是否处于正式发布
if(this.prod){
// 1.获取OSS文件
const name = this.git.name
const type = this.prod ? 'prod':'dev'
const ossProject = await getProjectOSS({name,type})
console.log(ossProject)
//2.判断当前项目OSS文件是否存在
if(Object.is(ossProject.code,0) && ossProject.data.length>0){
// 3.询问客户是否进行覆盖安装
const cover = (await inquirer.prompt({
type:'list',
defaultValue:true,
name:'cover',
message:`OSS已存在[${name}]项目,是否强行覆盖发布?`,
choices:[{
name:'覆盖发布',
value:true
},{
name:'放弃发布',
value:false
}]
})).cover
if(!cover){ //放弃发布
throw new Error('发布终止')
}
}
}
}

// models/cloudbuild/cloudbuild.js
const request = require('@cloudscope-cli/request')

module.exports = function (data) {
return request({
url:'/project/oss',
method:'get',
params:data
})
}

4-3 服务端缓存文件清除功能实现
本节主要是对redis以及缓存文件进行清除,并在build结束后,断开链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/io/middleware/auth.js
//清除缓存文件
const cloudBuildTask = await createCloudBuildTask(ctx,app)
await cloudBuildTask.clean()

// app/models/CloudBuildTask
async clean(){
if (fs.existsSync(this._dir)) {
fse.removeSync(this._dir);
}
const { socket } = this._ctx;
const client = socket.id;
const redisKey = `${REDIS_PREFIX}:${client}`;
await this._app.redis.del(redisKey);
}

// app/io/controller/build.js
socket.emit('build',helper.parseMsg('build success',{
message:`云构建成功,访问链接:https://${cloudBuildTask._prod ? 'cloudscope-cli' : 'cloudscope-cli-dev'}.liugezhou.online/${cloudBuildTask._name}/index.html`
}))
socket.disconnect()

4-4 自动提交代码 BUG 修复

本地未出错

4-5 history模式发布原理讲解

使用 createWebHistory发布代码时,在OSS服务器,改变url地址,再刷新的话,会显示404,在nginx中有try_files的配置,而我们这里没有,因此除了将createWebhistory改为createWebHashHistory模式外,本节课讲述history模式发布解决这个问题—通过本地方式演示。

  • 首先,history模式,需要在nginx上做配置
  • 然后,分两个步骤实现
    • index.html放到nginx服务器指定位置,配置好这个nginx
    • css/js等文件上传到OSS服务器上
  1. vue.config.js中配置OSS路径publicPath

module.exports= {
publicPath:‘https://xxxx.online/vue-router-demo/
}

  1. 本地打包文件dist下目录手动上传至oss服务器 :vue-router-demo目录下。
  2. 将本地dist/index.html下文件复制到Desktop/vue-router/index.html中。
  3. 配置您选配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 8081;
server_name resource;
root /Users/liumingzhou/Desktop/ ;
autoindex on;
location / {
add_header Access-Control-Allow-Origin *;

}
location /vue-router-demo {
try_files $uri $uri/ /vue-router-demo/index.html;
}
add_header Cache-Control "no-cache, must-revalidate";
}
  1. 这个时候启动本地nginx服务器,访问8081端口,再刷新页面,histpry路由模式就不会报404了

4-6 history模式远程发布原理讲解

  • 登录ESC服务器
  • 配置nginx
  • 本地文件上传到服务器
    • scp -r /User/liugezhou/Desktop/vue-router-demo/index.html root@liugezhou:upload/test

4-7 脚手架自动上传模板逻辑开发
4-8 获取 OSS 文件 API 开发
4-9 上传模板功能实现

通过三个新的参数:sshUser 、sshIp、sshPath继续下一步,具体代码就不贴了,这几章就只是流程的问题了。

4-10 自动打tag+合并代码至master分支流程开发

本节课直接看着仓库代码,具体的流程还需要后续深度学习。

第五章 本周加餐:node常用三方库介绍

5-1 Node高分库:PDF文件生成工具——PDFKit

awesome-nodejs

本节sam老师,主要是讲解了这个pdfkit库。

  • 创建一个nodejs项目:npm init -y
  • npm install -S pdfkit
  • 新建kit/index.js,官方仓库代码copy如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const PDFDocument = require('pdfkit');
const fs = require('fs');

// Create a document
const doc = new PDFDocument();

// Pipe its output somewhere, like to a file or HTTP response
// See below for browser usage
doc.pipe(fs.createWriteStream('output.pdf'));

// Embed a font, set the font size, and render some text
doc
.font('fonts/PalatinoBold.ttf')
.fontSize(25)
.text('Some text with an embedded font!', 100, 100);

// Add an image, constrain it to a given size, and center it vertically and horizontally
doc.image('path/to/image.png', {
fit: [250, 300],
align: 'center',
valign: 'center'
});

// Add another page
doc
.addPage()
.fontSize(25)
.text('Here is some vector graphics...', 100, 100);

// Draw a triangle
doc
.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill('#FF3300');

// Apply some transforms and render an SVG path with the 'even-odd' fill rule
doc
.scale(0.6)
.translate(470, -380)
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
.fill('red', 'even-odd')
.restore();

// Add some text with annotations
doc
.addPage()
.fillColor('blue')
.text('Here is a link!', 100, 100)
.underline(100, 100, 160, 27, { color: '#0000FF' })
.link(100, 100, 160, 27, 'http://google.com/');

// Finalize PDF file
doc.end();

5-2 Node Excel处理库讲解
https://github.com/dtjohnson/xlsx-populate

5-3 命令行交互库Listr讲解

  • np:Better npm publish
  • np核心库:listr

5-4 利用Listr对项目自动创建Tag逻辑进行优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Listr = require('listr')
const { Observable } = require('rxjs')

const tasks = new Listr([
{
title:'Task1',
task:()=>new Listr([{
title:'Task1-1',
task:()=>{
return new Observable(o=>{
o.next('Task-1-1')
setTimeout(() => {
o.next('Task-1-1-1')
o.complete()
}, 1000);

})
}
}])
},
{
title:'Task2',
task:()=>{
throw new Error('error')
},
}
])
tasks.run()
process.on('unhandledRejection',(e)=>{
console.log(e.message)
})