简

人生短暂,学海无边,而大道至简。


  • 首页

  • 归档

  • 分类

  • 标签

ECMAScript 6 Promise

发表于 2019-04-15 | 分类于 Node

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择。

一、用法
1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

1
2
3
4
5
promise.then(function(value) {
// success
}, function(error) {
// failure
});

简单例子。

1
2
3
4
5
6
7
8
9
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}

timeout(100).then((value) => {
console.log(value);
});

上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
// Promise 新建后立即执行,所以首先输出的是Promise。
// 然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
reject();
});

promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');

用Promise对象实现的 Ajax 操作的例子。

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
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();

});

return promise;
};

getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
Promise.prototype.then()

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

1
2
3
4
5
6
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

1
2
3
4
5
6
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

1
2
3
4
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
promise
.finally(() => {
// 语句
});

// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
const databasePromise = connectDatabase();

const booksPromise = databasePromise
.then(findAllBooks);

const userPromise = databasePromise
.then(getCurrentUser);

Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));

上面代码中,booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations这个回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。

待续。。。

ECMAScript 6 基础知识

发表于 2019-04-14 | 分类于 Node

一、let和const命令

1. let命令
基本用法

ES6新增了let命令,用来声明变量。用法类似于var,但是也存在新特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1)let所声明的变量,只在let命令所在的代码块有效。适用于for循环。

{let a = 10;var b = 1;}
//a a is not defined
//b 1

2)不存在变量提升。
console.log{bar};
let bar = 2;
//报错ReferenceError:bar is not defined

3)暂时性死区。
在代码块内,使用let命令变量之前,该变量都是不可用的。
var tmp = 123;
if(true){
tmp = 'abc'; //ReferenceError
let tmp;
}

4)不允许重复声明。
function(){let a =10;let a = 1;} //报错
块级作用域

let实际上为JavaScript新增了块级作用域。外层作用域无法读取内层作用域的变量,内层作用域可以定义外层作用域的同名变量。

1
2
3
4
5
6
7
let foo = 1;
{
let foo = 2; //定义同名变量
let bar = "one";
}
console.log{foo}; //1
console.log(bar); //报错

块级作用域的出现,实际上使得广泛应用的立即执行函数表达式(IIFE)不再必要了。

2. const命令
基本用法

const声明一个只读的变量。一旦声明,常量的值就不能改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1)const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo; //SyntaxErroe:Missing initializer in const declaration
2)块级作用域
只在声明所在的块级作用域内有效。
3)暂时性死区
在代码块内,使用let命令声明变量之前,该变量是不可用。

if(true){
console.log(MAX); //ReferenceError const MAX = 5;
}
4)不允许重复声明
var message = 'Hello!';
let age = 25;
//以下两行都会报错
const message = 'Goodbye!';
const age = 30;

用途:为了防止意外修改变量,比如,引入库名,组件名等

let、const与var的区别:

1
2
3
a. let、const有块级作用域的概念,只要是定义的变量,就是一个块级作用域;var只有在函数中才有作用域;
b. let、const不允许重复声明;
c. let和var声明的变量,都可以再修改,而const定义的是常量,值是不可以修改的(对象除外,对象的属性可以修改)

作用域的概念:ES5有两个作用域:全局和局部

ES6有了块作用域的概念:块作用域,通俗的理解,就是一个大括号,就是一个块作用域;

ES6 声明变量的六种方法

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

1
2
3
4
5
window.a = 1;
a // 1

a = 2;
window.a // 2

上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。

顶层对象的属性与全局变量挂钩,是JavaScript 语言最大的设计败笔之一。ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

1
2
3
4
5
6
7
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。

globalThis 对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

1
2
3
浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
  • 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);

// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};

现在有一个提案,在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。

二、解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructing)。

1
2
3
4
5
6
7
8
以前,为变量赋值,只能直接指定值。

let a = 1;
let b = 2;
let c = 3;
ES6 允许写成下面这样。

let [a, b, c] = [1, 2, 3];

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果结构不成功,变量的值就等于undefined。另一种情况是不完全解构,即等号左边的模式只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

数组的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let [a,b,c] = [1,2,3];

1)不完全解构:

let [a,[b],d] = [1,[2,3],4]; //a=1;b=2;d=4

2)集合解构:

let[head,...tail] = [1,2,3,4]; //head=1;tail=[2,3,4]

3)默认值(当匹配值严格等于undefined时,默认值生效):

let [x,y='b'] = ['a']; //x = 'a',y = 'b'

4)默认值也可以为函数:

function f(){ console.log('aaa'); }
let [x=f()] = [1];
对象的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1)对象的属性没有次序,变量必须与属性同名,才能取到正确的值

let {foo,bar} = {foo:'aaa',bar:'bbb'}; //foo='aaa';bar='bbb'

2)如果变量名与属性名不一致,必须写成下面这样。

var {foo:baz} = {foo:'aaa',bar:'bbb'}; // baz='aaa'

3)这实际上说明,对象的解构赋值是下面形式的简写。

let {foo:foo,bar:bar} = {foo:'aaa',bar:'bbb'};

4)嵌套结构

let obj = {p:['Hello',{y:'World'}]};
let {p:[x,{y}]} = obj; // x='Hello';y='World'

5)默认值(默认值生效的条件是,对象的属性值严格等于undefined)
var {x:y=3} = {}; // y=3

对象的解构也可以指定默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
字符串的解构赋值
1
2
3
4
5
6
7
1)解构时,字符串被转换成了一个类似数组的对象。

const [a,b,c,d,e] = 'hello'; //a=h;b=e;c=l;d=l;e=o

2)也可以对数组的属性解构

let {length:len} = 'hello'; // len=5
数值和布尔值的解构赋值
1
2
3
4
解构时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString:s} = 123; // s===Number.prototype.toString true
let {toString:s} = true; // s===Boolean.prototype.toString true
函数参数的解构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1)基本语法

function add([x,y]){return x+y;}
add([1,2]);

2)默认值

function move({x=0,y=0}){
return [x,y];
}
move({x:3,y:8}); //[3,8]
move({x:3}); //[3,0]
move({}); //[0,0]
move(); //[0,0]
常见用途
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
1)交换变量的值

let x = 1;
let y = 2;
[x,y] = [y,x];

2)从函数返回多个值

function example(){
return [1,2,3];
}
let [a,b,c] = example();

3)函数参数的定义

function f([x,y,z]){...}
f([1,2,3]);

4)提取JSON数据

let jsonData = {id:42,status:'OK',data:[867,5309]};
let {id,status,data:number} = jsonData;

5)输入模块的指定方法

const {SourceMapConsumer,SourceNode} = require("source-map");

6)函数参数的默认值

jQuery.ajax = function(url,{
async = true,cache = true,global = true,
beforeSend = function(){},
complete = function(){},
//...more config
}){//...do stuff};

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo||'default foo';这样的语句。

7)遍历map结构

var map = new Map();
map.set('first','hello');
map.set('second','world');
for(let [key,value] of map){
console.log(key + "is" + value);
}

三、字符串、正则、对象、函数、数组等扩展

字符串的扩展

字符串的遍历器接口

1
2
3
4
5
6
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"

Jenkins介绍和安装使用

发表于 2019-04-13 | 分类于 Jenkins

一、什么是持续集成?

(1)Continuous integration(CI)

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。

(2)没有持续集成

项目做模块集成的时候,发现很多借口都不通==>浪费大量时间
需要手动去编译打包最新的代码==>构建过程不透明
发布代码,上线,基本靠手工==>脚本乱飞

(3)持续集成最佳实践:

维护一个单一的代码库
使构建自动化
执行测试是构建的一部分
集成日志及历史记录
使用统一的依赖包管理库
每天至少集成一次

(4)jenkins介绍

Jenkins只是一个平台,真正运作的都是插件。这就是jenkins流行的原因,因为jenkins什么插件都有
Hudson是Jenkins的前身,是基于Java开发的一种持续集成工具,用于监控程序重复的工作,Hudson后来被收购,成为商业版。后来创始人又写了一个jenkins,jenkins在功能上远远超过hudson
Jenkins官网:https://jenkins.io/
Jenkins下载:http://updates.jenkins-ci.org/
jenkins的全部镜像:http://mirrors.jenkins-ci.org/status.html

(5)部署代码上线流程:

1.代码获取(直接了拉取)
2.编译      (可选)
3.配置文件放进去
4.打包
5.scp到目标服务器
6.将目标服务器移除集群
7.解压并放置到Webroot
8.Scp 差异文件
9.重启      (可选)
10.测试
11.加入集群

二、安装使用

yum的repo中默认没有Jenkins,需要先将Jenkins存储库添加到yum repos,执行下面的命令:
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo

然后执行下面的命令:

rpm –import https://pkg.jenkins.io/redhat-stable/jenkins.io.key

然后 安装Jenkins

yum install -y jenkins 默认安装最新的

或者
直接下载到本地:wget https://pkg.jenkins.io/redhat/jenkins-2.160-1.1.noarch.rpm
然后通过命令安装成功:rpm -ih jenkins-2.160-1.1.noarch.rpm

jenkins安装成功后,默认的用户是jenkins,端口是8080,为了防止冲突,并且给用户赋权限,我们修改用户名和端口。

输入命令,进入jenkins配置文件: vi /etc/sysconfig/jenkins

修改用户名root,端口,然后保存,退出

1
2
3
4
5
6
7
8
9
10
11
12
安装jenkins有两种方式,tomcat方式部署和java部署启动。

tomcat方式部署:
1、首先安装tomcat和JAVA;
2、官网下载下来的jenkins.war文件放入tomcat下的webapps目录下,进入tomcat的/bin目录下,启动tomcat即启动jenkins。
3、启动jenkins时,会自动在webapps目录下建立jenkins目录,访问地址为:http://localhost:8080/jenkins
4、如果启动时报错:Caused by:java.awt.AWTError: Can't connect to X11 window server using ':0' as the value of the DISPLAY varible...
解决:[jenkins@osb30 bin]$ vim apache-tomcat-8.0.30/bin/catalina.sh,修改为:JAVA_OPTS="-Xms1024m -Xmx1024m -Djava.awt.headless=true"

java部署启动jenkins:
1、切换到jenkins.war存放的目录,输入如下命令:java -jar jenkins.war,可以修改启动端口java -jar jenkins.war --httpPort=8000
2、然后在浏览器http://localhost:8080即可访问

启动jenkins

1
2
3
4
5
6
7
8
9
10
service jenkins start
启动报Starting Jenkins bash: /usr/bin/java: No such file or directory
修改Jenkins启动配置文件,指定java安装路径。
vim /etc/init.d/jenkins
在candidates中第一行添加java路径,如下:
candidates="
/usr/local/jdk8/bin/java
启动成功,浏览器访问http://192.168.1.110:8088

初始密码在:/var/lib/jenkins/secrets/initialAdminPassword

登录成功后显示

20200413162014

选择”Install suggested plugins”安装默认的插件,下面Jenkins就会自己去下载相关的插件进行安装。安装完成后配置账号,配置好账号之后hu/123456,进入Jenkins。

接下来在jenkins中配置jdk、svn、maven、git,并且构建web项目和spring boot项目。

三、安装Maven

1
2
3
4
5
6
7
8
9
10
11
12
官网下载安装包apache-maven-3.6.0-bin.tar.gz到Linux上
解压[root@localhost local]# tar -zxvf apache-maven-3.6.0-bin.tar.gz
// 配置环境变量到/etc/profile
export PATH=$PATH:cbin
//查看版本
[root@localhost local]# mvn --version
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T02:41:47+08:00)
Maven home: /usr/local/apache-maven-3.6.0
Java version: 1.8.0_191, vendor: Oracle Corporation, runtime: /usr/local/jdk8/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "2.6.32-431.el6.x86_64", arch: "amd64", family: "unix"
修改一下源:

四、安装git

安装过程 略

有的插件安装失败,通过换源来解决或者手动下载:jenkins->系统管理->管理插件->高级,如
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.jsonss

五、使用Jenkins

使用构建

配置DK、MAVEN、Git为实际对应的安装目录

20200413161952

安装以下插件:

1、 Maven Integration plugin
2、 Subversion Plugin
3、 Checkstyle Plugin
4、 Findbugs Plugin
5、 PMD Plugin
6、 Warnings Plugin
7、 Maven Info Plugin
8、 Maven Repository Scheduled Cleanup Plugin
9、 Unleash Maven Plugin
10、 Maven Invoker plugin

Subversion Plug-in ## 版本管理 SVN 的插件
Git plugin ## 版本管理 GIT 的插件
Maven Integration plugin ## 项目构建 Maven 的插件
Gradle Plugin ## 项目构建 Gradle 的插件

无需重启 Jenkins 插件即生效。如遇失败可重试或离线安装。

一般情况下,常使用到如下这些插件:

FindBugs Plug-in: 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对        比以发现可能的问题。
Checkstyle Plug-in:是一个静态分析工具,检查Java程序代码。
Deploy to container Plugin:用于构建项目后,自动发布war包重新部署的插件
SSH Plugin:这个插件使用 SSH 协议执行远程 shell 命令。
Multijob Plugin:这个插件是一个将多个项目连接在一起的插件。

1、构建Maven项目

20200413162214

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
由用户 hu 启动
Running as SYSTEM
构建中 在工作空间 /var/lib/jenkins/workspace/maven-jenkins-test 中
using credential dd5c879e-a6f2-4902-95fc-81219b15096f
Cloning the remote Git repository
Cloning repository https://github.com/huingsn/demo
> git init /var/lib/jenkins/workspace/maven-jenkins-test # timeout=10
Fetching upstream changes from https://github.com/huingsn/demo
> git --version # timeout=10
using GIT_ASKPASS to set credentials
> git fetch --tags --progress https://github.com/huingsn/demo +refs/heads/*:refs/remotes/origin/*
> git config remote.origin.url https://github.com/huingsn/demo # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url https://github.com/huingsn/demo # timeout=10
Fetching upstream changes from https://github.com/huingsn/demo
using GIT_ASKPASS to set credentials
> git fetch --tags --progress https://github.com/huingsn/demo +refs/heads/*:refs/remotes/origin/*
> git rev-parse refs/remotes/origin/master^{commit} # timeout=10
> git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
> git rev-parse origin/master^{commit} # timeout=10
ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.
Finished: FAILURE

原因分析:jenkins无法准确识别revision

操作方法:

20200413162306

再次构建,成功!

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
由用户 hu 启动
Running as SYSTEM
构建中 在工作空间 /var/lib/jenkins/workspace/dsafsdfdsafas 中
using credential dd5c879e-a6f2-4902-95fc-81219b15096f
Cloning the remote Git repository
Cloning repository https://github.com/huingsn/demo
> git init /var/lib/jenkins/workspace/dsafsdfdsafas # timeout=10
Fetching upstream changes from https://github.com/huingsn/demo
> git --version # timeout=10
using GIT_ASKPASS to set credentials
> git fetch --tags --progress https://github.com/huingsn/demo +refs/heads/*:refs/remotes/origin/*
> git config remote.origin.url https://github.com/huingsn/demo # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url https://github.com/huingsn/demo # timeout=10
Fetching upstream changes from https://github.com/huingsn/demo
using GIT_ASKPASS to set credentials
> git fetch --tags --progress https://github.com/huingsn/demo +refs/heads/*:refs/remotes/origin/*
Seen branch in repository origin/github.com/huingsn/demo
Seen 1 remote branch
> git show-ref --tags -d # timeout=10
Checking out Revision c8688fb590873bd7b77f3537d11640a5139c0da4 (origin/github.com/huingsn/demo)
> git config core.sparsecheckout # timeout=10
> git checkout -f c8688fb590873bd7b77f3537d11640a5139c0da4
Commit message: "dfsdfsdfs"
First time build. Skipping changelog.
Parsing POMs
Discovered a new module com.hu:maven-jenkins-test maven-jenkins-test Maven Webapp
Modules changed, recalculating dependency graph
Established TCP socket on 37646
[dsafsdfdsafas] $ /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.201.b09-2.el7_6.x86_64/bin/java -cp /var/lib/jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-agent-1.12.jar:/usr/share/maven/boot/plexus-classworlds.jar org.jvnet.hudson.maven3.agent.Maven3Main /usr/share/maven /var/cache/jenkins/war/WEB-INF/lib/remoting-3.29.jar /var/lib/jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-1.12.jar /var/lib/jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-commons-1.12.jar 37646
<===[JENKINS REMOTING CAPACITY]===>channel started
Executing Maven: -B -f /var/lib/jenkins/workspace/dsafsdfdsafas/pom.xml install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-jenkins-test Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/3.0.2/maven-resources-plugin-3.0.2.pom
//省略下载包过程
Downloaded: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-utils/3.0.15/plexus-utils-3.0.15.jar (234 KB at 17.8 KB/sec)
[INFO] Installing /var/lib/jenkins/workspace/dsafsdfdsafas/target/maven-jenkins-test.war to /root/.m2/repository/com/hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.war
[INFO] Installing /var/lib/jenkins/workspace/dsafsdfdsafas/pom.xml to /root/.m2/repository/com/hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.pom

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6:17.736s
[INFO] Finished at: Sat Apr 06 21:22:19 CST 2019
[INFO] Final Memory: 14M/79M
[INFO] ------------------------------------------------------------------------
Waiting for Jenkins to finish collecting data
[JENKINS] Archiving /var/lib/jenkins/workspace/dsafsdfdsafas/pom.xml to com.hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.pom
[JENKINS] Archiving /var/lib/jenkins/workspace/dsafsdfdsafas/target/maven-jenkins-test.war to com.hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.war
channel stopped

Finished: SUCCESS

构建触发器

指定的项目完成构建后,触发此项目的构建。

Poll SCM

1.首先格式为:* * * * *(五个星);

2.第一个*表示分钟,取值0~59

第二个*表示小时,取值0~23
第三个*表示一个月的第几天,取值1~31
第四个*表示第几月,取值1~12
第五个*表示一周中的第几天,取值0~7,其中0和7代表的都是周日

3.使用举例:

每隔10分钟构建一次:H/5 * * * *
每隔1小时构建一次: H/1* *
每月30号构建一次:   H 30 * *
Build periodically:此选项仅仅通知Jenkins按指定的频率对项目进行构建,而不管SCM是否有变化。如果想在这个Job中运行一些测试用例的话,它就很有帮助。

20200413162444

构建

  这部分主要是配置构建的相关内容,用于定时触发构建或者手动执行构建的时候,对代码检验、编译时进行的操作。构建概念到处可查到,形象来说,构建就是要把代码从某个地方拷贝过来,编译,再拷贝到某个地方去等等操作,当然不仅与此,但是主要用来干这个。

因为我的项目是用ant脚本实现的编译和打包,所以我选择的是Invoke Ant,Ant Version选择我Ant配置的那个名字(这里可以参见3.4.2),注意不要选择default喔,那个选择了没有用。

构建后操作

  用于定义当前项目构建完之后的一些操作,比如构建完之后将checkstyle结果输出到指定日志文件,重新发布项目,去执行其他项目构建等。

20200413162513

构建后发布项目

注意,首先必须安装好Deploy Plugin插件,然后在tomcat的conf目录配置tomcat-users.xml文件,如我这里配置的是manager, 在节点里添加如下内容:

20200413162537

WAR/EAR files:是war包的相对路径(相对于工作区路径,即在工作区中war包的相对路径.)如我的maven执行完成之后会在工作区的target目录下生成项目.war,所以这里我的路径就写target\项目.war.
Context path:访问时需要输入的内容http://192.168.1.112:8000/maven-jenkins-test,如果为空,默认是war包的名字。
Container:选择你的web容器,如tomca 7.x
Manager user name:填入tomcat-users.xml配置的username内容
Manager password:填入tomcat-users.xml配置的password内容

Tomcat URL:http://192.168.1.112:8000

Deploy on failure:构建失败依然部署,一般不选择

  注意:虽然这种部署方法可能会导致tomcat加载时出现卡死的现象。但是也是最简单的部署方式。如果卡死了重启下就好了,将tomcat的java内存参数调高可以解决这个问题。

上面写入错入,Tomcat端口是8080,开始部署:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
Running as SYSTEM
[EnvInject] - Loading node environment variables.
构建中 在工作空间 /var/lib/jenkins/workspace/first 中
using credential dd5c879e-a6f2-4902-95fc-81219b15096f
> git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url https://github.com/huingsn/demo # timeout=10
Fetching upstream changes from https://github.com/huingsn/demo
> git --version # timeout=10
using GIT_ASKPASS to set credentials
> git fetch --tags --progress https://github.com/huingsn/demo +refs/heads/*:refs/remotes/origin/*
Seen branch in repository origin/github.com/huingsn/demo
Seen 1 remote branch
> git show-ref --tags -d # timeout=10
Checking out Revision c8688fb590873bd7b77f3537d11640a5139c0da4 (origin/github.com/huingsn/demo)
> git config core.sparsecheckout # timeout=10
> git checkout -f c8688fb590873bd7b77f3537d11640a5139c0da4
Commit message: "dfsdfsdfs"
> git rev-list --no-walk c8688fb590873bd7b77f3537d11640a5139c0da4 # timeout=10
Parsing POMs
Established TCP socket on 36677
[first] $ /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.201.b09-2.el7_6.x86_64/bin/java -cp /var/lib/jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-agent-1.12.jar:/usr/share/maven/boot/plexus-classworlds.jar org.jvnet.hudson.maven3.agent.Maven3Main /usr/share/maven /var/cache/jenkins/war/WEB-INF/lib/remoting-3.29.jar /var/lib/jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-1.12.jar /var/lib/jenkins/plugins/maven-plugin/WEB-INF/lib/maven3-interceptor-commons-1.12.jar 36677
<===[JENKINS REMOTING CAPACITY]===>channel started

Executing Maven: -B -f /var/lib/jenkins/workspace/first/pom.xml install

[INFO] Scanning for projects...

[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-jenkins-test Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ maven-jenkins-test ---

[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /var/lib/jenkins/workspace/first/src/main/resources

[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ maven-jenkins-test ---

[INFO] No sources to compile
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ maven-jenkins-test ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /var/lib/jenkins/workspace/first/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ maven-jenkins-test ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ maven-jenkins-test ---

[INFO] No tests to run.
[JENKINS] Recording test results
[WARNING] Attempt to (de-)serialize anonymous class hudson.maven.reporters.BuildInfoRecorder$1; see: https://jenkins.io/redirect/serialization-of-anonymous-classes/
[INFO]
[INFO] --- maven-war-plugin:3.2.2:war (default-war) @ maven-jenkins-test ---

[INFO] Packaging webapp
[INFO] Assembling webapp [maven-jenkins-test] in [/var/lib/jenkins/workspace/first/target/maven-jenkins-test]
[INFO] Processing war project
[INFO] Copying webapp resources [/var/lib/jenkins/workspace/first/src/main/webapp]
[INFO] Webapp assembled in [75 msecs]
[INFO] Building war: /var/lib/jenkins/workspace/first/target/maven-jenkins-test.war

[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ maven-jenkins-test ---

[INFO] Installing /var/lib/jenkins/workspace/first/target/maven-jenkins-test.war to /root/.m2/repository/com/hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.war
[INFO] Installing /var/lib/jenkins/workspace/first/pom.xml to /root/.m2/repository/com/hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.pom

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.804s
[INFO] Finished at: Sat Apr 06 23:06:26 CST 2019
[INFO] Final Memory: 13M/98M
[INFO] ------------------------------------------------------------------------
Waiting for Jenkins to finish collecting data
[JENKINS] Archiving /var/lib/jenkins/workspace/first/pom.xml to com.hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.pom
[JENKINS] Archiving /var/lib/jenkins/workspace/first/target/maven-jenkins-test.war to com.hu/maven-jenkins-test/1.0-SNAPSHOT/maven-jenkins-test-1.0-SNAPSHOT.war
channel stopped

Deploying /var/lib/jenkins/workspace/first/target/maven-jenkins-test.war to container Tomcat 7.x Remote with context
[/var/lib/jenkins/workspace/first/target/maven-jenkins-test.war] is not deployed. Doing a fresh deployment.
Deploying [/var/lib/jenkins/workspace/first/target/maven-jenkins-test.war]

Finished: SUCCESS

成功部署到Tomcat,http://192.168.1.112:8080/maven-jenkins-test/成功访问。

好了!到此一个项目的获取源码,打包,远程部署

github+jenkins+maven+docker自动化构建部署

发表于 2019-04-13 | 分类于 Jenkins

在linux系统中,搭建jenkins服务,定时(或githook)的方式从github上拉取maven工程,构建war包。使用docker构建image,推送到docker registry上。

ECMAScript 6 简介

发表于 2019-04-13 | 分类于 Node

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

部署进度

各大浏览器的最新版本,对 ES6 的支持可以查看kangax.github.io/compat-table/es6/。随着时间的推移,支持度已经越来越高了,超过 90%的 ES6 语法特性都实现了。

Node 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node 已经实现的 ES6 特性。

1
2
3
4
5
// Linux & Mac
$ node --v8-options | grep harmony

// Windows
$ node --v8-options | findstr harmony

Babel 转码器

Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。

1
2
3
4
5
6
7
// 转码前
input.map(item => item + 1);

// 转码后
input.map(function (item) {
return item + 1;
});

上面的原始代码用了箭头函数,Babel 将其转为普通函数,就能在不支持箭头函数的 JavaScript 环境执行了。

下面的命令在项目目录中,安装 Babel。

1
$ npm install --save-dev @babel/core

配置文件.babelrc

Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。

该文件用来设置转码规则和插件,基本格式如下。

1
2
3
4
{
"presets": [],
"plugins": []
}

presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

1
2
3
4
5
#最新转码规则
$ npm install --save-dev @babel/preset-env

#react 转码规则
$ npm install --save-dev @babel/preset-react

然后,将这些规则加入.babelrc。

1
2
3
4
5
6
7
{
"presets": [
"@babel/env",
"@babel/preset-react"
],
"plugins": []
}

注意,以下所有 Babel 工具和模块的使用,都必须先写好.babelrc。

命令行转码

Babel 提供命令行工具@babel/cli,用于命令行转码。

它的安装命令如下。

$ npm install –save-dev @babel/cli

基本用法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#转码结果输出到标准输出
$ npx babel example.js

#转码结果写入一个文件
#--out-file 或 -o 参数指定输出文件
$ npx babel example.js --out-file compiled.js
# 或者
$ npx babel example.js -o compiled.js

# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ npx babel src --out-dir lib
# 或者
$ npx babel src -d lib

# -s 参数生成source map文件
$ npx babel src -d lib -s

babel-node

@babel/node模块的babel-node命令,提供一个支持 ES6 的 REPL 环境。它支持 Node 的 REPL 环境的所有功能,而且可以直接运行 ES6 代码。

首先,安装这个模块。

$ npm install –save-dev @babel/node

然后,执行babel-node就进入 REPL 环境。

1
2
3
$ npx babel-node
> (x => x * 2)(1)
2

babel-node命令可以直接运行 ES6 脚本。将上面的代码放入脚本文件es6.js,然后直接运行。

1
2
3
4
# es6.js 的代码
# console.log((x => x * 2)(1));
$ npx babel-node es6.js
2

@babel/register 模块

@babel/register模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用 Babel 进行转码。

1
$ npm install --save-dev @babel/register

使用时,必须首先加载@babel/register。

1
2
3
// index.js
require('@babel/register');
require('./es6.js');

然后,就不需要手动对index.js转码了。

1
2
$ node index.js
2

需要注意的是,@babel/register只会对require命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。

babel API

如果某些代码需要调用 Babel 的 API 进行转码,就要使用@babel/core模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var babel = require('@babel/core');

// 字符串转码
babel.transform('code();', options);
// => { code, map, ast }

// 文件转码(异步)
babel.transformFile('filename.js', options, function(err, result) {
result; // => { code, map, ast }
});

// 文件转码(同步)
babel.transformFileSync('filename.js', options);
// => { code, map, ast }

// Babel AST转码
babel.transformFromAst(ast, code, options);
// => { code, map, ast }

配置对象options,可以参看官方文档http://babeljs.io/docs/usage/options/。

@babel/polyfill

Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如Iterator、Generator、Set、Map、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

ES6 在Array对象上新增了Array.from方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

安装命令如下。

1
$ npm install --save-dev @babel/polyfill

然后,在脚本头部,加入如下一行代码。

1
2
3
import '@babel/polyfill';
// 或者
require('@babel/polyfill');

Babel 默认不转码的 API 非常多,详细清单可以查看babel-plugin-transform-runtime模块的definitions.js文件。

浏览器环境

Babel 也可以用于浏览器环境,使用@babel/standalone模块提供的浏览器版本,将其插入网页。

1
2
3
4
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>

注意,网页实时将 ES6 代码转为 ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。

Babel 提供一个REPL 在线编译器,可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。

Traceur 转码器

Google 公司的Traceur转码器,也可以将 ES6 代码转为 ES5 代码。

直接插入网页

Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部加载 Traceur 库文件。

1
2
3
4
5
6
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script type="module">
import './Greeter.js';
</script>

上面代码中,一共有 4 个script标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用 ES6 代码。

注意,第四个script标签的type属性的值是module,而不是text/javascript。这是 Traceur 编译器识别 ES6 代码的标志,编译器会自动将所有type=module的代码编译为 ES5,然后再交给浏览器执行。

除了引用外部 ES6 脚本,也可以直接在网页中放置 ES6 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="module">
class Calc {
constructor() {
console.log('Calc constructor');
}
add(a, b) {
return a + b;
}
}

var c = new Calc();
console.log(c.add(4,5));
</script>

正常情况下,上面代码会在控制台打印出9。

如果想对 Traceur 的行为有精确控制,可以采用下面参数配置的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
// Create the System object
window.System = new traceur.runtime.BrowserTraceurLoader();
// Set some experimental options
var metadata = {
traceurOptions: {
experimental: true,
properTailCalls: true,
symbols: true,
arrayComprehension: true,
asyncFunctions: true,
asyncGenerators: exponentiation,
forOn: true,
generatorComprehension: true
}
};
// Load your module
System.import('./myModule.js', {metadata: metadata}).catch(function(ex) {
console.error('Import failed', ex.stack || ex);
});
</script>

上面代码中,首先生成 Traceur 的全局对象window.System,然后System.import方法可以用来加载 ES6。加载的时候,需要传入一个配置对象metadata,该对象的traceurOptions属性可以配置支持 ES6 功能。如果设为experimental: true,就表示除了 ES6 以外,还支持一些实验性的新功能。

在线转换

Traceur 也提供一个在线编译器,可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。

上面的例子转为 ES5 代码运行,就是下面这个样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script>
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";

var Calc = function Calc() {
console.log('Calc constructor');
};

($traceurRuntime.createClass)(Calc, {add: function(a, b) {
return a + b;
}}, {});

var c = new Calc();
console.log(c.add(4, 5));
return {};
});
</script>

命令行转换

作为命令行工具使用时,Traceur 是一个 Node 的模块,首先需要用 npm 安装。

$ npm install -g traceur

安装成功后,就可以在命令行下使用 Traceur 了。

Traceur 直接运行 ES6 脚本文件,会在标准输出显示运行结果,以前面的calc.js为例。

1
2
3
$ traceur calc.js
Calc constructor
9

如果要将 ES6 脚本转为 ES5 保存,要采用下面的写法。

1
$ traceur --script calc.es6.js --out calc.es5.js

上面代码的–script选项表示指定输入文件,–out选项表示指定输出文件。

为了防止有些特性编译不成功,最好加上–experimental选项。

1
$ traceur --script calc.es6.js --out calc.es5.js --experimental

命令行下转换生成的文件,就可以直接放到浏览器中运行。

Node 环境的用法

Traceur 的 Node 用法如下(假定已安装traceur模块)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var traceur = require('traceur');
var fs = require('fs');

// 将 ES6 脚本转为字符串
var contents = fs.readFileSync('es6-file.js').toString();

var result = traceur.compile(contents, {
filename: 'es6-file.js',
sourceMap: true,
// 其他设置
modules: 'commonjs'
});

if (result.error)
throw result.error;

// result 对象的 js 属性就是转换后的 ES5 代码
fs.writeFileSync('out.js', result.js);
// sourceMap 属性对应 map 文件
fs.writeFileSync('out.js.map', result.sourceMap);

Jenkins使用jenkinsfile脚本构建

发表于 2019-04-13 | 分类于 Jenkins

jenkinsfile脚本使用

Jenkins 2.0的精髓是Pipeline as Code,是帮助Jenkins实现CI到CD转变的重要角色。

Pipeline就是一套运行于Jenkins上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂发布流程。

持续交付管道(CD Pipeline)是将软件从版本控制阶段到交付给用户或客户的完整过程的自动化表现。软件的每一次更改(提交到源代码管理系统)都要经过一个复杂的过程才能被发布。

Pipeline提供了一组可扩展的工具,通过Pipeline Domain Specific Language(DSL)syntax可以达到Pipeline as Code(Jenkinsfile存储在项目的源代码库)的目的。

Pipeline的几个基本概念:

  • Stage:阶段,一个Pipeline可以划分成若干个Stage,每个Stage代表一组操作,例如:”Build”,”Test”,”Deploy”。

    Stage是一个逻辑分组的概念,可以跨多个Node
  • Node:节点,一个Node就是一个Jenkins节点,或者是Master,或者是Agent,是执行Step的具体运行环境。

  • Step:步骤,Step是最基本的操作单元,小到创建一个目录,大到构建一个Docker镜像,由各类Jenklins Plugin提供,例如:sh ‘make’

要使用Jenkins Pipeline,需要:Jenkins 2.x或更高版本和Pipeline插件

Pipeline定义

Pipeline脚本是用Groovy写的,可以通过以下任一方式创建基本Pipeline:

  • pipeline script:直接在Web UI的script输入框里面输入pipeline script语句即可,参考说明可以点击输入框下边的Pipeline Syntax,里面有很多示例操作说明,非常好用。
  • pipeline script from SCM:需要配置SCM代码存储Git地址或SVN地址,指定script文件Jenkinsfile所在路径,每次构建job会自动去指定的目录执行script文件

以上两种方法定义Pipeline的语法都是一样的。

Pipeline配有内置的文档功能192.168.1.112:8000/pipeline-syntax/

流水线方式搭建,安装好Pipeline Maven Integration Plugin插件:

pipeline-model-definition
build-pipeline-plugin
buildgraph-view
workflow-aggregator

也可以直接安装blueocean

1、在Web UI中定义Pipeline

要在Jenkins Web UI中创建基本Pipeline Job,单击Jenkins主页上的New Item。输入Pipeline的名称,选择Pipeline,然后单击确定。
在流水线里写入代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
node {
stage('build'){
echo 'build';
}

stage('test'){
echo 'test';
}

stage('deploy'){
echo 'deploy';
}
}

20200413163302

保存,立即构建

20200413163326

构建成功

20200413163345

2、在SCM中定义pipeline,也就是结合版本管理工具进行构建

复杂的Pipeline难以在Pipeline配置页面的文本区域内进行写入和维护。为了解决这一问题,jenkins Pipeline支持在文本编辑器中编写脚本文件jenkinsFile,Jenkins可以通过从SCM选项的控件中加载Pipeline脚本。指定要检索的Pipeline脚本的路径。更新指定的存储库时,只要Pipeline配置了SCM轮询触发器,就会触发一个新构建。

首先创建一个jenkinsfile文件上传到git,作为测试使用。创建构建,和上面一致,这里选择SCM方式。

20200413163427

20200413163441

保存后,立即构建成功

20200413163458

jenkinsfile脚本语法

支持两种语法

Declarative 声明式(在Pipeline plugin 2.5中引入)和 Scripted Pipeline 脚本式

如何创建最基本的PIpeline:直接在Jenkins Web UI 网页界面中输入脚本

通过创建一个jenkinsfile可以检入项目的源代码管理库

20200413163640

声明式Pipeline

声明式Pipeline的基本语法和表达式遵循与Groovy语法相同的规则,但有以下例外:

声明式pipeline必须包含在固定格式pipeline{}快内

每个声明语句必须独立一行,行尾无需使用分号

块(blocks{})只能包含章节(Sections),指令(Directives),步骤(Steps)或赋值语句

属性引用语句被视为无参数方法调用。例:输入被视为 input()

  • 块(blocks{})

由大括号括起来的语句,如pipeline{},Section{},parameters{},script{}

  • 章节(Sections)

通常包含一个或多个指令或步骤。如 agent 、post、stages、steps

  • 指令(Directives)

environment、options、parameters、triggers(触发)、stage、tools、when

  • 步骤(Steps)

Pipeline steps reference 执行脚本式pipeline:使用script{}

agent
必须存在,agent必须在pipeline块内的顶层定义,但stage内是否使用使可选的

参数:any/none/label/node/docker/dockerfile

常用选项 label/cuetomWorkspace/reuseNode

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
agent { label 'my-label' }

agent {
node {
label 'my-label'
customWorkspace '/some/other/path'
}
}

agent {
docker {
image 'nginx:1.12.2'
label 'my-label'
args '-v /tmp:/tmp'
}
}
post 不是必须的,用于pipeline的最外层或者stage{}中

pipeline {
agent any
stages {
stage('Example'){
steps {
echo 'Hello world'
}
}
}
post {
always {
echo 'say goodbay'
}
}
}

stages 必须,包括顺序执行的一个或多个stage命令,在pipeline内仅能使用一次,通常位于agent/options后面,例子如上

steps 必须,steps位于stage指令块内部,包括一个或多个step。仅有一个step的情况下可以忽略关键字step及其{},例子如上

environment 不是必须的,environment定义了一组全局的环境变量键值对,存在于pipeline{}或者stage指令内。执行特殊方法credentials()可以获取jenkins中预定义的凭证明文内容

1
2
3
4
5
6
7
8
9
10
11
12
13
environment {CC='clang'}
environment {AN_ACCESS_KEY = credentials('my-prefined-secret-text')}
steps {sh 'printenv'}

options 不是必须的 预定义pipeline专有的配置信息,仅可定义一次

pipeline {
agent any
options{
timeout(time:1,unit: 'HOURS')
}
...
}

parameters 不是必须的 定义参数化构建的参数可选参数 booleanParam,choice,file,text,password,run,string

1
2
3
4
5
6
7
8
paramenters {
choice(name:'PerformMavenRelease',choices:'False\nTrue',description:'desc')
password(name:'CredsToUse',description:'Apassword to build with',defaultValue:'')
}
environment {
BUILD_USR_CHOICE="${params.PerformMavenRelease}"
BUILD_USR_CREDS="${params.CredsToUse}"
}

triggers 不是必须的 定义pipeline被自动触发的方式选项 cron、pollSCM、upstream

triggers {cron('H 4/* 0 0 1-5')}
triggers {pollSCM('H 4/* 0 0 1-5')}
triggers {upstream(upstreamProjects:'job1,job2',threshold:hudson.model.Result.SUCCESS)}

常用的辅助工具

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
Snipper Generator(代码片段生成器,语法检查器)
Replay Pipeline(重放pipeline,可以修改script,修改后的不存入config.xml)
DSL Reference 语法参考手册
全局变量引用
Stage View
BlueOcean(可视化)
Pipeline神器:可视化编辑器
命令行Pipeline调试工具
变量的传递

自定义变量(局部)
def username = 'Jenkins'
echo "Hello Mr.${username}"
#注意一定要用双引号,单引号识别为字符串
环境变量(局部)
withEnv(['MYTOOL_HOME=/usr/local/mytool']){
sh '$MYTOOL_HOME/bin/start'
}
环境变量(全局)
environment {CC='clang'}
echo "Compiler is ${env.CC}"
参数化构建(全局)
parameters {string(name:'Jenkins',defaultValue:'Hello',description:'How should I greet the world')}
ehco "${params.Greeting} World!"
判断
when仅用于stage内部
when的内置条件为:
- when {branch 'master'}
- when {environment name:'DEPLOY_TO',value:'production'}
#当有环境变量 name 为 DEPLOY_TO 值是 production 条件成立
- when {expression {return params.DEBUG_BUILD} }
#表达式返回值为真时
- when {not {branch 'master'} }
- when {allOf {branch 'master'; environment name:'DEBUG_TO',value:'production'} }
#allOf 所有条件都满足时
- when {anyOf {branch 'master' ; branch 'staging'} }
#anyOf有一个条件满足时即可

判断和异常处理 流程控制if/else条件

1
2
3
4
5
6
7
8
9
node {
stage('Example'){
if(env.BRANCH_NAME == 'master'){
echo 'I only execute on the master branch'
}else {
echo 'Iexecute elsewhere'
}
}
}

异常处理try/catch/finally

1
2
3
4
5
6
7
8
9
10
11
node{
stage('Example'){
try{
sh 'exit 1'
}
catch (exc) {
echo 'something failed,I should sound the klaxons!'
throw
}
}
}

循环

for循环仅存在域脚本式pipeline中,但是可以通过在声明式pipeline中调用script step来执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pipeline {
agent any
stages {
stage('Example'){
steps{
echo 'Hello world!'
script {
def browsers = ['chrome','firefox']
for (int i = 0;i < browers.size();++i){
echo "Testing the ${browsers[i]} browser"
}
}
}
}
}
}

并发需要放在stages中,stages可以嵌套使用 stage下的steps和parallel不能共存,只能二选一
使用了并发的stage不能再有agent/tools 强制所有并发任务退出,加参数 failFast true

脚本式 Pipeline

在脚本式 Pipeline 语法中,一个或多个 node 块执行贯穿整个 Pipeline 的核心工作。虽然这不是脚本式 Pipeline 语法的强制性要求,但将管道 work 限制在 node 块内部会做两件事情:

通过将 item 添加到 Jenkins 队列来运行块中包含的 step。只要执行程序在 node 上空闲,这些步骤就会运行。
创建一个工作空间(特定于该特定 Pipeline 的目录),其中可以对从源代码管理检出的文件执行工作。
注意:根据不同的 Jenkins 配置,部分 workspaces 不会在一段不活动时间之后自动清理。查看与 JENKINS-2111 相关的标记和讨论以获取更多信息。
1
2
3
4
5
6
7
8
9
10
11
12
Jenkinsfile (Scripted Pipeline)
node { //在任何可用的代理上执行这个 Pipeline 或其任意 stage
stage('Build') { //定义 "Build" stage
// 执行和 "Build" stage 相关的 step
}
stage('Test') {
//
}
stage('Deploy') {
//
}
}

基于SpringBoot、Quartz分布式单节点持久化

发表于 2019-04-12 | 分类于 Quartz

demo见https://github.com/huingsn/tech-point-record

基于SpringBoot、Quartz分布式多节点负载持久化

发表于 2019-04-12 | 分类于 Quartz

demo见https://github.com/huingsn/tech-point-record

Java实现任务调度定时方式总结

发表于 2019-04-12 | 分类于 java

一、java最基本方式使用

使用java.util.Timer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public class TimedTask {
private static void useTimerImplTimedTask(){
// 第一个参数是任务,第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间,时间单位是毫秒
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("Local Time is " + new Date().toString());
}
},0L,1000L);
}
public static void main(String[] args) {
useTimerImplTimedTask();
}
}

使用java.util.concurrent.ScheduledExecutorService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public class TimedTask {
private static void userScheduledExecutorServiceImplTiemdTask(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Local Time is " + new Date().toString());
}
};
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 第一个参数是任务,第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间,第四个参数是时间单位
service.scheduleAtFixedRate(runnable, 0L, 1L, TimeUnit.SECONDS);
}
public static void main(String[] args) {
userScheduledExecutorServiceImplTiemdTask();
}
}

这样不好,建议创建使用线程池

推荐使用java.util.concurrent.ScheduledThreadPoolExecutor结合org.apache.commons.lang3.concurrent.BasicThreadFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public class TimedTask {
private static void useScheduledThreadPoolExecutorImplTimedTask(){
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
1, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(false).build());
// 第一个参数是任务,第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间,第四个参数是时间单位
scheduledThreadPoolExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("Local Time is " + new Date().toString());
}
}, 0L, 1L, TimeUnit.SECONDS);
}
public static void main(String[] args) {
useScheduledThreadPoolExecutorImplTimedTask();
}
}

使用java.lang.Thread方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public class TimedTask {
private static void useThreadImplTimedTask(){
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("Local Time is " + new Date().toString());
try {
//时间间隔,单位是毫秒
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
public static void main(String[] args) {
useThreadImplTimedTask();
}
}

这种方式不优雅

二、Quartz框架实现

Quartz是一个强大任务调度框架,一个简单示例:

1
2
3
4
5
6
7
 <dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>

任务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 实现任务,任务执行逻辑
*/
public class HelloJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("现在的时间是:"+ sf.format(date));
//每两秒钟打印
System.out.println("Hello");
//每天晚上12点执行任务
//System.out.println("开始生成任务报表");
}
}

任务调度

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
 /**
* 任务调用,或者说任务触发地方
* 一个基本Quartz的任务再来介绍一下Quartz的3个重要组成,JobDetail,Trigger,Scheduler
* Scheduler - 用于与调度程序交互的主程序接口。
* Job - 我们预先定义的希望在未来时间能被调度程序执行的任务类。
* JobDetail - 使用JobDetail来定义定时任务的实例。
* Trigger - 触发器,表明任务在什么时候会执行,执行的时间条件。
* JobBuilder -用于声明一个任务实例,也可以定义关于该任务的详情比如任务名、组名等,这个声明的实例将会作为一个实际执行的任务。
* TriggerBuilder - 触发器创建器,用于创建触发器trigger实例。
* 一个调度器过程
* 1、创建调度工厂();   
* 2、根据工厂取得调度器实例();   
* 3、Builder模式构建子组件<Job,Trigger>    // builder模式, 如JobBuilder、TriggerBuilder、DateBuilder
* 4、通过调度器组装子组件 调度器.组装<子组件1,子组件2...>   
* 5、调度器.start();
*/
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
//创建一个任务的实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("demoJob").build();
//创建一个Trigger触发器的实例,定义该job立即执行,并且每4秒执行一次,一直执行
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("demoTrigger").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(4).repeatForever()).build();
//每天晚上12点触发任务
//CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("cronTrigger").withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")).build();
/**
* cron表达式编写规则
* Quartz Cron 表达式支持7个域 ,分别是秒/分/时/日/月/周/年,其中年是非必须项
*/
//创建schedule实例
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
//执行任务
scheduler.start();
scheduler.scheduleJob(jobDetail,trigger);
}
}

cron表达式编写规则

Quartz Cron 表达式支持7个域 ,分别是秒/分/时/日/月/周/年,其中年是非必须项

名称    是否必须    允许值    特殊字符
秒    是    0-59    , - * /
分    是    0-59    , - * /
时    是    0-23    , - * /
日    是    1-31    , - * ? / L W C
月    是    1-12 或 JAN-DEC    , - * /
周    是    1-7 或 SUN-SAT    , - * ? / L C #
年    否    空 或 1970-2099    , - * /
cron表达式中不区分大小写.

星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如, 在分钟字段时,表示“每分钟”;
问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;
逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;
L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;
W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;
LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;
井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;
C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。
Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。
官方示例:
表示式    说明
0 0 12 * * ?    每天12点运行
0 15 10 * * ?    每天10:15运行
0 15 10 * * ? 2008    在2008年的每天10:15运行
0 * 14 * * ?    每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。
0 0/5 14 * * ?    每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。
0 0/5 14,18 * * ?    每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。                         
0 0-5 14 * * ?    每天14:00点到14:05,每分钟运行一次。
0 10,44 14 ? 3 WED    3月每周三的14:10分到14:44,每分钟运行一次。
0 15 10 ? * MON-FRI    每周一,二,三,四,五的10:15分运行。
0 15 10 15 * ?    每月15日10:15分运行。
0 15 10 L * ?    每月最后一天10:15分运行。
0 15 10 ? * 6L    每月最后一个星期五10:15分运行。
0 15 10 ? * 6L 2007-2009                             在2007,2008,2009年每个月的最后一个星期五的10:15分运行。
0 15 10 ? * 6#3    每月第三个星期五的10:15分运行。

Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,主要类和接口解释:

Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;

JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。

Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案;

Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合,一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。

Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;

ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。

Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。

    所以,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。
  如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。

  Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过JobExecutionContext#getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。

  Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理事件。

Quartz的3大API之Job https://www.cnblogs.com/zhanghaoliang/p/7886110.html

三、Spring方式实现

1、使用注解

在spring配置中加入task的命名空间:

1
2
3
 xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd"

然后使用task配置扫描注解

1
2
3
 <!-- 定时任务 -->
<task:annotation-driven scheduler="qbScheduler" mode="proxy"/>
<task:scheduler id="qbScheduler" />

此时就可以直接使用@Scheduled(cron = “时间格式串”),应用该注解就可以实现定时的功能。

1
2
3
4
 @Scheduled(cron = "0/5 * * * * ?")  //每隔5秒执行一次定时任务
public void consoleInfo(){
System.out.println("定时任务");
}

2、不使用注解实现定时任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation=" http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd”
<description>
定时任务
</description>
//定时注解驱动
<task:annotation-driven />
//进行定时任务的类,将其定义为一个bean
<bean id="spaceStatisticsService" class="com.pojo.system.manager.sigar.impl.SpaceStatisticsServiceImpl"></bean>
//通过task标签,定义定时功能
<task:scheduled-tasks>
<task:scheduled ref="spaceStatisticsService" method="statisticSpace" cron="59 59 23 * * ?" />
</task:scheduled-tasks>

Java:

1
2
3
4
5
6
7
 @Service
public class SpaceStatisticsServiceImpl implements SpaceStatisticsService{
@Override
public void statisticSpace() {
System.out.println("实现定时功能");
}
}

3、spring boot上使用

开启定时任务功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 @SpringBootApplication
@EnableScheduling
public class ReadingListApplication {
public static void main(String[] args) {
//启动入口
SpringApplication.run(ReadingListApplication.class, args);
}
}
任务:
@Component
public class Task {
@Scheduled(cron = "0/5 * * * * ?")
public void pringMsg(){
System.out.println(11);
}
}

运行项目就会有结果

上面是单线程执行,如果需要并发多线程执行,这样做:

增加配置

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
/**
* 并发任务,使其任务在多线程执行。
* 通过ScheduleConfig配置文件实现SchedulingConfigurer接口,并重写setSchedulerfang方法
*/
@Configuration
class ScheduledConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(2));//分配两个线程
}
}
多个任务:
@Component
public class Task {
@Scheduled(cron = "0/5 * * * * ?")
public void pringMsg(){
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())+"----每5秒执行一次");
}
@Scheduled(cron = "0/1 * * * * ?")
public void pringMsg1(){
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())+"----每1秒执行一次");
}
}

四、Spring Boot集成Quartz

这里直接介绍spring boot整合Quartz

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
 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hu</groupId>
<artifactId>quartz-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

创建两个任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestTask1 extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext context)throws JobExecutionException{
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("TestQuartz01----"+sdf.format(new Date()));
}
}
public class TestTask2 extends QuartzJobBean{
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("TestQuartz02----" + sdf.format(new Date()));
}
}

配置定时任务执行:

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
/**
* 配置定时任务
*/
@Configuration
public class QuartzConfig {
@Bean
public JobDetail testQuartz1() {
return JobBuilder.newJob(TestTask1.class).withIdentity("testTask1").storeDurably().build();
}
@Bean
public Trigger testQuartzTrigger1() {
//5秒执行一次
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever();
return TriggerBuilder.newTrigger().forJob(testQuartz1())
.withIdentity("testTask1")
.withSchedule(scheduleBuilder)
.build();
}
@Bean
public JobDetail testQuartz2() {
return JobBuilder.newJob(TestTask2.class).withIdentity("testTask2").storeDurably().build();
}
@Bean
public Trigger testQuartzTrigger2() {
//cron方式,每隔1秒执行一次
return TriggerBuilder.newTrigger().forJob(testQuartz2())
.withIdentity("testTask2")
.withSchedule(CronScheduleBuilder.cronSchedule("*/1 * * * * ?"))
.build();
}
}

入口:

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

运行没问题

2019-05-28 19:24:31.254  INFO 4368 --- [          main] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.
TestQuartz02----2019-05-28 19:24:31
TestQuartz01----2019-05-28 19:24:31
2019-05-28 19:24:31.270  INFO 4368 --- [          main] com.hu.Application                      : Started Application in 0.656 seconds (JVM running for 1.008)
TestQuartz02----2019-05-28 19:24:32
TestQuartz02----2019-05-28 19:24:33
TestQuartz02----2019-05-28 19:24:34
TestQuartz02----2019-05-28 19:24:35
TestQuartz02----2019-05-28 19:24:36
TestQuartz01----2019-05-28 19:24:36
TestQuartz02----2019-05-28 19:24:37
TestQuartz02----2019-05-28 19:24:38

具体见项目spring-boot-starter-quartz
https://github.com/huingsn/spring-boot-sample-demo/tree/master/quartz
参考https://www.jianshu.com/p/d52d62fb2ac6

在新版本的SpringBoot2.0发布后,SpringBoot2.0版本集成了Quartz2.3.0官网最新版本,整合到spring-boot-starter-quartz。针对Quartz新版本进行了 AutoConfiguration自动化配置,省去了很多繁琐的配置。

之前调度中心简单例子中,使用spring集成Quartz,这个示例把之前改为使用spring-boot-starter-quartz的项目。
见:spring-boot-starter-quartz
在此基础上修改:
1、pom.xml配置文件,SpringBoot为我们提供了对应的依赖,我们将之前的quartz相关依赖删除,替换为spring-boot-starter-quartz。
2、删除QuartzConfiguration配置类
之前使用 QuartzConfiguration配置类来完成了 Quartz需要的一系列配置,如: JobFactory、 SchedulerFactoryBean等,在我们添加 spring-boot-starter-quartz依赖后就不需要主动声明工厂类,因为 spring-boot-starter-quartz已经为我们自动化配置好了。

自动化配置源码
查看spring-boot-autoconfigure-2.0.0.RELEASE.jar,找到org.springframework.boot.autoconfigure.quartz,该目录就是SpringBoot提供的Quartz自动化配置源码实现,在该目录下有如下所示几个类:
AutowireCapableBeanJobFactory
该类替代了我们之前在QuartzConfiguration配置类的AutowiringSpringBeanJobFactory内部类实现,主要作用是我们自定义的QuartzJobBean子类被Spring IOC进行托管,可以在定时任务类内使用注入任意被Spring IOC托管的类。
JobStoreType
该类是一个枚举类型,定义了对应application.yml、application.properties文件内spring.quartz.job-store-type配置,其目的是配置quartz任务的数据存储方式,分别为:MEMORY(内存方式:默认)、JDBC(数据库方式)。
QuartzAutoConfiguration
该类是自动配置的主类,内部配置了SchedulerFactoryBean以及JdbcStoreTypeConfiguration,使用QuartzProperties作为属性自动化配置条件。
QuartzDataSourceInitializer
该类主要用于数据源初始化后的一些操作,根据不同平台类型的数据库进行选择不同的数据库脚本。
QuartzProperties
该类对应了spring.quartz在application.yml、application.properties文件内开头的相关配置。
SchedulerFactoryBeanCustomizer
这是一个接口,我们实现该接口后并且将实现类使用Spring IOC托管,可以完成SchedulerFactoryBean的个性化设置,这里的设置完全可以对SchedulerFactoryBean做出全部的设置变更。

3、在springboot配置文件中增加Quartz的配置信息,移除之前的Quartz配置文件。
其实就是把之前 quartz.properties配置文件内的所有配置转换成 YAML风格,对应的添加在该配置下即可,在 QuartzAutoConfiguration类内,会自动调用 SchedulerFactoryBean的 setQuartzProperties方法,把 spring.quartz.properties内的所有配置进行设置。

完成,启动测试和之前一样。

Nodejs—MVVM

发表于 2019-04-12 | 分类于 Node

MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。

把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。需要用JavaScript编写一个通用的ViewModel,这样,就可以复用整个MVVM模型了。

单向绑定

MVVM就是在前端页面上,应用了扩展的MVC模式,我们关心Model的变化,MVVM框架自动把Model的变化映射到DOM结构上,这样,用户看到的页面内容就会随着Model的变化而更新。

经过MVVM框架的自动转换,浏览器就可以直接显示Model的数据。目前,常用的MVVM框架有:

1
2
3
Angular:Google出品,名气大,但是很难用;
Backbone.js:入门非常困难,因为自身API太多;
Ember:一个大而全的框架,想写个Hello world都很困难。

我们选择MVVM的目标应该是入门容易,安装简单,能直接在页面写JavaScript,需要更复杂的功能时又能扩展支持。综合考察,最佳选择是尤雨溪大神开发的MVVM框架:Vue.js 目前,Vue.js的最新版本是2.0,我们会使用2.0版本作为示例。

创建基于koa的Node.js项目结构如下:

编写MVVM

Model是一个JavaScript对象,它包含两个属性:

1
2
3
4
{
name: 'hu',
age: 15
}

而负责显示的是DOM节点可以用和来引用Model的属性:

1
2
3
4
<div id="vm">
<p>Hello, {{ name }}!</p>
<p>You are {{ age }} years old!</p>
</div>

最后一步是用Vue把两者关联起来。

在内部编写的JavaScript代码,需要用jQuery把MVVM的初始化代码推迟到页面加载完毕后执行,否则,直接在内执行MVVM代码时,DOM节点尚未被浏览器加载,初始化将失败。正确的写法如下:

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
<html>
<head>

<!-- 引用jQuery -->
<script src="/static/js/jquery.min.js"></script>

<!-- 引用Vue -->
<script src="/static/js/vue.js"></script>

<script>
// 初始化代码:
$(function () {
var vm = new Vue({
el: '#vm',
data: {
name: 'hu',
age: 15
}
});
window.vm = vm;
});
</script>

</head>

<body>
<div id="vm">
<p>Hello, {{ name }}!</p>
<p>You are {{ age }} years old!</p>
</div>

</body>
<html>

其中,el指定了要把Model绑定到哪个DOM根节点上,语法和jQuery类似。这里的’#vm’对应ID为vm的一个

节点:

在该节点以及该节点内部,就是Vue可以操作的View。Vue可以自动把Model的状态映射到View上,但是不能操作View范围之外的其他DOM节点。

然后,data属性指定了Model,我们初始化了Model的两个属性name和age,在View内部的

节点上,可以直接用引用Model的某个属性。

如果打开浏览器console,因为我们用代码window.vm = vm,把VM变量绑定到了window对象上,所以,可以直接修改VM的Model:

Vue作为MVVM框架会自动监听Model的任何变化,在Model数据变化时,更新View的显示。这种Model到View的绑定我们称为单向绑定。

单向绑定

在Vue中,可以直接写绑定某个属性。如果属性关联的是对象,还可以用多个.引用,例如,。

另一种单向绑定的方法是使用Vue的指令v-text,写法如下:

Hello, !

这种写法是把指令写在HTML节点的属性上,它会被Vue解析,该节点的文本内容会被绑定为Model的指定属性,注意不能再写双花括号{{ }}。

双向绑定

在Vue中,使用双向绑定非常容易,我们仍然先创建一个VM实例:

1
2
3
4
5
6
7
8
9
10
$(function () {
var vm = new Vue({
el: '#vm',
data: {
email: '',
name: ''
}
});
window.vm = vm;
});

然后,编写一个HTML FORM表单,并用v-model指令把某个<input>和Model的某个属性作双向绑定:

1
2
3
4
<form id="vm" action="#">
<p><input v-model="email"></p>
<p><input v-model="name"></p>
</form>

我们可以在表单中输入内容,然后在浏览器console中用window.vm.$data查看Model的内容,也可以用window.vm.name查看Model的name属性,它的值和FORM表单对应的<input>是一致的。

如果在浏览器console中用JavaScript更新Model,例如,执行window.vm.name=’22222’,表单对应的<input>内容就会立刻更新。

多个checkbox可以和数组绑定:

1
2
3
<label><input type="checkbox" v-model="language" value="zh"> Chinese</label>
<label><input type="checkbox" v-model="language" value="en"> English</label>
<label><input type="checkbox" v-model="language" value="fr"> French</label>

对应的Model为:

1
language: ['zh', 'en']

单个checkbox可以和boolean类型变量绑定:

<input type="checkbox" v-model="subscribe">

对应的Model为:

subscribe: true; // 根据checkbox是否选中为true/false

下拉框<select>绑定的是字符串,但是要注意,绑定的是value而非用户看到的文本:

1
2
3
4
5
<select v-model="city">
<option value="bj">Beijing</option>
<option value="sh">Shanghai</option>
<option value="gz">Guangzhou</option>
</select>

对应的Model为:

city: ‘bj’ // 对应option的value

双向绑定最大的好处是我们不再需要用jQuery去查询表单的状态,而是直接获得了用JavaScript对象表示的Model。

处理事件

当用户提交表单时,传统的做法是响应onsubmit事件,用jQuery获取表单内容,检查输入是否有效,最后提交表单,或者用AJAX提交表单。

现在,获取表单内容已经不需要了,因为双向绑定直接让我们获得了表单内容,并且获得了合适的数据类型。

响应onsubmit事件也可以放到VM中。我们在

元素上使用指令:

<form id="vm" v-on:submit.prevent="register">

其中,v-on:submit=”register”指令就会自动监听表单的submit事件,并调用register方法处理该事件。使用.prevent表示阻止事件冒泡,这样,浏览器不再处理的submit事件。

因为我们指定了事件处理函数是register,所以需要在创建VM时添加一个register函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
var vm = new Vue({
el: '#vm',
data: {
...
},
methods: {
register: function () {
// 显示JSON格式的Model:
alert(JSON.stringify(this.$data));
// TODO: AJAX POST...
}
}
});

在register()函数内部,我们可以用AJAX把JSON格式的Model发送给服务器,就完成了用户注册的功能。

同步DOM结构

除了简单的单向绑定和双向绑定,MVVM还有一个重要的用途,就是让Model和DOM的结构保持同步。

我们用一个TODO的列表作为示例,从用户角度看,一个TODO列表在DOM结构的表现形式就是一组

  • 节点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <ol>
    <li>
    <dl>
    <dt>产品评审</dt>
    <dd>新款iPhone上市前评审</dd>
    </dl>
    </li>
    <li>
    <dl>
    <dt>开发计划</dt>
    <dd>与PM确定下一版Android开发计划</dd>
    </dl>
    </li>
    <li>
    <dl>
    <dt>VC会议</dt>
    <dd>敲定C轮5000万美元融资</dd>
    </dl>
    </li>
    </ol>

    而对应的Model可以用JavaScript数组表示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    todos: [
    {
    name: '产品评审',
    description: '新款iPhone上市前评审'
    },
    {
    name: '开发计划',
    description: '与PM确定下一版Android开发计划'
    },
    {
    name: 'VC会议',
    description: '敲定C轮5000万美元融资'
    }
    ]

    使用MVVM时,当我们更新Model时,DOM结构会随着Model的变化而自动更新。当todos数组增加或删除元素时,相应的DOM节点会增加

  • 或者删除
  • 节点。

    在Vue中,可以使用v-for指令来实现:

    1
    2
    3
    4
    5
    6
    7
    8
    <ol>
    <li v-for="t in todos">
    <dl>
    <dt>{{ t.name }}</dt>
    <dd>{{ t.description }}</dd>
    </dl>
    </li>
    </ol>

    v-for指令把数组和一组

  • 元素绑定了。在
  • 元素内部,用循环变量t引用某个属性,例如,。这样,我们只关心如何更新Model,不关心如何增删DOM节点,大大简化了整个页面的逻辑。

    我们可以在浏览器console中用window.vm.todos[0].name=’计划有变’查看View的变化,或者通过window.vm.todos.push({name:’新计划’,description:’blabla…’})来增加一个数组元素,从而自动添加一个

  • 元素。

    需要注意的是,Vue之所以能够监听Model状态的变化,是因为JavaScript语言本身提供了Proxy或者Object.observe()机制来监听对象状态的变化。但是,对于数组元素的赋值,却没有办法直接监听,因此,如果我们直接对数组元素赋值:

    1
    2
    3
    4
    vm.todos[0] = {
    name: 'New name',
    description: 'New description'
    };

    会导致Vue无法更新View。

    正确的方法是不要对数组元素赋值,而是更新:

    1
    2
    3
    4
    5
    6
    7
    8
    vm.todos[0].name = 'New name';
    vm.todos[0].description = 'New description';
    或者,通过splice()方法,删除某个元素后,再添加一个元素,达到“赋值”的效果:

    var index = 0;
    var newElement = {...};
    vm.todos.splice(index, 1, newElement);
    Vue可以监听数组的splice、push、unshift等方法调用,所以,上述代码可以正确更新View。

    和后端交互

    现在,如果要把这个简单的TODO应用变成一个用户能使用的Web应用,我们需要解决几个问题:

    • 用户的TODO数据应该从后台读取;
    • 对TODO的增删改必须同步到服务器后端;
    • 用户在View上必须能够修改TODO。

    第1个和第2个问题都是和API相关的。只要我们实现了合适的API接口,就可以在MVVM内部更新Model的同时,通过API把数据更新反映到服务器端,这样,用户数据就保存到了服务器端,下次打开页面时就可以读取TODO列表。

    在api.js文件中,我们用数组在服务器端模拟一个数据库,然后实现以下4个API:

    1
    2
    3
    4
    GET /api/todos:返回所有TODO列表;
    POST /api/todos:创建一个新的TODO,并返回创建后的对象;
    PUT /api/todos/:id:更新一个TODO,并返回更新后的对象;
    DELETE /api/todos/:id:删除一个TODO。

    和上一节的TODO数据结构相比,我们需要增加一个id属性,来唯一标识一个TODO。

    准备好API后,在Vue中,有两个方法:一是直接用jQuery的AJAX调用REST API,不过这种方式比较麻烦。第二个方法是使用vue-resource这个针对Vue的扩展,它可以给VM对象加上一个$resource属性,通过$resource来方便地操作API。

    使用vue-resource只需要在导入vue.js后,加一行

    我们给VM增加一个init()方法,读取TODO列表:

    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
    var vm = new Vue({
    el: '#vm',
    data: {
    title: 'TODO List',
    todos: []
    },
    created: function () {
    this.init();
    },
    methods: {
    init: function () {
    var that = this;
    that.$resource('/api/todos').get().then(function (resp) {
    // 调用API成功时调用json()异步返回结果:
    resp.json().then(function (result) {
    // 更新VM的todos:
    that.todos = result.todos;
    });
    }, function (resp) {
    // 调用API失败:
    alert('error');
    });
    }
    }
    });

    注意到创建VM时,created指定了当VM初始化成功后的回调函数,这样,init()方法会被自动调用。

    类似的,对于添加、修改、删除的操作,我们也需要往VM中添加对应的函数。以添加为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var vm = new Vue({
    ...
    methods: {
    ...
    create: function (todo) {
    var that = this;
    that.$resource('/api/todos').save(todo).then(function (resp) {
    resp.json().then(function (result) {
    that.todos.push(result);
    });
    }, showError);
    },
    update: function (todo, prop, e) {
    ...
    },
    remove: function (todo) {
    ...
    }
    }
    });

    添加操作需要一个额外的表单,我们可以创建另一个VM对象vmAdd来对表单作双向绑定,然后,在提交表单的事件中调用vm对象的create方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var vmAdd = new Vue({
    el: '#vmAdd',
    data: {
    name: '',
    description: ''
    },
    methods: {
    submit: function () {
    vm.create(this.$data);
    }
    }
    });

    vmAdd和FORM表单绑定:

    1
    2
    3
    4
    5
    <form id="vmAdd" action="#0" v-on:submit.prevent="submit">
    <p><input type="text" v-model="name"></p>
    <p><input type="text" v-model="description"></p>
    <p><button type="submit">Add</button></p>
    </form>

    最后,把vm绑定到对应的DOM:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <div id="vm">
    <h3>{{ title }}</h3>
    <ol>
    <li v-for="t in todos">
    <dl>
    <dt contenteditable="true" v-on:blur="update(t, 'name', $event)">{{ t.name }}</dt>
    <dd contenteditable="true" v-on:blur="update(t, 'description', $event)">{{ t.description }}</dd>
    <dd><a href="#0" v-on:click="remove(t)">Delete</a></dd>
    </dl>
    </li>
    </ol>
    </div>

    这里我们用contenteditable=”true”让DOM节点变成可编辑的,用v-on:blur=”update(t, ‘name’, $event)”在编辑结束时调用update()方法并传入参数,特殊变量$event表示DOM事件本身。

    删除TODO是通过对节点绑定v-on:click事件并调用remove()方法实现的。

    如果一切无误,我们就可以先启动服务器,然后在浏览器中访问http://localhost:3000/static/index.html,对TODO进行增删改等操作,操作结果会保存在服务器端。

    通过Vue和vue-resource插件,我们用简单的几十行代码就实现了一个真正可用的TODO应用。

  • 上一页1…567…25下一页
    初晨

    初晨

    永远不要说你知道本质,更别说真相了。

    249 日志
    46 分类
    109 标签
    近期文章
    • WebSocket、Socket、TCP、HTTP区别
    • Springboot项目的接口防刷
    • 深入理解Volatile关键字及其实现原理
    • 使用vscode搭建个人笔记环境
    • HBase介绍安装与操作
    © 2018 — 2020 Copyright
    由 Hexo 强力驱动
    |
    主题 — NexT.Gemini v5.1.4