第一章 本周导学


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. // models/git/lib/index.js
  2. async preparePublish(){
  3. log.info('开始进行云构建前代码检查')
  4. …………
  5. const pkg = this.getPackageJson()
  6. const buildCmdArray = this.buildCmd.split('')
  7. const buildLastCmd = buildCmArrat.slice(-1).toString()
  8. if(!pkg.scripts || !Object.keys(pkg.scripts).includes(lastCmd)){
  9. throw new Error(`${this.buildCmd}命令不存在`)
  10. }
  11. log.success('云构建代码预检查通过')
  12. }
  13. getPackageJson(){
  14. const pkgPath = path.resolve(this.dir,'package.json')
  15. if(!fs.existSync(pkgPath)){
  16. throw new Error(`package.json不存在,源码目录:${this.dir}`)
  17. }
  18. return fse.readJsonSync(pkgPath)
  19. }

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

  • 上一节我们检查了build这个命令
  • 接着,我们需要选择上传资源服务器的类型,也就是OSS
    • 这里的代码是为了后续如果要修改资源服务器类型,可以进行代码的再开发—添加其他资源服务器类型。
    • 与检查git_token或者git_server等一样,本节需要去新建或者检查.git_publish文件,代码实现如下
  • 最后,cloudscope-cli publish这个命令添加了 —prod这个参数,以及.git_publish文件内容,传入到 Cloudbuild类中去
  1. // models/git/lib/index.js
  2. const GIT_PUBLISH_FILE='.git_publish'
  3. const GIT_PUBLISH_TYPE=[{
  4. name:'OSS',
  5. value:'oss'
  6. }]
  7. async preparePublish(){
  8. log.info('开始进行云构建前代码检查')
  9. ………………
  10. log.success('云构建代码预检查通过')
  11. const gitPublishPath = this.createPath(GIT_PUBLISH_FILE)
  12. let gitPublish = readFile(gitPublishPath)
  13. if(!gitPublish){ // 如果没有读取到.git-publish文件中的内容
  14. gitPublish = await this.choicePublish(gitPublishPath)
  15. log.success('git publish 写入成功',`${gitPublish} -> ${gitPublishPath}`)
  16. }else{ // 如果读取到了 内容
  17. log.success('git publish 文件读取成功!')
  18. }
  19. this.gitPublish = gitPublish
  20. }
  21. async choicePublish(gitPublishPath){
  22. const gitPublish = (await inquirer.prompt({
  23. type:'list',
  24. name:'gitPublish',
  25. message:'请选择你想要上传代码的平台',
  26. choices:GIT_PUBLISH_TYPE
  27. })).gitPublish;
  28. writeFile(gitPublishPath,gitPublish)
  29. return gitPublish
  30. }

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

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

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

  1. // app/io/controller/build.js
  2. await prePublish(cloudBuildTask,socket,helper)
  3. async function prePublish(cloudBuildTask,socket,helper){
  4. socket.emit('build',helper.parseMsg('pre-publish',{
  5. message:'开始发布前检查'
  6. }))
  7. const prePublishRes = await cloudBuildTask.prePublish()
  8. if(!prePublishRes || Object.is(prePublishRes.code,FAILED)){
  9. socket.emit('build',helper.parseMsg('pre-publish failed',{
  10. message:'发布前检查失败,失败原因:' + (prePublishRes && prePublishRes.message ? prePublishRes.message:'未知')
  11. }))
  12. throw new Error('发布终止')
  13. }else{
  14. socket.emit('build',helper.parseMsg('pre-publish',{
  15. message:'发布前检查通过'
  16. }))
  17. }
  18. }
  19. //app/models/cloudBuildTask.js
  20. async prePublish(){
  21. //获取构建结果
  22. const buildPath = this.findBuildPath()
  23. //检查构建结果
  24. if(!buildPath){
  25. return this.failed('未找到构建结果,请检查')
  26. }
  27. this._buildPath = buildPath
  28. return this.success()
  29. }
  30. findBuildPath(){
  31. const buildDir =['build','dist']
  32. const buildPath = buildDir.find(dir=>fs.existsSync(path.resolve(this._sourceCodeDir,dir)))
  33. this._ctx.logger.info('buildPath',buildPath)
  34. if(buildPath){
  35. return path.resolve(this._sourceCodeDir,buildPath)
  36. }
  37. return null
  38. }

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

  • cnpm install -S ali-oss
  • 客户端传递prod参数到服务端,服务端根据prod参数,获取不同环境的OSS
  1. // app/models/CloudBuildTask.js
  2. const OSS = require('./OSS')
  3. const config = require('../../config/db')
  4. class CloudBuildTask(options,ctx){
  5. constructor(){
  6. this._prod = options.prod === 'true' ? true : false
  7. }
  8. }
  9. async prepare(){
  10. fse.ensureDirSync(this._dir)
  11. fse.emptyDirSync(this._dir)
  12. this._git = new Git(this._dir)
  13. if(this._prod){ //生产准备OSS
  14. this.oss =new OSS(config.OSS_PROD_BUCKET)
  15. }else{//测试
  16. this.oss =new OSS(config.OSS_DEV_BUCKET)
  17. }
  18. console.log(this.oss)
  19. return this.success()
  20. }
  21. // app/models/OSS.js
  22. 'use strict';
  23. const OSS = require('ali-oss');
  24. const config = require('../../config/db')
  25. class OSS{
  26. constructor(bucket){
  27. this.oss = new OSS({
  28. region:config.OSS_REGION,
  29. accessKeyId: config.OSS_ACCESS_KEY,
  30. accessKeySecret: config.OSS_ACCESS_SECRET_KEY,
  31. bucket,
  32. })
  33. }
  34. }
  35. module.exports = OSS
  36. //app/config/db.js
  37. const fs = require('fs')
  38. const path = require('path')
  39. const userHome = require('user-home')
  40. const OSS_ACCESS_KEY = ''
  41. const OSS_ACCESS_SECRET_KEY = fs.readFileSync(path.resolve(userHome,'.cloudscope-cli','oss_access_secret_key')).toString()
  42. const OSS_PROD_BUCKET=''
  43. const OSS_DEV_BUCKET=''
  44. const OSS_REGION=''

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

3-5 云发布核心流程:上传OSS功能开发

代码逻辑如下

  • npm i -S glob
  1. // app/io/controller/build.js
  2. await publish(cloudBuildTask,socket,helper)
  3. async function publish(cloudBuildTask,socket,helper){
  4. socket.emit('build',helper.parseMsg('publish',{
  5. message:'开始发布'
  6. }))
  7. const publishRes = await cloudBuildTask.publish()
  8. if(!publishRes || Object.is(publishRes.code,FAILED)){ // downlod下载失败
  9. socket.emit('build',helper.parseMsg('publish failed',{
  10. message:'发布失败'
  11. }))
  12. return
  13. }else{
  14. socket.emit('build',helper.parseMsg('publish',{
  15. message:'发布成功'
  16. }))
  17. }
  18. }
  19. // app/models/CloudBuildTask.js
  20. async publish(){
  21. return new Promise((resolve,reject) =>{
  22. glob('**',{
  23. cwd:this._buildPath,
  24. nodir:true,
  25. ignore:'**/node_modules/**'
  26. },(err,files) =>{
  27. if(err){
  28. resolve(false)
  29. }else{
  30. Promise.all(files.map(async file=>{
  31. console.log(file)
  32. const filePath = path.resolve(this._buildPath,file)
  33. const uploadOSSRes = await this.oss.put(`${this._name}/${file}`,filePath)
  34. return uploadOSSRes
  35. })).then(()=>{
  36. resolve(true)
  37. }).catch(err=>{
  38. this._ctx.logger.error(err)
  39. resolve(false)
  40. })
  41. }
  42. })
  43. })
  44. }
  45. // app/models/OSS.js
  46. async put(object, localPath, options = {}) {
  47. await this.oss.put(object, localPath, options);
  48. }

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

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

  • 域名绑定
  • CDN绑定

第四章 云发布流程完善


4-1 获取OSS API开发

服务端

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

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

  1. // app/controller/project.js
  2. const { failed } = require("../utils/request")
  3. const OSS = require('../models/OSS')
  4. const config = require('../../config/db')
  5. async getOSSProject(){
  6. const { ctx } = this
  7. const ossProjectType = ctx.query.type
  8. const ossProjectName = ctx.query.name
  9. if(!ossProjectName){
  10. ctx.body = failed('项目名称不存在)
  11. return
  12. }
  13. if(!ossProjectType){
  14. ossProjectType = 'prod'
  15. }
  16. let oss;
  17. if(Object.is(ossProjectType,'prod')){
  18. oss = new OSS(config.OSS_DEV_BUCKET)
  19. }else{
  20. oss = new OSS(config.OSS_PROD_BUCKET)
  21. }
  22. const ossList = await oss.list(ossProjectName)
  23. ctx.body = ossList
  24. }
  25. //app/utils/request.js
  26. 'use strict'
  27. function success(message,data){
  28. return {
  29. code:0,
  30. message,
  31. data
  32. }
  33. }
  34. function failed(message,data){
  35. return {
  36. code:-1,
  37. message,
  38. data
  39. }
  40. }
  41. module.exports = {
  42. success,
  43. failed
  44. }
  45. //app/models/OSS.js
  46. async list({prefix}){
  47. const ossFileList = await this.oss.list({prefix})
  48. if(ossFileList && ossFileList.objects){
  49. return ossFileList.objects
  50. }
  51. return []
  52. }

4-2 覆盖发布逻辑开发

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

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

4-3 服务端缓存文件清除功能实现

本节主要是对redis以及缓存文件进行清除,并在build结束后,断开链接

  1. // app/io/middleware/auth.js
  2. //清除缓存文件
  3. const cloudBuildTask = await createCloudBuildTask(ctx,app)
  4. await cloudBuildTask.clean()
  5. // app/models/CloudBuildTask
  6. async clean(){
  7. if (fs.existsSync(this._dir)) {
  8. fse.removeSync(this._dir);
  9. }
  10. const { socket } = this._ctx;
  11. const client = socket.id;
  12. const redisKey = `${REDIS_PREFIX}:${client}`;
  13. await this._app.redis.del(redisKey);
  14. }
  15. // app/io/controller/build.js
  16. socket.emit('build',helper.parseMsg('build success',{
  17. message:`云构建成功,访问链接:https://${cloudBuildTask._prod ? 'cloudscope-cli' : 'cloudscope-cli-dev'}.liugezhou.online/${cloudBuildTask._name}/index.html`
  18. }))
  19. 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/‘ }

  2. 本地打包文件dist下目录手动上传至oss服务器 :vue-router-demo目录下。

  3. 将本地dist/index.html下文件复制到Desktop/vue-router/index.html中。
  4. 配置您选配置文件

    1. server {
    2. listen 8081;
    3. server_name resource;
    4. root /Users/liumingzhou/Desktop/ ;
    5. autoindex on;
    6. location / {
    7. add_header Access-Control-Allow-Origin *;
    8. }
    9. location /vue-router-demo {
    10. try_files $uri $uri/ /vue-router-demo/index.html;
    11. }
    12. add_header Cache-Control "no-cache, must-revalidate";
    13. }
  5. 这个时候启动本地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如下: ```javascript 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();

  1. <a name="vXmEg"></a>
  2. #### 5-2 Node Excel处理库讲解
  3. [https://github.com/dtjohnson/xlsx-populate](https://github.com/dtjohnson/xlsx-populate)
  4. <a name="G6Tqw"></a>
  5. #### 5-3 命令行交互库Listr讲解
  6. - [np](https://github.com/sindresorhus/np):Better npm publish
  7. - np核心库:[listr](https://www.npmjs.com/package/listr)
  8. <a name="v9tmg"></a>
  9. #### 5-4 利用Listr对项目自动创建Tag逻辑进行优化
  10. ```javascript
  11. const Listr = require('listr')
  12. const { Observable } = require('rxjs')
  13. const tasks = new Listr([
  14. {
  15. title:'Task1',
  16. task:()=>new Listr([{
  17. title:'Task1-1',
  18. task:()=>{
  19. return new Observable(o=>{
  20. o.next('Task-1-1')
  21. setTimeout(() => {
  22. o.next('Task-1-1-1')
  23. o.complete()
  24. }, 1000);
  25. })
  26. }
  27. }])
  28. },
  29. {
  30. title:'Task2',
  31. task:()=>{
  32. throw new Error('error')
  33. },
  34. }
  35. ])
  36. tasks.run()
  37. process.on('unhandledRejection',(e)=>{
  38. console.log(e.message)
  39. })