jenkins自动化部署微前端项目(二)
续接上文,我们能得到的方案只能在jenkins相同目录下的构建部署,而且部署之后的文件和文件夹都会和项目的应用包分布一样。有鉴于此,后续对上文的方案进行了优化并且给出其他两种可以提供构建和部署不是同一服务器的自动化部署方案。一、相同服务器部署优化 上文结尾的方案最后部署后都会和项目的应用包分布一样,但是目前需要部署好的结构为主包所有文件在部署目录下,各个子包按子包名称部署在所需目录下,所以方案中只需要修改构建的shell命令即可实现,代码如下#!/bin/bash #各个应用包名,用于部署主包的时候过滤掉其他文件夹而不被删除 packages="main,subs/appletuser,subs/college,subs/follow,subs/project,subs/questions,subs/statistics,subs/system" #部署地址 PUBLISH_PATH=/home/jenkinsA project_path=/ OLD_IFS="$IFS" IFS="," #选中需要构建部署包的数组 arr=($mutiParams) IFS="$OLD_IFS" for i in ${arr[@]} do cd $WORKSPACE$project_path$i rm -rf dist if [[ $isRunInstall == "true" ]] then npm install fi npm run build #判断是否为主包,若主包名称不为main则需要修改 if [[ $i == "main" ]] then for element in `ls $PUBLISH_PATH` do #若文件夹名称不存在于设置的packages中则删除 [[ $packages =~ $element ]] || rm -rf $PUBLISH_PATH/$element done #部署主包至部署目录 mv dist/* $PUBLISH_PATH else #分割字符串获取子包名 subdir=${i##*/} rm -rf $PUBLISH_PATH/$subdir mkdir -p $PUBLISH_PATH/$subdir mv dist/* $PUBLISH_PATH/$subdir fi done二、构建服务器和部署服务器不是同一服务器,并通过构建完成后使用scp将对应应用的部署包传输到部署服务器指定位置;本方法因传输过程为自定义shell的scp操作需要用户输入密码,所以需要在部署服务器安装expect插件,用于scp操作执行时自动输入密码;该方法使用构建中执行指定shell命令打包,构建执行完成之后在jenkins构建后操作步骤使用send build artifacts over ssh插件进行连接部署服务器,使用该插件前需要到jenkins的系统管理-系统配置-ssh servers配置部署服务器连接信息,输入服务器名称、IP地址以及登录的用户名,并展开高级提供用于登录的密码或者秘钥;在选择完部署的服务器之后source file、remove prefix、remove directory都置为空,本次方案该插件的用处只为使用jenkins登录上部署服务器,而后在Exec command中的一切命令都是执行在部署服务器上(本方案也可以将Exec command中的命令写在构建步骤的执行shell中,只需要在打包完成后使用scp登录远程服务器,之后在执行部署命令,最后退出部署服务器即可),构建和部署代码如下:构建执行shell:#!/bin/bash source /etc/profile project_path=/ OLD_IFS="$IFS" IFS="," arr=($mutiParams) IFS="$OLD_IFS" for i in ${arr[@]} do cd $WORKSPACE$project_path$i rm -rf dist if [[ $isRunInstall == "true" ]] then npm install fi npm run build done构建后执行部署:#!/bin/bash project_path=/ PUBLISH_PATH=/home/jenkinsB packages="main,subs/appletuser,subs/college,subs/follow,subs/project,subs/questions,subs/statistics,subs/system" IFS="," array=($mutiParams) for i in ${array[@]} do if [[ $i == "main" ]] then for file in $PUBLISH_PATH/* do filename=${file##*/} [[ $packages =~ $filename ]] || rm -rf $file done expect -c " set timeout -1 spawn bash -c \"scp -r root@42.192.51.146:/$WORKSPACE$project_path$i/dist/* $PUBLISH_PATH\" expect { \"*assword\" {send \"Zhong@54Du#12Hua\\\$05\n\";} \"yes/no\" {send \"yes\n\"; exp_continue;} } expect eof" else subdir=${i##*/} rm -rf $PUBLISH_PATH/$subdir mkdir -p $PUBLISH_PATH/$subdir expect -c " set timeout -1 spawn bash -c \"scp -r root@42.192.51.146:/$WORKSPACE$project_path$i/dist/* $PUBLISH_PATH/$subdir\" expect { \"*assword\" {send \"Zhong@54Du#12Hua\\\$05\n\";} \"yes/no\" {send \"yes\n\"; exp_continue;} } expect eof" fi done部分部署代码用途描述:expect 实现自动和交互式任务进行通信,而无需人的干预:expect -c 从当前行开始执行expect操作spawn 进入expect环境后才可以执行的expect内部命令,它主要的功能是给ssh运行进程加个壳,用来传递交互指令bash -c 为了保证后面的命令使用的是bash shell来执行,因expect没有shell的展开文件夹的功能,因此需要使用shell执行expect eof 表示expect指令交互结束三、构建服务器和部署服务器不是同一服务器,本次方法不使用expect插件,构建完成之后将对应的包放到项目的临时文件夹中,之后再通过SSH Publish发送到部署服务器,再在部署服务器操作部署包进行部署工作,本方案在构建后执行步骤需要使用到send build artifacts over ssh插件进行部署包的传输,部署服务器配置和方案二一致,选择完所需的服务器之后,source file填入publish/**(文件夹为构建好所需要部署的包),remove prefix可以不填(该项主要用于在传输到部署服务器后的文件地址路径,主要删除的是source file填入的路径),remove directory填入/home/jenkinsC(该项内容未项目在部署服务器中的地址),对应执行代码如下:构建执行shell:#!/bin/bash packages="main,subs/appletuser,subs/college,subs/follow,subs/project,subs/questions,subs/statistics,subs/system" project_path="" OLD_IFS="$IFS" IFS="," arr=($mutiParams) IFS="$OLD_IFS" rm -rf $WORKSPACE$project_path/publish for i in ${arr[@]} do cd $WORKSPACE$project_path/$i rm -rf dist if [[ $isRunInstall == "true" ]] then npm install fi npm run build [[ $i == "main" ]] && subdir=$i || subdir=${i##*/} mkdir -p $WORKSPACE$project_path/publish/$subdir mv dist/* $WORKSPACE$project_path/publish/$subdir done构建后执行部署:#!/bin/bash packages="main,subs/appletuser,subs/college,subs/follow,subs/project,subs/questions,subs/statistics,subs/system,publish" PUBLISH_PATH=/home/jenkinsC for package in `ls $PUBLISH_PATH/publish` do if [[ $package == "main" ]] then for element in `ls $PUBLISH_PATH` do [[ $packages =~ $element ]] || rm -rf $PUBLISH_PATH/$element done mv $PUBLISH_PATH/publish/$package/* $PUBLISH_PATH else rm -rf $PUBLISH_PATH/$package mkdir -p $PUBLISH_PATH/$package mv $PUBLISH_PATH/publish/$package/* $PUBLISH_PATH/$package fi done rm -rf $PUBLISH_PATH/publish方案二和三所达到的效果是一致的,所以可以按实际需求和场景选择使用
jenkins自动化部署微前端项目(一)
简单配置自动化部署 为方便微前端部署的打包流程,特引入jenkins实现自动化部署,jenkins依据配置自动从用户指定的git远程仓库中拉取代码,并按配置执行打包部署等动作,单页面应用部署过程主要流程为(此处不对jenkins的安装和插件的选用做过多的介绍): 1、jenkins配置项目远程git仓库,实现构建时自动拉取对应仓库分支代码 2、编写构建时所需要的使用到的shell代码,例如安装环境,执行打包等命令(单个shell命令块中的命令按顺序执行,前一指令未完成不会执行后面的指令) 3、可使用构建后操作将shell命令打包之后的文件部署到所需服务器的项目位置(若部署服务器和jenkins在一起也可以使用shell命令移动部署文件进行部署)微前端项目配置自动化部署以上流程为单页面或其他项目的大概部署流程,本文主要记录的是微前端如何按需进行构建打包的流程。一、构建过程中jenkins所使用的的主要插件 1、Git:git插件主要用于获取git远程仓库的代码,同步到服务器中,测试时代码被同步到服务器的地址为 /var/lib/jenkins/workspace 2、Extended Choice Parameter:该插件为参数化构建过程中使用到的插件,目前只使用到该插件的Base Parameter Types的功能,插件大概使用的方式参考下图 3、布尔值参数:主要用于控制项目构建时是否需要执行安装npm命令,提供构建可选择需求 4、GIT:主要用于配置项目代码远程仓库地址和用户认证信息,构建时jenkins使用配置的信息去拉取对应的代码 二、利用构建过程中所需要使用到的插件配置好所需参数之后在构建步骤添加执行shell命令,shell命令主要是利用参数化构建过程中设置好的参数进行项目中主应用和子应用的配置和打包,以及部署到项目部署环境。shell命令中首先获取到用户构建开始所选择的微前端所有主包和子包在项目中的路径字符串,并将其分割成数组,之后循环数组对每个子项目执行打包部署流程,打包过程中判断用户是否勾选了执行npm install指令,若未勾选则跳过该指令,主要是每次构建都安装一遍npm这样的操作既无任何意义,也会拖慢构建时间,具体执行代码参考如下:#!/bin/bash source /etc/profile OLD_IFS="$IFS" IFS="," arr=($mutiParams) IFS="$OLD_IFS" for i in ${arr[@]} do cd $WORKSPACE/$i rm -rf dist if [ $isRunInstall ] then npm install fi npm run build rm -rf /home/jenkins/$i mkdir -p /home/jenkins/$i mv dist/* /home/jenkins/$i done参考代码的移动打包好的文件是需要使用在当前服务器,若需要部署到非jenkins所在的服务器则需要使用其他shell指令进行移动.
服务器部署nodejs、nginx
部署和安装node、nginx 先安装node 1、下载对应的二进制文件 wget -c https://nodejs.org/dist/v8.9.1/node-v8.9.1-linux-x64.tar.xz 2、提取文件 tar -xvf node-v8.9.1-linux-x64.tar.xz 3、解压之后得到的文件就是安装好的nodejs了,为了方便我们可以对它重命名下 mv node-v8.9.1-linux-x64 /app/node 4、node和npm只能在/app/node/bin文件夹下使用,想要node和npm全局有效,可以通过创建软连接 ln -s /app/node/bin/node /usr/local/bin/node ln -s /app/node/bin/npm /usr/local/bin/npm 现在node和npm可以全局使用了,同样输入命令node -v来检查nodejs是否安装成功 安装常用node包 npm i pm2 webpack vue-cli -g 安装nginx 1、yum安装 yum install -y nginx 安装完成使用nginx -v检查是否成功,若出现以下输出则安装成功 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful 2、启动nginx systemctl start nginx.service // 启动nginx systemctl stop nginx.service // 停止nginx systemctl restart nginx.service // 重启nginx systemctl enable nginx.service // 设置开机启动部署线上vue项目 在usr/share目录下用mkdir创建www文件夹,创建app.js启动文件,const fs = require('fs'); const path = require('path'); const express = require('express'); const app = express(); app.use(express.static(path.resolve(__dirname, './dist'))); app.get('*',function(req,res){ const html = fs.readFileSync(path.resolve(__dirname, './dist/index.html'),'utf-8'); res.send(html); }) app.listen(8082) 代码在这就不做解释,这里主要讲解如何部署项目环境 由于我们使用了express框架,所以我们可以生成一个package.json,将依赖项添加进去{ "name":"vue-resume-dist", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.15.3" } } 然后将项目通过软件传到你需要的目录下,如果你是通过xshell上传的zip压缩包可以用unzip xxx.zip命令解压文件,如果是其他类型的压缩文件请自行搜索解压命令 之后通过命令cd到你的项目下进行npm install安装依赖,然后pm2 start app.js就成功启动服务了现在通过ip加端口形式就能正常访问,但是如果想通过域名访问就需要配置nginx映射nginx端口映射配置 首先你需要把一个二级域名解析到你的主机IP,比如我使用的vultr.zwight.net这个二级域名 解析域名(示例为万网域名解析): 1、先将二级域名解析到主机IP 在一级域名的解析列表添加一条新的解析记录,记录类型为A,主机记录为你的二级域名格式为 xxx(自定义的字符).zwight.net(一级域名),解析线路默认,记录值为你的主机IP; 这个二级域名访问网页除了默认的80端口(http)或者443(https)之外访问其他端口都需要二级域名+端口访问,若你需要在访问地址上不想出现端口号则可以通过域名的隐性URL解析到其他地址 2、隐性URL解析: 在解析列表添加一条新的解析记录,记录类型选择 隐性URL,主机记录还是xxx(自定义的字符,需要和二级域名不一致).zwight.net(一级域名),记录值则是二级域名+端口号(比如我的端口号为8080则填入:http://vultr.zwight.net:8080) nginx配置 将下面的配置放入etc/nginx的nginx.conf文件中,其中的端口和server_name改为自己的配置,server_name为你的二级域名upstream resume{ server 127.0.0.1:8082; } server { listen 8080; server_name vultr.zwight.net; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Nginx-proxy true; proxy_pass http://resume; proxy_redirect off; } }执行nginx -s reload重启nginx服务器
浏览器不同标签页间通信
前段时间由于有个项目可能需要两个屏幕的页面能实时通信,考虑到websocket需要服务端支持,存在不稳定性,因此项目中两个屏幕采用同一个主机控制,两个屏幕打开的页面属于同一个项目,只是通过两个标签页打开,所以探讨了不同浏览器标签页或者窗口发送数据的不同方法。 BroadCastChannel API 目前web api中有一个新的api为BroadCastChannel API,该API从Chrome54和其他几个浏览器的最新版本开始提供,其作用是允许来自同源的不同浏览器上下文(即窗口,标签等)之间的简单的通信,以下是使用该API的实例: let channel = new BroadcastChannel('channel-name'); 我们使用BroadCastChannel构造函数创建一个新通道,该构造函数接受频道的名称作为参数。频道名称很重要,因为正在侦听相同频道名称的频道间能够相互广播消息。通道可以从不同的标签页、工作程序和其他浏览器上下文中实例化,但只要他们正在侦听相同的通道名称,他们就能够相互通信。 使用频道广播消息,我们将调用postMessage()接收任何类型的参数进行广播消息: channel.postMessage('some message'); channel.postMessage({key:'value'}); 这将向正在侦听相同频道名称的所有其他频道发送消息,然后频道可以为此事件添加事件处理程序,以获取广播的消息: channel.onmessage = function(e){ const message = e.data; } 我们还可以通过关闭它来使通道停止接收消息: channel.close(); 理想情况下,BroadCastChannel API是实现跨标签页进行通信时的方法,它不仅可以跨标签页广播数据,还可以在其他浏览环境中广播数据,还包括iframe等。但是这个API相对较新,只有一些较新版本的主流浏览器能支持,因此使用时需注意版本的兼容问题。 SharedWorker API Local Storage
angular2+创建宿主视图(动态组件)
创建动态组件前先需要了解一个名词ViewRef,ViewRef表示一个angular视图,在angular中视图(View)是应用程序UI的基本构件。angular鼓励开发人员将UI看作是视图的组成,而不是独立的html标记树。 Angular支持两种视图Embedded Views which are linked to a Template(连接到模板的嵌入式视图) -----连接到模板的嵌入视图,在组件模板元素中添加模板(DOM元素、DOM元素组)Host Views which are linked to a Component(连接到组建的宿主视图) -----连接到组件的嵌入视图,在组件元素中添加别的组件 目前我们只讨论创建宿主视图。Creating host view(创建宿主视图) 当组件被动态实例化时,会创建宿主视图,使用ComponentFactoryResolver 可以动态的创建一个组件。 在angular中每个组件都要被绑定到一个注入器(inject)的特定实例,因此我们在创建组件时传递当前的注入器实例,而且需要将动态实例化的组件添加到模块或托管组件的EnterComponents中。视图创建完毕后我们可以使用ViewContainer将其插入到DOM中。ViewContainerRef 表示一个容器,其中可以附加一个或多个视图。 这里要提到的第一件事是,任何DOM元素都可以用作视图容器。有趣的是,Angular 在元素内部没有插入视图,而是在元素绑定到 ViewContainer 之后附加它们。这类似于 router-outlet 插入组件。 通常,一个好的候选对象可以标记一个 ViewContainer 应该被创建的位置,它是 ng-container 元素。它是作为一个注释呈现的,因此它不会向DOM引入冗余的html元素。下面是一个 ViewContainer 的示例:@Component({ selector: 'sample', template: ` <span>I am first span</span> <ng-container #vc></ng-container> <span>I am last span</span> ` })export class SampleComponent implements AfterViewInit { @ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef; ngAfterViewInit(): void { // outputs `template bindings={}` console.log(this.vc.element.nativeElement.textContent); } } 正如其他DOM抽象一样, ViewContainer 被绑定到通过 element 属性访问的特定DOM元素。在这个例子中,它绑定到 ng-container 元素作为注释,因此输出是 template bindings={} 。Manipulating views (操作视图) ViewContainer为操作视图提供了一个方便的apiclass ViewContainerRef { ...clear() : void insert(viewRef: ViewRef, index?: number) : ViewRef get(index: number) : ViewRef indexOf(viewRef: ViewRef) : number detach(index?: number) : ViewRef move(viewRef: ViewRef, currentIndex: number) : ViewRef } 我们前面已经看到了如何从模板和组件手动创建两种视图。一旦我们有了视图,我们就可以使用insert方法将它 insert 到DOM中。 ViewContainer还提供了自动创建视图的APIclass ViewContainerRef { element: ElementRef length: number createComponent(componentFactory...): ComponentRef<C> createEmbeddedView(templateRef...): EmbeddedViewRef<C> ... }下面是自动创建视图的例子import { Component, ViewChild, ComponentFactoryResolver, Input, ComponentRef, OnInit, AfterViewInit } from "@angular/core"; @Component({ selector: 'modal-host', templateUrl: './modal-host.component.html', styleUrls: ['./modal-host.component.scss'] }) export class ModalHostComponent{ isShow: boolean = false; @ViewChild('container') modalContentHost: ViewContainerRef; constructor( private componentFactoryResolver: ComponentFactoryResolver, ) { } private _contentComponent: any; @Input('content-data') contentData: any; @Input('content-component') set contentComponent(value: any) { this._contentComponent = value; if (!!this._contentComponent) { this.loadComponent(); } else { this.clearComponent(); } } get contentComponent() { return this._contentComponent; } private componentRef: ComponentRef<any>; loadComponent() { setTimeout(()=>{ this.isShow = true; },10) let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this._contentComponent); this.modalContentHost.viewContainerRef.clear(); this.componentRef = this.modalContentHost.viewContainerRef.createComponent(componentFactory); if (!!this.contentData) { this.componentRef.instance.data = this.contentData; } } clearComponent() { if (!!this.modalContentHost) { this.modalContentHost.viewContainerRef.clear(); } this.isShow = false; } close() { } stopPropagation($event) { $event.stopPropagation(); } }ngComponentOutlet该指令类似于 ngTemplateOutlet,其不同之处在于它创建了一个宿主视图(实例化一个组件),而不是一个嵌入式视图,该指令在angular4+中开始出现。你可以这样使用:<ng-container *ngComponentOutlet="ColorComponent"></ng-container>
angular2以上视频播放结束方法
播放视频后需要触发某个事件,js的方法为监听ended事件:html:<video id="media" controls width="400px" heigt="400px"> <source src="src" > </video>js: var md=document.getElementsByTagName("video")[0]; md.addEventListener("ended",function(){ alert("播放结束"); })在angular中它提供了一个方法在视频播放结束后触发某个事件angular1中事件为on-ended,angular2以上为(ended);代码:template:<video (ended)="videoEnd()"> <source src="assets/vid.mp4" type="video/mp4"> </video>component:videoEnd() {}