3F

Fight For Freedom


  • 主页

  • 关于

  • 标签

  • 分类

  • 归档

CSS 知识总结

Posted on 2019-07-20 | Post modified: 2019-07-22   |   post.updated 2019-07-22 | In Web 前端 |

CSS 简介

如上篇《HTML 知识总结》所描述的那样:HTML 是页面的骨架,CSS 是页面的样式, JavaScript 是页面的行为。CSS 的基本语法如下图所示:

CSS 常用属性

Colors 属性

颜色可以使用预定义的颜色名称或RGB、十六进制、HSL、RGBA、HSLA值指定,详情可查看 CSS Colors。例如:

1
p {color: blue;}

1
p {color: #46AEDA;}

等等。

Background Color
1
p {background-color: blue;}
Text Color
1
p {color: blue;}
Border Color
1
p {border-color: blue;}

Backgrounds 属性

background-color
1
body {background-color: lightblue;}
background-image

background-image 属性指定样式的背景图片,url 中指定图片路径即可,如:

1
body {background-image: url("page.gif");}

background-repeat

background-repeat 属性指定背景图片的排版,属性值有:repeat-x(水平方向重复)、repeat-y(垂直方向重复)、repeat(向x y方向重复,这是默认值)、round(平铺图片)、no-repeat(不重复)、inherit(继承)等,如:

1
2
3
4
body {
background-image: url("page.gif");
background-repeat: repeat-x;
}

background-attachment

background-attachment 属性指定背景图片的附着方式,属性值有:fixed(固定)、scroll(滚动),设置为 fixed 图片不随页面的滚动而滚动,设置为 scroll 则会随着页面的滚动而滚动,如:

1
2
3
4
body {
background-image: url("page.gif");
background-attachment: scroll;
}

background-position

background-position 属性指定背景图片的位置,属性值有:left、right、top、center 等,如:

1
2
3
4
body {
background-image: url("page.gif");
background-position: top right;
}

Height/Width 属性

可以用属性 height/width 修饰元素的宽高,属性值可以是 px、cm 或者百分比的形式,如:

1
2
3
4
5
div {
height: 300px;
width: 50%;
background-color: burlywood;
}

另外,除了 height/weight 属性还有:max-height/max-width(设置元素最大高度/宽度)、min-height/min-width(设置元素最小高度/宽度)。

Text 属性

Text Alignment

text-align 用作文本的排列,属性值有:center、left、right、justify(每行宽度一样)等,如:

1
2
3
div {
text-align: justify;
}

Text Decoration

text-decoraton 用于装饰文本,属性值有:overline(上划线)、line-through(中划线)、underline(下划线)、none(不要装饰文本),如:

1
2
3
h2 {
text-decoration: line-through;
}

Text Transformation

text-transform 用于指定文本大小写,属性值有:uppercase(文本全部大写)、lowercase(文本全部小写)、capitalize(文本首字母大写),如:

1
2
3
p {
text-transform: capitalize;
}

Text Indentation

text-indent 用于指定文本首行缩进,如:

1
2
3
p {
text-indent: 40px;
}

除了以上的文本属性外,还有:letter-spacing(设置字符直接的空格)、line-height(设置文本行与行之间的行高)、direction(设置文本的元素方向)、word-spacing(设置单词之间的空格)、text-shadow(设置文本阴影效果)等等属性,感兴趣的同学自行 google 了,这里不再介绍。

Fonts 属性

Font Family

fon-family 可以指定文本字体类型,属性值有:Time New Roman、Times、serif、Arial、Helvetica、sans-serif等字体类型,如:

1
2
3
p {
font-family: "Times New Roman";
}

Font Style

fon-style 可以指定文本字体样式,属性值有:normal(正常字体)、italic(斜体)、oblique(与 italic 相似的斜体,但是可能浏览器支持没有 italic 广)字体,如:

1
2
3
p {
font-style: italic;
}

Font Size

fon-size 指定文字体尺寸,属性值可以用 px 或 em (px/16=em),如:

1
2
3
p {
font-size: 16px;
}

Font Weight

fon-weight 指定文字重量,属性值可以是:normal、bold(粗体)等,如:

1
2
3
p {
font-weight: bold;
}

Links 属性

链接标签 <a> 有四个状态:link(当未访问链接时)、visited(当用户访问了链接)、hover(当鼠标放在链接上时)、active(当鼠标点击了链接)。同时需要注意的是:hover 需要写在 link 和 visited 之后;active 需要写在 hover 之后。可以看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<style>
a:link {
color: red;
}
a:visited {
color: green;
}
a:hover {
color:blue;
}
a:active {
color: yellow;
}
</style>
</head>
<body>
<p><b><a href="http://www.baidu.com" target="_blank">This is a link</a></b></p>
</body>
</html>

以上的例子在打开网页时链接会显示为绿色,将鼠标移动到链接上时会显示为蓝色,当鼠标点击了链接后颜色会变成黄色。

Position 属性

元素的位置定位需要用到 positon 属性,其属性值有:static(默认值,没有定位属性,元素正常出现在文档流中)、relative(相对定位,相对于元素的正常位置进行定位)、absolute(绝对定位,相对于除 static 定位以外的元素进行定位)、fixed(固定位置,相对于浏览器进行定位,网站中的固定 header 和 footer 就是用固定定位来实现的),如:

1
2
3
4
5
div {
position: relative;
left: 30px;
border: 3px solid #00acec; /* 分别是 border-width、border-style(虚线、实线、双线等)、border-color 的缩写 */
}

以上的 div 就会偏离左侧 30 个像素开始显示。

Float 属性

float 属性用户定位和格式化内容,其属性值有:left(元素在容器的左侧浮动)、right(元素在容器的右侧浮动)、none(默认值,不浮动)、inherit(继承父类的属性值)。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<style>
img {
float: left;
}
</style>
<body>
<p>The image will float to the left in the paragraph.</p>
<p><img src="example.jpg" style="width:170px;height:170px;"></p>
</body>
</head>
</html>

以上的图片就会浮动在浏览器页面的左侧。
再举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<style>
.div1 {
float: left;
width: 200px;
height: 100px;
margin: 20px;
border: 3px solid #4c75a3;
}
.div2 {
border: 1px solid #8e908c;
}
</style>
<body>
<div class="div1">div1</div>
<div class="div2">div2</div>
</body>
</head>
</html>

结果如下图所示:

我们先不用管 div1 中各个属性是什么意思,只知道它是向左浮动的,然后肯定是希望 div2 是在 div1 下方显示。但是,很不幸,事与愿违。其实这背后的原因是这样的,指明了 float 属性的元素是脱离文档流(页面的显示像流水一样,比如鼠标上下滑动页面也跟着上下滑动)的,所以 div2 的元素就显示在 div1 的上方了。那有什么办法实现我们预期的效果呢?答案就是使用 clear 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<style>
.div1 {
float: left;
width: 200px;
height: 100px;
margin: 20px;
border: 3px solid #4c75a3;
}
.div2 {
border: 1px solid #8e908c;
clear: left;
}
</style>
<body>
<div class="div1">div1</div>
<div class="div2">div2</div>
</body>
</head>
</html>

其中,clear: left 的意思就是说 div2 的左侧不允许出现 float 元素。所以,这样就达到了预期效果,结果如下图所示:

Align 排列

居中元素

居中块级元素(如:div)可以使用 margin: auto,如:

1
2
3
4
5
. center {
margin: auto;
width: 60%;
border: 5px solid red;
}

其中选择器用于修饰块级元素(如:div)元素就会居中显示。

居中文本

居中文本可以使用 text-align: center,如:

1
2
3
4
. center {
text-align: center;
border: 5px solid red;
}

居中图片

居中图片可以设置 margin-left 和 margin-right 为 auto,并让其作为块元素,如:

1
2
3
4
5
6
img {
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
}

Inline-block 显示

关于显示 display 有三个属性值:inline(元素行内显示,可是设置 width 和 height,但是设置 top
和 bottom 无效)、inline-block(跟 inline 类似,但是设置 top 和 bottom 可以生效)、block(跟 inline-block 相似,但是多了 line-break(会换行显示))。如:

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
 <html>
<head>
<style>
.inline {
display: inline;
width: 100px;
height: 100px;
padding: 5px;
border: 1px solid blue;
}
.inline-block {
display: inline-block;
width: 100px;
height: 100px;
padding: 5px;
border: 1px solid blue;
}
.block {
display: block;
width: 100px;
height: 100px;
padding: 5px;
border: 1px solid blue;
}
</style>
<body>
<h2>inline display</h2>
<div>
So you want to learn CSS? Great decision; CSS is the language for web and graphic designers to learn. Short for Cascading Style Sheet, CSS is the language used to add style to HTML elements.<span class="inline">inline1</span> <span class="inline">inline2</span>If you are unfamiliar with HTML you will have little use for CSS, so I recommend you begin reading my HTML Lessons.
</div>
<h2>inline-block display</h2>
<div>
So you want to learn CSS? Great decision; CSS is the language for web and graphic designers to learn. Short for Cascading Style Sheet, CSS is the language used to add style to HTML elements.<span class="inline-block">inline-block1</span> <span class="inline-block">inline-block2</span>If you are unfamiliar with HTML you will have little use for CSS, so I recommend you begin reading my HTML Lessons.
</div>
<h2>block display</h2>
So you want to learn CSS? Great decision; CSS is the language for web and graphic designers to learn. Short for Cascading Style Sheet, CSS is the language used to add style to HTML elements.<span class="block">block1</span> <span class="block">block2</span>If you are unfamiliar with HTML you will have little use for CSS, so I recommend you begin reading my HTML Lessons.
</div>

</body>
</head>
</html>

结果如下所示:

CSS 盒子模型

关于 CSS 的盒子模型,其实上面的介绍中或多或少都有所涉及到。盒子模型如下图所示:

所有的 HTML 元素都可以看做是盒子,盒子内又分为:Content(盒子的内容用于显示文本和图片)、Padding(盒子的内边框围绕着盒子的内容,但内边框是透明的)、Border(盒子的边框围绕着内容和内边框)、Margin(外边框围绕着边框,跟 Padding 一样,它也是透明的)。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<style>
div {
width: 200px;
height: 100px;
margin: 20px;
border: 3px solid #4c75a3;
padding: 30px;
}
</style>
<body>
<h2>Box Model Demo</h2>
<div>
width: 200px;height: 100px;margin: 20px;border: 3px solid #4c75a3;padding: 30px;
</div>
</body>
</head>
</html>

页面显示如下所示:

另外,关于盒子模型,我还想补充下 outline(轮廓)属性:outline-style(轮廓样式)、outline-color(轮廓颜色)、outline-width(轮廓宽度)、outline-offset(轮廓偏移量,即指定偏移元素的偏移量)等。接上一例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<style>
div {
width: 200px;
height: 100px;
margin: 20px;
border: 3px solid #4c75a3;
padding: 30px;
outline-width: 3px;
outline-style: solid;
outline-color: red;
}
</style>
<body>
<h2>Box Model Demo</h2>
<div>
width: 200px;height: 100px;margin: 20px;border: 3px solid #4c75a3;padding: 30px;
</div>
</body>
</head>
</html>

页面显示效果如下所示:

CSS 选择器

元素选择器

元素选择器是以 HTML 的标签元素命名,其实,文档的元素本身也是选择器。看下面的例子你的印象会更深刻:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<style>
html {
color: red;
}
h2 {
color: green;
}
</style>
</head>
<body>
<h1>This is head1</h1>
<h2>This is head2</h2>
<p>This is a paragraph.</p>
</body>
</html>

显示效果如下图所示:

从上图元素选择器渲染的效果我们可以知道:html 父元素可以正常渲染子元素 h1 和 p,但是由于 h2 是已经定义的元素选择器,所以子元素 h2 会覆盖父元素 html 的选择器渲染效果。即:子元素中的元素选择器会覆盖父元素的元素选择器,类似于编程语言中局部变量会覆盖全局变量的意思。

选择器分组

我们可以对选择器进行分组,实现有针对性的样式渲染。比如我们只想对 h2 和 p 设置成蓝色,那么可以这么写选择器:

1
2
3
h2, p {
color: blue;
}

其实还有通配符的选择器,如:

1
2
3
* {
color: blue;
}

这样声明的选择器,会对所以 html 元素起作用。

类选择器

元素选择器只对指定的元素起作用,但是类选择器只要某个元素通过 class 属性指定了该类选择器,那么类选择器对该元素就起作用。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<style>
. class-selector {
color: green;
}
</style>
</head>
<body>
<h2>Class Selector Demo</h2>
<p class="class-selector">with class selector</p>
<p>without class selector</p>
</body>
</html>

以上的例子对于指定了类选择器的 p 的文本就会是绿色,但是未指定类选择器的 p 就不会是绿色。这样就可以实现更加灵活的控制。

其实类选择器结合指定的元素可以实现更精细化的渲染控制,举个例子,我想要指定段落 p 进行渲染控制,那么可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
p.render {
color: green;
}
</style>
</head>
<body>
<h2>Element Class Selector Demo</h2>
<p class="render">with p class selector</p>
<p>without p class selector</p>
<div class="render">div use p class selector,it doesn't work</div>
</body>
</html>

以上的例子对于指定了类选择器的 p 的文本就会是绿色,但是未指定类选择器的 p 就不会是绿色,尽管 div 也指定了该选择器,但是因为该选择器只对元素 p 有效,所以 div 也不起作用。

ID 选择器

ID 选择器的名称以 # 开始,如果一个 HTML 文档中有多个相同的 ID,那肯定会引起不必要的麻烦,所以在使用 ID 选择器的时候强烈建议只使用一次,例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<style>
#render {
color: green;
}
</style>
</head>
<body>
<h2>ID Selector Demo</h2>
<p id="render">with id selector</p>
<p>without id selector</p>
</body>
</html>

属性选择器

属性选择器可以根据元素的属性及属性值来选择元素。通过 “*[属性名]” 来定义的属性选择器,只要某个元素中含有该属性就会生效;通过“标签名[属性名][属性名]”来定义的属性选择器,只会满足该标签的属性的元素生效。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<style>
a[href][title] {
color: yellow;
}
</style>
</head>
<body>
<h2>property Selector Demo</h2>
<a href="www.baidu.com" title="百度一下">百度一下</a>
<a href="www.baidu.com">百度一下</a>
</body>
</html>

在以上例子中没有 title 属性的链接标签就不会对改属性选择器生效。

后代选择器

后代选择器只会对满足后代规则的元素其作用,如:

1
2
3
div p {
color: red;
}

以上的后代选择器只会对 div 下 的 p 起作用,而对于不是 div 下的 p 不起作用,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<style>
div p {
color: red;
}
</style>
</head>
<body>
<h2>Descendant Selector Demo</h2>
<div>
<p>match descendant selector</p>
</div>
<div>
<section>
<p>match descendant selector</p>
</section>
</div>
<p>doesn't match descendant selector</p>
</body>
</html>

注意:针对上面的例子,div 下 的 p 会生效,div 下的 xxx(任意元素)下的 p 还是会生效。

子元素选择器

子元素选择器相对后代选择器,可以实现更精准的匹配,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<style>
div > p {
color: red;
}
</style>
</head>
<body>
<h2>Child Selector Demo</h2>
<div>
<p>match Child selector</p>
</div>
<div>
<section>
<p>doesn't match Child selector</p>
</section>
</div>
<p>doesn't match Child selector</p>
</body>
</html>

这个时候 div 下的 xxx(任意元素)下的 p 就不会生效了。

相邻兄弟选择器

如果需要选择紧接在另一个元素后的元素,而且二者有相同的父元素,那么可以使用相邻兄弟选择器,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
h2 + p {
color: red;
}
</style>
</head>
<body>
<h2>Adjacent Sibling Selector Demo</h2>
<p>match adjacent sibling selector</p>
<p>doesn't match adjacent sibling selector</p>
<p>doesn't match adjacent sibling selector</p>
</body>
</html>

伪类

前面文章中有提到过链接标签 a 有四个状态:a:link、a:visited、a:hover、a:active,其实这就是伪类,这里不再赘述。

这里重点介绍下伪类 first-child ,包括他它的三种使用场景:元素的 first-child(如:“p:first-child”)、子元素的 first-child(如:“p > i:first-child”)、后代元素的 first-child(如:“p:first-child i”),以下结合例子说明。

p:first-child

这个写法的意思是 p 标签作为子元素,匹配作为子元素 p 的首个 p,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<style>
p:first-child {
color: red;
}
</style>
</head>
<body>
<div>
<p>match p:first-child</p>
<p>doesn't match p:first-child</p>
<p>doesn't match p:first-child</p>
</div>
</body>
</html>

p > i:first-child

对于这个写法可以把 p > i 看成一个整体,然后将其作为子元素,匹配作为子元素 p > i 的首个 p > i,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
p > i:first-child {
color: red;
}
</style>
</head>
<body>
<div>
<p>paragraph <i>match</i>paragraph <i>doesn't match</i></p>
<p>paragraph <i>doesn't match</i>paragraph <i>doesn't match</i></p>
</div>
</body>
</html>

p:first-child i

对于这个写法,可以将其分两部分来理解:前半部分 p:first-child,先匹配作为子元素 p 的首个 p;后半部分是后代选择器 i,作为后代选择器会匹配所以的 i,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
p:first-child i {
color: red;
}
</style>
</head>
<body>
<div>
<p>paragraph <i>match</i>paragraph <i>match</i></p>
<p>paragraph <i>doesn't match</i>paragraph <i>doesn't match</i></p>
</div>
</body>
</html>

伪元素

CSS 的伪元素用于向某些选择器设置特殊效果

对于 :first-letter

向文本的第一个字母添加特殊样式,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
p:first-letter {
color: red;
}
</style>
</head>
<body>
<div>
<p>paragraph1</p>
<p>paragraph2</p>
</div>
</body>
</html>

那么 paragraph1 和 paragraph2 的首字母 p 会为红色。

对于 :first-line

向文本的首行添加特殊样式,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<style>
p:first-line {
color: red;
}
</style>
</head>
<body>
<div>
<p>
line1<br />
line2<br />
line3<br />
</p>
</div>
</body>
</html>

那么 line1 为红色。

对于 :before

在元素之前添加内容,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<style>
p:before {
content: "before content";
color: red;
}
</style>
</head>
<body>
<div>
<p>paragraph1</p>
<p>paragraph2</p>
</div>
</body>
</html>

那么 paragraph1 和 paragraph2 的前面都会加上红色的 before content 字符串。

对于 :after

在元素之后添加内容,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<style>
p:after {
content: "before content";
color: red;
}
</style>
</head>
<body>
<div>
<p>paragraph1</p>
<p>paragraph2</p>
</div>
</body>
</html>

那么 paragraph1 和 paragraph2 的后面都会加上红色的 before content 字符串。

HTML 知识总结

Posted on 2019-05-03 | Post modified: 2019-06-03   |   post.updated 2019-06-03 | In Web 前端 |

HTML 简介

HTML 的全称是超文本标记语言(HyperText Markup Language),它不是编程语言,而是标记语言。Web 前端的核心知识包括 HTML、CSS 和 JavaScript,HTML 是前端的骨架,它定义了网页内容的含义和结构,如果类比一座房子,HTML 就是砖瓦、钢筋混泥土;CSS 的全称叫层叠样式表(cascading style sheet),通常用来描述一个网页的表现与展示效果,好比一座房子的外观样式;JavaScript 主要用来实现网页的功能与行为。这些内容我都会在后面逐一介绍。

光说不干都是扯淡,我们来看下面的结合了 HTML + CSS + JavaScript 的小栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<title>demo</title>
<style type="text/css">
p {color: blue; }
</style>
<script type="text/javascript">
function myFunction(){document.write("Hello World.");}
</script>
</head>
<body>
<h1 align="center">This is a heading 1</h1>
<p>This is a paragraph.</p>
<button type="button" onclick="myFunction()">click me</button>
</body>
</html>

将以上代码使用 txt 等编辑器保存为 html 文件格式的文件,然后用浏览器打来即可看到效果。对以上代码的简要解释:!DOCTYPE html 是 HTML5 的声明,它已经是新一代标准;<html>…</html>、<head>…</head>、<body>…</body>、<titile>…</title>、<h1>…</h1>、<p>…</p>、<button></button>等是 html 标签,一般都是成对出现,一个是开始标签,一个是结束标签,如果你将这些标签的含义套用到写文章上就比较好理解了;<stype>…</stype> 标签下定义了一个 p 标签的元素选择器,用来修饰段落的,让段落的颜色显示蓝色;<script>…</script> 标签嵌入了一段 JavaScript 代码,逻辑就是向网页输出 Hello World;再看看 <button>…</button> 标签,基本逻辑就是点击了 click me 按钮就会触发 JavaScript 代码,输出 Hello World。

HTML 常用元素总结

上一节已经对 HTML 作了简要介绍,这一节再对 HTML 进行更详细的解释。“HTML 不是编程语言而是标记语言”,相信通过上面的例子对这句话理解更深刻了。以下对 HTML 常用的元素及属性进行总结。

<html> 元素

<html> 元素定义了整个 HTML 文档,每个 HTML 文档都应该包含该元素。

<head> 元素

<head> 元素是所有头部元素的容器。<head> 内的元素可包含脚本,指示浏览器在何处可以找到样式表,提供元信息,等等。head 标签下通常会有 meta 标签、title 标签、link/style 标签和 script 标签,在编写 HTML 的 head 部分时建议也是按照这个顺序来编写。因为,meta 最好写在第一个,特别是 meta chaset 设定必须写第一个,它们是元数据,能让数据获取方得知此页面的元数据;title 紧随 meta 之后是因为可能获取方需要知道页面标题信息,并且在最先获取 chaset 信息后知道此标题的编码方式;link、style 紧随 meta,其实还是主要为 link 大部分都是 CSS 样式文件考虑;script 放在最后,是基于script 不光是下载还是执行都会阻塞页面考虑,让它尽量偏后。

<meta> 标签

meta 标签是 HTML 中 head 头部的一个辅助性标签,它位于 HTML 文档头部的 head 和 title 标记之间,它提供用户不可见的信息。虽然这部分信息用户不可见,但是其作用非常强大,特别是当今的前端开发工作中,设置合适的 meta 标签可以大大提升网站页面的可用性。比如在桌面端开发中,meta 标签通常用来为搜索引擎优化(SEO)及 robots 定义页面主题,或者是定义用户浏览器上的 cookie;它可以用于鉴别作者,设定页面格式,标注内容提要和关键字;还可以设置页面使其可以根据你定义的时间间隔刷新自己,以及设置 RASC 内容等级,等等。以下是它的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<!--声明文档使用的字符编码-->
<meta charset="utf-8">
<!--简体中文生声明-->
<!-- Web 应用的名称(仅当网站被用作为一个应用时才使用)-->
<meta name="application-name" content="应用名称">
<!-- 在客户端存储 cookie,web 浏览器的客户端识别 -->
<title>页面标题</title>>
...
</head>
...
</html>

meta 标签根据属性的不同,可分为两大部分:http-equiv 和 name 属性。http-equiv:相当于 http 的文件头作用,它可以向浏览器传回一些有用的信息,以帮助浏览器正确地显示网页内容;name 属性:主要用于描述网页,与之对应的属性值为 content,content 中的内容主要是便于浏览器,搜索引擎等机器人识别,等等。

<title> 元素

title 标签定义文档的标题。其实该字段还会对 SEO 有帮助,建议不要缺省。

样式表

样式表有三种,分别为:外部样式表、内部样式表和内联样式表。当 HTML 代码中如果涉及三种不同的样式表时,它们的优先级是这样的:外部样式表 < 内部样式表 < 内联样式表。

外部样式表

外部样式表如下所示,使用 link 标签链接外部 css 资源。

1
2
3
<head>
<link rel="stylesheet" type="text/css" href="mystyle.css">
</head>

内部样式表

内部样式表如下所示,在 head 标签内部声明并定义 css 样式。

1
2
3
4
5
6
<head>
<style type="text/css">
body {background-color: red}
p {margin-left: 20px}
</style>
</head>

内联样式

内联样式表如下所示,在用到某个元素标签时以元素的属性的形式定义显示样式。

1
2
3
<p style="color: red; margin-left: 20px">
This is a paragraph
</p>

<script> 元素

script 标签用于定义客户端脚本,比如 JavaScript。script 元素既可包含脚本语句,也可通过 src 属性指向外部脚本文件。必需的 type 属性规定脚本的 MIME 类型 JavaScript 最常用于图片操作、表单验证以及内容动态更新。

通过 src 属性指向外部脚本文件的例子:

1
<script src="./static/demo.js"></script>

script 标签包含脚本语句的例子(在 HTML5 中 type 属性可缺省):

1
2
3
<script type="text/javascript">
document.write("Hello World!")
</script>

<body> 元素

body 元素定义了 HTML 文档的主体。在 body 元素下面的元素及属性等就很多了,这里只介绍常用的。

<h1> - <h6> 元素

h1 到 h6 字体会逐渐变小,值得注意的是,在使用的时候不要觉得它有放大文字的功能就在需要放大字体的场景下去使用 h1 等标签,h1 等的使用场景还是需要跟语义保持一致。简单示例如下(由于比较简单,显示效果就不放上了):

1
2
3
<h1>This is a heading 1</h1>
<h2>This is a heading 2</h2>
<h3>This is a heading 3</h3>

<p> 元素

p 标签是 paragraph 的缩写,是段落的意思,还是那句话,使用场景需要跟语义保持一致,需要用段落去排版的时候就用这个标签。简单示例代码如下所示:

1
2
<p>This is a paragraph.</p>
<p>This is another paragraph.</p>

<a> 元素

a 元素主要用作超链接,属性 href 中指定 url,然后在内容框中输入要显示的文字即可:

1
<a href="http://www.w3school.com.cn">This is a link</a>

<img> 元素

img 标签用于显示图片, src 属性指定图片的路径。需要注意的是 img 标签不需要结束标签,使用的时候不要忘了在最后面加上结束符号 ‘/‘,示例如下所示:

1
<img src="w3school.jpg" width="104" height="142" />

<br /> 元素

br 标签用于换行,跟 img 标签一样没有借宿标签,使用的时候不要忘了在最后面加上结束符号 ‘/‘。

style 属性

style 属性用于改变 HTML 元素的样式。不局限于在 body 标签下使用,凡是 html 标签均可。示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<!--整个 html 页面背景设置为黄色-->
<body style="background-color:yellow">
<!--h2 背景设置为红色-->
<h2 style="background-color:red">This is a heading</h2>
<!--段落 p 背景设置为绿色-->
<p style="background-color:green">This is a paragraph.</p>
<!--h1 字体设置为 verdana-->
<h1 style="font-family:verdana">A heading</h1>
<!--段落 p 字体为 arial,颜色红色,字体大小为 20 个像素-->
<p style="font-family:arial;color:red;font-size:20px;">A paragraph.</p>
<!--h1 中心对齐-->
<h1 style="text-align:center">This is a heading</h1>

html 文本格式化

直接看例子就会用,代码:

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
<!DOCTYPE html>
<html>
<head>
<title>formattings</title>
</head>
<body>
<b>定义粗体文本。</b>
<br />
<big>定义大号字。</big>
<br />
<em>定义着重文字。</em>
<br />
<i>定义斜体字。</i>
<br />
<small>定义小号字。</small>
<br />
<strong>定义加重语气。</strong>
<br />
<sub>定义下标字。</sub>
<br />
<sup>定义上标字。</sup>
<br />
<ins>定义插入字。</ins>
<br />
<del>定义删除字。</del>
<br />
</body>
</html>

效果图:

Bgcolor 属性

背景颜色(Bgcolor)属性将背景设置为某种颜色。属性值可以是十六进制数、RGB 值或颜色名。如:

1
2
3
<body bgcolor="#000000">
<body bgcolor="rgb(0,0,0)">
<body bgcolor="black">

Backgrounds 属性

背景(Background)属性将背景设置为图像。属性值为图像的 URL。如果图像尺寸小于浏览器窗口,那么图像将在整个浏览器窗口进行复制。

1
2
<body background="clt.jpg">
<body background="http://www.chenliangtang.top/clt.jpg">

URL 可以是相对地址,如第一行代码。也可以是绝对地址,如第二行代码。

HTML 表格

html 表格使用 table (表格)标签定义,表格的字段名称或者列名称用 th (表格头部)定义,表格的行用 tr (表格行)表示,每行的列用 td (表格数据)表示,简单示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<title>table</title>
</head>
<body>
<table border="1">
<tr>
<th>heading 1</th>
<th>heading 2</th>
</tr>
<tr>
<td>row 1,cell 1</td>
<td>row 1,cell 2</td>
</tr>
<tr>
<td></td>
<td>row 2,cell 2</td>
</tr>
</table>
</body>
</html>

效果图如下所示:

HTML 列表

html 列表常用的有有序列表 ol,无序列表 ul,li 是具体的某项列表,它们支持嵌套,简单的代码示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<title>list</title>
</head>
<body>
<h4>biology</h4>
<ul>
<li>monkey</li>
<li>orangutan</li>
<li>baboon</li>
<li>human</li>
<ol type="a">
<li>man</li>
<li>woman</li>
</ol>
</ul>
</body>
</html>

效果如下所示:

HTML 块
<div>

div 是一个块级元素,可以包含段落,表格等内容,用于放置不同的内容。一般我们在网页通过div来布局定位网页中的每个区块。同是块级元素的还有 <h1>, <p>, <ul>, <table> 等。

<span>

span 是一个内联元素,没有实际意义,它的存在纯粹是为了应用样式,给一段内容加上 <span></span> 标记可以通过在 span 上定义样式来设定其内容的样式。同是内联元素的还有 <b>, <td>, <a>, <img> 等。

栗子
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
<!DOCTYPE html>
<html>
<head>
<title>the difference between div and span</title>
</head>
<body>
<div style="float:left">
<h3>This is a header.</h3>
<p>This is a paragraph.</p>
</div>
<div style="float:left;display:inline">
<h3>This is a header.</h3>
<p>This is a paragraph.</p>
</div>
<div style="float:left;display:block">
<h3>This is a header.</h3>
<p>This is a paragraph.</p>
</div>
<br />
<span style="display:block">span style is block display</span>
<span>span style is default(inline).</span>
<span style="display:inline">span style is inline display.</span>
<span>span style is default(inline).</span>
</body>
</html>

效果如下所示:

HTML 表单

HTML 表单用于收集用户输入,整个表单的结构由 form 标签包裹着。表单的输入多数情况下被用到的表单标签是输入标签 input,输入类型是由类型属性 type 定义的,多数经常被用到的输入类型如下:文本域(Text Fields),当用户要在表单中键入字母、数字等内容时,就会用到文本域;单选按钮(Radio Buttons);复选框(Checkboxes);表单的动作属性(Action)和确认按钮。当用户单击确认按钮时,表单的内容会被传送到另一个文件。表单的动作属性定义了目的文件的文件名。由动作属性定义的这个文件通常会对接收到的输入数据进行相关的处理。可以通过下面的例子感受感受:

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<html lang="zh-cmn-Hans">
<meta name="application-name" content="form app">
<title>form demo</title>
</head>
<body>
<h3>个人基本信息</h3>

<form action=“html5.html” method=“post”> <!---定义采用 post 方式向 html5.html 文件发送表单数据-->
<br />
GitHub 账号
<br />
<table> <!--用表格控制输入框的对齐-->
<tr>
<td>用户名:</td>
<td><input type="text"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password"></td>
</tr>
</table>
你的爱好
<br />
看书<input type="checkbox"> <!--复选框-->
旅游<input type="checkbox">
写作<input type="checkbox">
看电影<input type="checkbox">
<br />
请选择您的性别:
男<input type="radio" name="sex"> <!--单选框-->
女<input type="radio" name="sex">
保密<input type="radio" name="sex">
<br />
请选择您常用的网站
<select> <!--下拉列表-->
<option>www.baidu.com</option>
<option>www.google.com</option>
<option>www.github.com</option>
</select>
<br />
个人简介:<textarea name="text" rows="5" cols="30"></textarea> <!--文本域-->
<br />
<br />
<input type="submit" value="提交"> <!--提交表单到 html5.html 文件中-->
</form>
</body>
</html>

效果如下图所示:

当然,HTML 表单的内容远远不止这些,但用法都大同小异,这里就不一一展开了,感兴趣的同学可以再查找相关资料学习学习。

HTML5 语义化与布局

H5 语义元素的页面大致结构如下图所示:

header 标签通常放在页面或页面某个区域的顶部,用来设置页眉;nav 标签可以用来定义导航链接的集合,点击链接可以跳转到其他页面;article 标签中的内容比较独立,可以是一篇新闻报道,一篇博客,它可以独立于页面的其他内容进行阅读;section 标签表示页面中的一个区域,通常对页面进行分块或对内容进行分段,section 标签和 article 标签可以互相嵌套;aside 标签用来表示除页面主要内容之外的内容,比如侧边栏;footer 标签位于页面或页面某个区域的底部,用来设置页脚,通常包含版权信息,联系方式等。以下通过个例子看看:

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
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8">
<html lang="zh-cmn-Hans">
<meta name="application-name" content="form app">
<title>HTML5 语义化</title>
</head>
<body>
<header>
<h1>How to learn Front-end</h1>
<p>You need to learn HTML、CSS、JavaScript first.</p>
</header>

<nav>
<a href="/html/">HTML</a> |
<a href="/css/">CSS</a> |
<a href="/js/">JavaScript</a> |
</nav>

<section>
<h1>What is HTML</h1>
<p>Hypertext Markup Language (HTML) is the standard markup language for creating web pages and web applications. With Cascading Style Sheets (CSS) and JavaScript, it forms a triad of cornerstone technologies for the World Wide Web.</p>
</section>

<article>
<h1>What is CSS</h1>
<p>Cascading Style Sheets (CSS) is a style sheet language used for describing the presentation of a document written in a markup language like HTML.[1] CSS is a cornerstone technology of the World Wide Web, alongside HTML and JavaScript.</p>
</article>

<aside>
<h1>What is JavaScript</h1>
<p>Javascript is a simple programming language built into Netscape 2.0 and greater. It is integrated with and embedded in HTML. It allows greater control of web page behavior than does HTML alone. Since the Javascript interpreter is part of Netscape, it is platform-independent: Javascript incorporated into HTML runs on Windows, Macintosh, and other Netscape-supported systems.</p>
</aside>

<footer>
<p>Posted by: ChenLiangtang</p>
<p>Contact information: <a href="709320543@qq.com">
709320543@qq.com</a>.</p>
</footer>

</body>
</html>

效果如下图所示:

HTML 响应式 Web 设计

响应式 Web 设计的目的就是让内容布局能随用户使用显示器的不同而变化,我们看看下面的例子:

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
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8">
<meta name="application-name" content="web app">
<title>Web 响应式设计</title>
<style> <!--样式向左浮动-->
.front-end {
float: left;
margin: 5px;
padding: 15px;
width: 300px;
height: 300px;
border: 1px solid black;
}
</style>
</head>


<body>

<h1>Responsive web design</h1>
<br>

<div class="front-end">
<h2>HTML</h2>
<p>Hypertext Markup Language (HTML) is the standard markup language for creating web pages and web applications. With Cascading Style Sheets (CSS) and JavaScript, it forms a triad of cornerstone technologies for the World Wide Web.</p>
</div>

<div class="front-end">
<h2>CSS</h2>
<p>Cascading Style Sheets (CSS) is a style sheet language used for describing the presentation of a document written in a markup language like HTML.[1] CSS is a cornerstone technology of the World Wide Web, alongside HTML and JavaScript.</p>
</div>

<div class="front-end">
<h2>JavaScript</h2>
<p>Javascript is a simple programming language built into Netscape 2.0 and greater. It is integrated with and embedded in HTML. It allows greater control of web page behavior than does HTML alone. Since the Javascript interpreter is part of Netscape, it is platform-independent: Javascript incorporated into HTML runs on Windows, Macintosh, and other Netscape-supported systems.</p>
</div>

</body>
</html>

直接看下面的两个效果图就能明白什么是 Web 响应式设计了:

HTML(5) 代码规范

请使用正确的文档类型

请始终在文档的首行声明文档类型:

1
<!DOCTYPE html>

不建议用小写:

1
<!doctype html>

请使用小写元素名

HTML5 允许在元素名中使用混合大小写字母,但是建议全部用小写,如:

1
2
3
<section> 
<p>This is a paragraph.</p>
</section>

不要这样:

1
2
3
<SECTION> 
<p>This is a paragraph.</p>
</SECTION>

也不要这样:

1
2
3
<Section> 
<p>This is a paragraph.</p>
</SECTION>

关闭所有 HTML 元素

在 HTML5 中,可以不用关闭所有元素(例如 p 元素),但建议关闭所有 HTML 元素,如:

1
2
3
4
<section>
<p>This is a paragraph.</p>
<p>This is a paragraph.</p>
</section>

1
<meta charset="utf-8" />

不要这样:

1
2
3
4
<section>
<p>This is a paragraph.
<p>This is a paragraph.
</section>

使用小写属性名

HTML5 允许大小写混合的属性名,但建议使用小写属性名,同时建议属性值都加上引号,避免当属性值有空格时出错,如:

1
<div class="menu">

1
<table class="table striped">

不要这样:

1
<div CLASS="menu">

也不要这样:

1
<table class=table striped>

必需的属性

请始终对图像使用 alt 属性。当图像无法显示时该属性很重要,同时也请始终定义图像尺寸。这样做会减少闪烁,因为浏览器会在图像加载之前为图像预留空间,如:

1
<img src="html5.gif" alt="HTML5" style="width:128px;height:128px">

空格和等号

等号两边的空格是合法的,但是精简空格更易阅读,建议这样:

1
<link rel="stylesheet" href="styles.css">

不建议这样:

1
<link rel = "stylesheet" href = "styles.css">

避免长代码行

当使用 HTML 编辑器时,通过左右滚动来阅读 HTML 代码很不方便,请尽量避免代码行超过 80 个字符。

空行和缩进

请勿毫无理由地增加空行。为了提高可读性,请增加空行来分隔大型或逻辑代码块。为了提高可读性,请增加两个空格的缩进。请勿使用 TAB。请勿使用没有必要的空行和缩进。没有必要在短的和相关项目之间使用空行,也没有必要缩进每个元素。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>

<h1>Famous Cities</1>
<h2>Tokyo</h2>

<p>
Tokyo is the capital of Japan, the center of the Greater Tokyo Area,
and the most populous metropolitan area in the world.
It is the seat of the Japanese government and the Imperial Palace,
and the home of the Japanese Imperial Family.
</p>

</body>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<table>
<tr>
<th>Name</th>
<th>Description</th>
<tr>
<tr>
<td>A</td>
<td>Description of A</td>
<tr>
<tr>
<td>B</td>
<td>Description of B</td>
<tr>
</table>
</p>

</body>

太简短了没必要这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>

<h1>Famous Cities</1>

<h2>Tokyo</h2>

<p>
Tokyo is the capital of Japan, the center of the Greater Tokyo Area,
and the most populous metropolitan area in the world.
It is the seat of the Japanese government and the Imperial Palace,
and the home of the Japanese Imperial Family.
</p>

</body>

省略 <html> 和 <body>?

在 HTML5 标准中,能够省略 <html> 标签和 <body> 标签,但不推荐省略 <html> 和 <body> 标签。

省略 <head>?

在 HTML5 标准中,<head> 标签也能够被省略。默认地,浏览器会把 <body> 之前的所有元素添加到默认的 <head> 元素,但省略标签的做法是陌生的,不建议省略。

元数据

title 元素在 HTML5 中是必需的。请尽可能制作有意义的标题,如:

1
<title>HTML5 Syntax and Coding Style</title>

为了确保恰当的解释,以及正确的搜索引擎索引,在文档中对语言和字符编码的定义越早越好,如:

1
2
3
4
5
6
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>HTML5 Syntax and Coding Style</title>
</head>

HTML 注释

短注释应该在单行中书写,并在 在独立的行中书写:

1
2
3
4
<!-- 
This is a long comment example. This is a long comment example. This is a long comment example.
This is a long comment example. This is a long comment example. This is a long comment example.
-->

如果它们被缩进两个空格的话,长注释更易观察。

样式表

请使用简单的语法来链接样式表(type 属性不是必需的),如:

1
<link rel="stylesheet" href="styles.css">

短规则可以压缩为一行,就像这样:

1
p.into {font-family:"Verdana"; font-size:16em;}

长规则应该分为多行:

1
2
3
4
5
6
body {
background-color: lightgrey;
font-family: "Arial Black", Helvetica, sans-serif;
font-size: 16em;
color: black;
}

开括号与选择器位于同一行,在开括号之前用一个空格;使用两个字符的缩进;在每个属性与其值之间使用冒号加一个空格;在每个逗号或分号之后使用空格;在每个属性值对(包括最后一个)之后使用分号;只在值包含空格时使用引号来包围值;把闭括号放在新的一行;之前不用空格;避免每行超过 80 个字符;在逗号或分号之后添加空格,是所有书写类型的通用规则。

在 HTML 中加载 JavaScript

请使用简单的语法来加载外部脚本(type 属性不是必需的),如:

1
<script src="myscript.js">

使用“不整洁”的 HTML 样式的后果,是可能会导致 JavaScript 错误。

使用小写文件名

大多数 web 服务器(Apache、Unix)对文件名的大小写敏感,如不能以 london.jpg 访问 London.jpg。为了避免这些问题,请始终使用小写文件名。

文件扩展名

HTML 文件名应该使用扩展名 .html(而不是 .htm);CSS 文件应该使用扩展名 .css;JavaScript 文件应该使用扩展名 .js。

备注:以上的 HTML 代码有缩进的我都是只缩进两个空格,不知为何显示出来的就是一个 Tab 了,很无奈。。。

容器化部署 Springboot 应用

Posted on 2019-05-02 | Post modified: 2019-05-03   |   post.updated 2019-05-03 | In Java |

CentOS7 上安装 JDK8

这些年来 Docker 是越来越流行了,企业的环境部署也普遍都容器化了,虽说 Docker 当前也支持在 Windows 上安装,但真正的线上环境估计没有人选用 Windows 作为容器化平台,所以我们还是选择在 Linux 上容器化部署应用。既然 Springboot 应用基于 Java 来开发,那就先在 Linux 上安装 JDK 吧。这里有个小问题值得注意下,我们知道 Linux 下都有在线安装软件工具,比如 CentOS 的 yum,如果 JDK 也选择在线安装,那么一般安装下来的是 OpenJDK,这个跟 Oracle JDK 还是有些区别的,所以强烈建议还是到 Oracle 官网上去下载 JDK 版本。

我这里还是安装 JDK8,首先到JDK8 下载地址找到 jdk-8u152-linux-x64 版本,然后下载(需要注册账号才可以下载,那就注册个账号吧)。

我已经预先安装好了 CentOS7 虚拟机,关于 CentOS 的安装我就不在这里介绍了,感兴趣的同学可以查找相关资料去学习。我这里选用 xshell 作为 ssh 工具,用 xftp 作为 Windows 与 Linux 之间的文件传输工具。先将下载下来的软件包 jdk-8u152-linux-x64.tar.gz 上传到 /root/目录下:

1
2
3
4
5
# pwd
/root

# ll jdk-8u152-linux-x64.tar.gz
-rw-r--r--. 1 root root 189784266 5月 3 12:08 jdk-8u152-linux-x64.tar.gz

然后创建 JDK 的安装目录(在 Linux 下一般都将第三方软件安装到 /usr/local/目录下):

1
# mkdir -p /usr/local/java

接着将软件包解压到 /usr/local/java 目录下:

1
# tar -xzvf jdk-8u152-linux-x64.tar.gz -C /usr/local/java/

进入 /usr/local/java/ 目录,查看解压是否成功了:

1
2
3
4
5
6
7
8
9
# cd /usr/local/java/

# ls
jdk1.8.0_152

# cd jdk1.8.0_152/

# ls
bin COPYRIGHT db include javafx-src.zip jre lib LICENSE man README.html release src.zip THIRDPARTYLICENSEREADME-JAVAFX.txt THIRDPARTYLICENSEREADME.txt

解压成功了,这里解压后的文件就是可执行文件了,不用再编译、安装(configure、 make、 make install),接下来配置下环境变量就可以了。打开 CentOS 下的系统配置文件 /etc/profile:

1
# vi /etc/profile

在文末追加以下内容:

1
2
3
4
export JAVA_HOME=/usr/local/java/jdk1.8.0_152
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

然后保存文件,执行 source 命令使其生效:

1
# source /etc/profile

最后验证下是否安装成功了:

1
2
3
4
# java -version
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

OK 了。

CentOS7 上安装 Docker

在 CentOS 上安装 Docker,我们可以使用 yum 工具在线安装,先为 yum 增加安装 Docker 的国内源,这样会使安装过程快点:

1
# yum-config-manager --add-repo https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo

然后安装一些 Docker 依赖包:

1
# yum install -y yum-utils device-mapper-persistent-data lvm2

接着更新 yum 软件源缓存并安装 Docker:

1
2
3
# yum -y makecache fast

# yum -y install docker-ce

启动 Docker

1
2
3
# systemctl enable docker

# systemctl start docker

建立 docker 用户组,不然当前用户下可能无法使用 Docker:

1
2
3
# groupadd docker

# usermod -aG docker $USER

好了,至此应该可以正常使用 Docker 了,为保万无一失,我们拉取个 nginx 镜像验证下吧:

1
2
3
4
5
6
7
8
9
10
11
# docker pull nginx:latest
latest: Pulling from library/nginx
27833a3ba0a5: Pull complete
ea005e36e544: Pull complete
d172c7f0578d: Pull complete
Digest: sha256:edeeacc4b2605cfbdc5b9e4dc502e3269c8346aa4d257d2fab74dccfc0f7b972
Status: Downloaded newer image for nginx:latest

# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 27a188018e18 2 weeks ago 109MB

启动 Docker 容器(docker run 命令用来创建容器,–name 参数是给这个容器起个名字,-d 是让容器在后台运行,-p 是指定容器的访问端口,其中 9999 是对外访问的接口, 80 是 nginx 容器的内部端口, 最后的参数就是指定镜像名称):

1
2
3
# docker run --name nginxtest -d -p 9999:80 nginx
WARNING: IPv4 forwarding is disabled. Networking will not work.
03e6aabe8a0957cb547e41a183161a11f8feceec87f5b0e601c17bcfa9adeb45

用命令 docker ps 查看容器是否起来了:

1
2
3
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e6aabe8a09 nginx "nginx -g 'daemon of…" 5 seconds ago Up 2 seconds 0.0.0.0:9999->80/tcp nginxtest

好,那我们访问下 nginx 首页吧:

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
# curl 192.168.18.128:9999/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

完美。

编写 Dockerfile

接着上篇 使用 Springboot 编写 Helloworld 应用 我们完成了使用 Springboot 编写 Helloworld 应用的过程,接下来我们要基于这个简单的应用制作 Dockerfile 文件,以构建 Helloworld 应用的镜像,实现容器化部署。

先将 hellospringboot 工程拷贝到 CentOS7 机器上来,然后用 gradlew 打包应用:

1
2
3
# chmod + gradlew

# ./gradlew build

这时,在 ./build/libs/ 目录下就可以看到打包好了的 jar 包了,这时候运行命令:

1
# java -jar ./build/libs/hellospringboot-0.0.1-SNAPSHOT.jar

应用就可以跑起来了,访问 ip:8080 就可以看到有 Helloworld 返回了。但是这还不是我们想要的,我们要实现容器化部署。于是我编写了 Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM java:8-jre

MAINTAINER <chenliangtang>

COPY ./build/libs/* docker-entrypoint.sh /

RUN chmod +x docker-entrypoint.sh && ln -s /docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

WORKDIR /

EXPOSE 8080

CMD ["docker-entrypoint.sh"]

其中, docker-entrypoint.sh shell 脚本内容如下所示:

1
2
3
4
5
#!/bin/bash

jarFile=$(ls . | grep jar)

java -jar $jarFile

对 Dockerfile 内容的解释:其中 Dockerfile 这个名称不能变,就如 Makefile 文件是 make 编译的文件说明一样,这是规范;FROM 这行表示我构建的镜像基础是 jdk8,因为 hellospringboot 应用依赖于 jdk8;MAINTAINER 写上作者;COPY 行是把需要生成镜像的 jar 包和 shell 脚本拷贝进镜像中;RUN 行把 shell 脚本改成可执行文件,同时将脚本链接到环境变量中,以方便后面的 CMD 执行;WORKDIR 指明镜像的工作目录;EXPOSE 指明镜像的开放端口,因为 hellospringboot 端口为 8080 所以这里也设置为 8080;最后一行的 CMD 是指在启动容器时会执行的命令,这里就是通过执行 java -jar 命令将服务启动起来。

docker-entrypoint.sh 脚本就很简单了,就是获取到 jar 包,然后通过 java -jar 命令把应用启动起来。

构建镜像、容器化启动应用

上面我们已经完成 Dockerfile 文件的编写,接下来就简单了,在 Dockerfile 文件的当前目录下执行命令 docker build 即可构建镜像(-t 参数是给镜像起个名字并以 v1 作为 tag 标识):

1
2
3
4
# docker build -t hellospringboot:v1 .
......
Successfully built dfbdde43b0aa
Successfully tagged hellospringboot:v1

提示成功了,执行 docker images 命令查看构建成功的镜像:

1
2
3
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hellospringboot v1 dfbdde43b0aa 38 minutes ago 328MB

很好,通过 docker run 启动容器(–name 是给容器起个名字;-d 是让其在后台运行;-p 是指定服务对外提供的访问端口,其中 8888是对外的端口, 8080 是容器内部端口,就是 Dockerfile 文件中 EXPOSE 指定的那个端口):

1
2
3
4
5
6
# docker run --name hello -d -p 8888:8080 hellospringboot:v1
574d0ed0ed6940d6a45f994a84eda45d2be8f6e85663aa9ba4a9b07970fb178f

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
574d0ed0ed69 hellospringboot:v1 "docker-entrypoint.sh" 39 minutes ago Up 39 minutes 0.0.0.0:8888->8080/tcp hello

哈哈,访问下吧:

1
2
# curl 192.168.18.128:8888
HelloWorld.

至此 over。

遇到的几个坑

坑1,在 Windows 上写好的脚本,上传到 Linux 构建镜像出错

这个问题其实是格式的问题,Windows 上的换行符是 \r\n,而 Linux 上的换行符是 \n,什么?不信?我们来看看吧。下面 docker-entrypoint.sh 是我刚从 Windows 上上传到 Linux 上的脚本:

1
2
file docker-entrypoint.sh 
docker-entrypoint.sh: a /usr/bin/env bash\015 script, ASCII text executable, with CRLF line terminators

后面显示的 CRLF 就是换行符说明,那怎么转换成 Linux 格式?推荐使用 dos2unix 命令,发现没有该命令?,没事安装下就行:

1
2
3
4
5
6
7
# yum -y install dos2unix

# dos2unix docker-entrypoint.sh
dos2unix: converting file docker-entrypoint.sh to Unix format ...

]# file docker-entrypoint.sh
docker-entrypoint.sh: Bourne-Again shell script, ASCII text executable

perfect,问题解决。

坑2,容器起来了,本机可以访问服务,但是其他机器却无妨访问

这个问题是防火墙的问题,CentOS 默认将防火墙打开,关掉防火墙再重启 docker 服务即可。

1
2
3
4
# systemctl stop firewalld.service
# systemctl disable firewalld.service
# systemctl restart docker
# docker start 574d0ed0ed69

其实 容器在启动时可以加上参数 –restart=always,到时 docker 重启或宿主机重启后,容器都会自动重启了。

使用 Springboot 编写 Helloworld 应用

Posted on 2019-05-02 | Post modified: 2019-05-02   |   post.updated 2019-05-02 | In Java |

在 Windows 平台安装 Springboot 应用开发环境

Springboot 框架是当前 Java 语言中最流行的微服务开发框架,本篇文章采用的操作系统是 Win10, Java 版本是 JDK8,编辑器使用 IDEA 专业版。

在 Win10 下安装 JDK8

首先下载 JDK,点击 JDK8 下载链接,我选择的版本是:jdk-8u152-windows-x64.exe,下载完成后依次点击安装,以下是我安装的路径:

1
2
3
D:\Program Files\Java
jdk1.8.0_152
jre1.8.0_152

接下来配置下 JDK 环境变量,依次选择:“我的电脑” > “鼠标右键” > “属性” > “高级系统设置”,进入环境变量设置页面。

在“系统变量”栏中点击“新增”,变量名为:JAVA_HOME,变量值为:D:\Program Files\Java\jdk1.8.0_152;

接着继续点击“新增”按钮,变量名为:CLASSPATH,变量值为:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

在系统变量栏选择“Path”,然后点击“编辑”,在弹出的“编辑环境变量”对话框中点击“新增”按钮,然后输入:%JAVA_PATH%\bin,最后再点击“确认”按钮。

安装配置完成了,检验下是否安装安装成功了:按下键盘 Win + R 组合键,在弹出的对话框输入栏中输入命令:cmd,在 cmd 命令行窗口中输入命令:java -version,查看 Java 版本号:

1
2
3
4
C:\Users\Administrator>java -version
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

安装成功了。

在 Win10 下安装 IDEA 专业版

首先下载软件包,进入官网,选择 “Ultimate”专业版下载,下载完成后也是按照提示一步一步按照即可。由于专业版是收费版本,如果资金允许还是支持下正版吧,如果想破解的话也很简单,点击获取 IDEA 注册码,然后就可以很方便的破解了。

创建 Springboot 应用

安装 gradle

首先下载软件包,进入官网,然后找到 windows 版本的软件包,下载安装即可,最后再配置下环境变量;

即可,最后进入 cmd 窗口验证是否安装、配置成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
C:\Users\Administrator>gradle -v

------------------------------------------------------------
Gradle 5.4
------------------------------------------------------------

Build time: 2019-04-16 02:44:16 UTC
Revision: a4f3f91a30d4e36d82cc7592c4a0726df52aba0d

Kotlin: 1.3.21
Groovy: 2.5.4
Ant: Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM: 1.8.0_152 (Oracle Corporation 25.152-b16)
OS: Windows 10 10.0 amd64

安装成功。

使用 IDEA 创建 Springboot 应用

打开 IDEA,选择创建新工程,在弹出的“New Project”对话框中的左侧导航栏中选择“Spring Initializr”,右上方的“Project SDK”选择 java1.8,如下图所示:

然后点击“next”按钮,这里我们使用 gradle 作为工程的依赖管理工具,跟 maven 相比 gradle 会显得简洁不少。具体填写信息如下图所示:

接着点击“next”按钮,因为我们打算编写 Web 应用,所以这里的依赖选择 Web:

然后继续点击“next”按钮,即可完成工程的创建。

在弹出的“Import Module from Gradle”对话框中直接点击“Ok”即可:

基于 gradle 的 springboot 工程简要说明

在新创建的工程中,当打开 buid.gradle 文件时发现右上方有提示询问你要不要安装 gradle wrapper,这里我们选择安装它,gradle wrapper 简单理解就是如果安装了它尽管当前系统没有安装 gradle 一样可以使用 gradlew 命令作为依赖管理,所以很方便。

当 gradle wrapper 安装完成后,你会发现工程目录下多出了 gradlew 和 gradlew.bat 两个文件,这两个文件就是对 gradle 的二次封装,一个可以在 Linux 平台使用(gradlew)一个在 Windows 平台使用(尽管没有安装 gradler 也可以使用)。

接下来简要说说 build.gradle 文件,这个文件就相当于 maven 工程的 pom.xml 文件,是对整个工程依赖包的说明,我对这个文件补充一些注释并给仓库添加了国内源,这样在拉取依赖包的时候速度会快很多。具体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
plugins {
id 'org.springframework.boot' version '2.1.4.RELEASE'
id 'java'
}

// 使用spring boot的自动依赖管理
apply plugin: 'io.spring.dependency-management'

group = 'com.chenliangtang'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
// 优先使用国内源
maven {url 'http://maven.aliyun.com/nexus/content/groups/public/'}
mavenCentral()
}

// 依赖列表
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

编写 hello world

springboot 有它的代码规范,整个代码都放在 src 目录下,其中 main 目录是放真正的业务代码, test 目录放的是测试代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
src/
├── main
│   ├── java
│   │   └── com
│   │   └── chenliangtang
│   │   └── hellospringboot
│   │   └── HellospringbootApplication.java
│   └── resources
│   ├── application.properties
│   ├── static
│   └── templates
└── test
└── java
└── com
└── chenliangtang
└── hellospringboot
└── HellospringbootApplicationTests.java

在 com.chenliangtang.hellospringboot 目录下创建 controller 目录,该目录下放置的是前端控制代码,然后新增代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.chenliangtang.hellospringboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloworldController {
@GetMapping("/")
@ResponseBody
public String Home() {
return "HelloWorld.";
}
}

上述代码逻辑很简单,就是定义了一个接口为 / 的 get 请求,返回内容为 HelloWorld。

OK,接下来运行下程序。运行方式可以是 GUI 的点击方式,也可以是命令行,GUI 方式如下图所示双击 bootRun 即可:

也可在 Terminal 栏下输入命令运行,如下图所示:

在客户端(我在 Linux 上用 curl 命令,也可以用浏览器访问)上访问:

1
2
# curl 192.168.43.244:8080/
HelloWorld.

到此完成!

CentOS7 安装 Python3

Posted on 2019-05-01 | Post modified: 2019-05-03   |   post.updated 2019-05-03 | In Python |

CentOS7 的基础环境

关于 CentOS 的安装过程就不介绍了,网上搜索相关资料照着安装即可。
以下是我当前 CentOS7 系统的版本信息:

1
2
3
4
5
# cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core)

# uname -a
Linux CentOS 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

先来看下当前系统预装的 Python 版本:

1
2
# python -V
Python 2.7.5

再看看 pip 是否安装了:

1
2
# pip -V
-bash: pip: 未找到命令

CentOS7 下安装 pip

yum 是 CentOS 的软件包管理器,可以使用它在线安装软件。 依次执行以下命令就可以安装 pip 了,当然前提机器是可以上网的,

1
2
3
4
5
6
7
8
9
10
11
12
# yum -y install epel-release
......
已安装:
epel-release.noarch 0:7-11

完毕!

# yum -y install python-pip
......
已安装:
python2-pip.noarch 0:8.1.2-8.el7
......

查看 pip 是否安装成功了:

1
2
# pip -V
pip 8.1.2 from /usr/lib/python2.7/site-packages (python 2.7)

pip 安装成功, 如果你想升级 pip,可以执行下面的命令升级 pip:

1
2
3
4
5
6
# pip install --upgrade pip
......
Successfully installed pip-19.1

# pip -V
pip 19.1 from /usr/lib/python2.7/site-packages/pip (python 2.7)

pip 升级成功了。

安装 Python3

我们知道 Python2 到 2020 年官方就停止更新了,所以,是时候拥抱 Python3 了。先在 https://www.python.org上查看当前 linux 下 python3 的最新版本,然后使用 wget 命令将软件包下载下来:

1
2
3
4
# wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0a3.tgz

# ls
anaconda-ks.cfg Python-3.8.0a3.tgz

下载成功了,然后解压到当前目录下:

1
2
3
4
5
6
7
# tar -xzvf Python-3.8.0a3.tgz

# ll
总用量 22772
-rw-------. 1 root root 1417 4月 15 05:41 anaconda-ks.cfg
drwxr-xr-x. 18 clt clt 4096 3月 26 03:44 Python-3.8.0a3
-rw-r--r--. 1 root root 23307857 3月 26 04:03 Python-3.8.0a3.tgz

解压成功,进入解压后的目录,然后编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# cd Python-3.8.0a3

# ./configure prefix=/usr/local/python3
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking for python3.8... no
checking for python3... no
checking for python... python
checking for --enable-universalsdk... no
checking for --with-universal-archs... no
checking MACHDEP... checking for gcc... no
checking for cc... no
checking for cl.exe... no
configure: error: in `/root/Python-3.8.0a3':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details

发现生成 Makefile 失败了,提示没有安装编译器 gcc,好吧,接下来安装 gcc:

1
2
3
4
5
# yum -y install gcc gcc-c++ kernel-devel 

# gcc -v
......
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)

编译器安装成功后,接下执行编译命令:

1
2
3
4
5
6
7
8
# ./configure prefix=/usr/local/python3

# make && make install
......
ERROR:root:code for hash md5 was not found.
......
ImportError: cannot import name 'sha512' from 'hashlib' (/root/Python-3.8.0a3/Lib/hashlib.py)
make: *** [install] 错误 1

这是因为 python 编译的时候没有 openssl 依赖(依赖 libssl.so 和 libcrypto.so),执行以下命令(libssl.so 和 libcrypto.so 的版本和安装路径未必一样,需要根据实际情况填写):

1
2
3
# ln -s /usr/lib64/libssl.so.1.0.2k libssl.so

# ln -s /usr/lib64/libcrypto.so.1.0.2k libcrypto.so

然后重新编译:

1
2
3
4
5
6
7
8
# ./configure /usr/local/python3

# make && make install
......
zipimport.ZipImportError: can't decompress data; zlib not available
......
zipimport.ZipImportError: can't decompress data; zlib not available
make: *** [install] 错误 1

要崩溃了。。。linux 上通过源码编译安装有时候就是这样,各种依赖,所以也只能一个依赖一个依赖的去解决。那我再安装 zlib 相关的依赖:

1
# yum -y install zlib*

然后再重新编译:

1
2
3
4
# make && make install
......
ModuleNotFoundError: No module named '_ctypes'
make: *** [install] 错误 1

我。。。不信了,再安装依赖:

1
# yum -y install libffi-devel

再重新编译、安装:

1
2
3
4
5
6
7
8
# yum -y install libffi-devel

# make && make install
......
Collecting setuptools
Collecting pip
Installing collected packages: setuptools, pip
Successfully installed pip-19.0.3 setuptools-40.8.0

这次终于都安装成功了.

进到安装目录下看看:

1
2
3
4
5
6
7
# cd /usr/local/python3/bin/

# ./python3 -V
Python 3.8.0a3

# ./pip3 -V
pip 19.0.3 from /usr/local/python3/lib/python3.8/site-packages/pip (python 3.8)

没问题了,接着先备份原来的 python2 及 pip2:

1
2
3
4
5
6
7
8
9
# whereis python
python: /usr/bin/python /usr/bin/python2.7 /usr/lib/python2.7 /usr/lib64/python2.7 /etc/python /usr/include/python2.7 /usr/share/man/man1/python.1.gz

# mv /usr/bin/python /usr/bin/python2

# whereis pip
pip: /usr/bin/pip /usr/bin/pip2.7

# mv /usr/bin/pip /usr/bin/pip2

然后为 python3 及 pip3 创建软连接:

1
2
3
4
5
6
7
8
9
# ln -s  /usr/local/python3/bin/python3 /usr/bin/python

# ln -s /usr/local/python3/bin/pip3 /usr/bin/pip

# python -V
Python 3.8.0a3

# pip -V
pip 19.0.3 from /usr/local/python3/lib/python3.8/site-packages/pip (python 3.8)

至此 python3 安装完成了。但是这个时候再去使用 yum 去安装软件就会报错:

1
2
3
4
5
# yum -y install gcc
File "/usr/bin/yum", line 30
except KeyboardInterrupt, e:
^
SyntaxError: invalid syntax

这是因为 yum 是使用 python2 编写的,所以需要把 yum 的头文件改成用 python2 作为解释器:

1
2
3
4
5
6
# whereis yum
yum: /usr/bin/yum /etc/yum /etc/yum.conf /usr/share/man/man8/yum.8

# vim /usr/bin/yum

其中,#!/usr/bin/python 改成 #!/usr/bin/python2 即可

项目管理知识总结

Posted on 2019-04-07 | Post modified: 2019-05-01   |   post.updated 2019-05-01 | In 项目管理 |

引论

项目管理的五大过程组和十大知识领域是核心知识点,本篇文章主要对此进行总结。每个项目或阶段都可以按启动、规划、执行、监控、收尾划分,中间会涉及整合管理、范围管理、进度管理、成本管理、质量管理、资源管理、沟通管理、风险管理、采购管理、相关方管理,其中各个知识领域又包括 49 个过程,详情如下表所示。

十大知识领域 启动过程组(2) 规划过程组(24) 执行过程组(10) 监控过程组(12) 收尾过程组(1)
4 整合管理 4.1 制定项目章程 4.2 制定项目管理计划 4.3 指导与管理项目工作
4.4 管理项目知识
4.5 监控项目工作
4.6 实施整体变更控制
4.7 结束项目或阶段
5 范围管理 5.1 规划范围管理
5.2 收集需求
5.3 定义范围
5.4 创建 WBS
5.5 确认范围
5.6 控制范围
6 进度管理 6.1 规划进度管理
6.2 定义活动
6.3 排列活动顺序
6.4 估算活动持续时间
6.5 制定进度计划
6.6 控制进度
7 成本管理 7.1 规划成本管理
7.2 估算成本
7.3 制定预算
7.4 控制成本
8 质量管理 8.1 规划质量管理 8.2 管理质量 8.3 控制质量
9 资源管理 9.1 规划资源管理
9.2 估算活动资源
9.3 获取资源
9.4 建设团队
9.5 管理团队
9.6 控制资源
10 沟通管理 10.1 规划沟通管理 10.2 管理沟通 10.3 监督沟通
11 风险管理 11.1 规划风险管理
11.2 识别风险
11.3 实施定性风险分析
11.4 实施定量风险分析
11.5 规划风险应对
11.6 实施风险应对 11.7 监督风险
12 采购管理 12.1 规划采购 12.2 实施采购 12.3 控制采购
13 相关方管理 13.1 识别相关方 13.2 规划相关方参与 13.3 管理相关方 13.4 监督相关方

从五大过程组来看,启动过程组只包含两个过程(制定项目章程和识别相关方)。项目章程的主要作用就是批准项目成立和授权项目经理,相当于古时候的委任状,意思就是这个可以做了,然后就交给你了;而识别相关方就是记录与项目相关的干系人,从而更好的了解他们的需求和期望,以便更好的管理项目。规划过程组的过程最多,但简而言之实际就是为项目的实施制定管理策略和行动方案。执行过程组便是按照制定的项目管理计划实施、执行的过程。监控过程组就是对执行过程的把控,以保证项目按期交付。最后的收尾过程组便是进行项目或阶段的收尾工作,包括移交可交付成功、总结经验教训等。

项目运行环境

组织过程资产(主观因素)和事业环境(客观因素)。

项目经理的角色

项目经理在领导项目团队达成项目目标方面发挥着至关重要的作用。一般而言,项目经理从项目启动时开始参与项目,直到项目结束;可能会在项目启动之前就参与评估和分析活动,以推进战略目标的实现、提高组织绩效或满足客户需求;可能会协助项目商业分析、商业论证以及项目组合管理事宜;可能还参与后续跟进活动,以实现项目的商业效益。

整合管理

制定项目章程

制定项目章程是编写一份正式批准项目并授权项目经理在项目活动中使用组织资源的文件的过程。本过程的主要作用是,明确项目与组织战略目标之间的直接联系,确立项目的正式地位,并展示组织对项目的承诺。本过程仅开展一次或仅在项目的预定义点开展。

制定项目管理计划

制定项目管理计划是定义、准备和协调项目计划的所有组成部分,并把它们整合为一份综合项目管理计划的过程。本过程的主要作用是,生成一份综合文件,用于确定所有项目工作的基础及其执行方式,它仅开展一次或仅在项目的预定义点开展。

指导与管理项目工作

指导与管理项目工作是为实现项目目标而领导和执行项目管理计划中所确定的工作,并实施已批准变更的过程。本过程的主要作用是,对项目工作和可交付成果开展综合管理,以提高项目成功的可能性。本过程需要在整个项目期间开展。

管理项目知识

管理项目知识是使用现有知识并生成新知识,以实现项目目标,并且帮助组织学习的过程。本过程的主要作用是,利用已有的组织知识来创造或改进项目成果,并且使当前项目创造的知识可用于支持组织运营和未来的项目或阶段。本过程需要在整个项目期间开展。

监控项目工作

监控项目工作是跟踪、审查和报告整体项目进展,以实现项目管理计划中确定的绩效目标的过程。本过程的主要作用是,让相关方了解项目的当前状态并认可为处理绩效问题而采取的行动,以及通过成本和进度预测,让相关方了解未来项目状态。本过程需要在整个项目期间开展。

实施整体变更控制

实施整体变更控制是审查所有变更请求、批准变更,管理对可交付成果、项目文件和项目管理计划的变更,并对变更处理结果进行沟通的过程。本过程审查对项目文件、可交付成果或项目管理计划的所有变更请求,并决定对变更请求的处置方案。本过程的主要作用是确保对项目中已记录在案的变更做综合评审。如果不考虑变更对整体项目目标或计划的影响就开展变更,往往会加剧整体项目风险。本过程需要在整个项目期间开展。

关于变更请求:当客户提起变更时,您应:了解所要求的变更类型,并与团队成员交流以评估变更的含义; 如果存在正式的变更控制机制,请打开变更控制;将变更传达给管理层,并告知客户变更的影响;实施变更,如果被接受;如果提交的变更涉及到行业的法律法规且作为项目合同的一部分,则必须实施变更,朝这一步的第一步是激活变更控制机制。

结束项目或阶段

结束项目或阶段是终结项目、阶段或合同的所有活动的过程。本过程的主要作用是,存档项目或阶段信息,完成计划的工作,释放组织团队资源以展开新的工作。它仅开展一次或仅在项目的预定义点开展。

可能的项目或阶段的收尾流程:项目发起人或客户验收了可交付成果并已经确认满足项目目标;将可交付成果的所有权转移给指定的干系人,推动项目收尾;向所有干系人分发最终项目报告,提供项目的最终报告;总结经验教训,并更新组织的知识库。通常把项目收尾时组织过程资产的总结和更新叫做行政收尾:包括项目档案、项目或阶段收尾文件、历史信息和经验总结。

范围管理

规划范围管理

规划范围管理是为记录如何定义、确认和控制项目范围及产品范围,而创建范围管理计划的过程。本过程的主要作用是,在整个项目期间对如何管理范围提供指南和方向。本过程仅开展一次或仅在项目的预定义点开展。

收集需求

收集需求是为实现目标而确定、记录并管理相关方的需要和需求的过程。本过程的主要作用是,为定义产品范围和项目范围奠定基础,且仅开展一次或仅在项目的预定义点开展。

定义范围

定义范围是制定项目和产品详细描述的过程。本过程的主要作用是,描述产品、服务或成果的边界和验收标准。

定义范围过程会输出项目范围说明书。其内容包括:项目范围描述、验收标准、可交付成果、除外责任、制约因素、假设条件。

创建 WBS

创建工作分解结构(WBS)是把项目可交付成果和项目工作分解成较小、更易于管理的组件的过程。本过程的主要作用是,为所要交付的内容提供架构,它仅开展一次或仅在项目的预定义点开展。

创建WBS的过程:识别和分析可交付成果及相关工作;确定WBS的结构和编排工作;自上而下逐层细化分解;为WBS组件制定和分配标识编码;核实可交付成果分解的程度是否恰当。

确认范围

确认范围是正式验收已完成的项目可交付成果的过程。本过程的主要作用是,使验收过程具有客观性;同时通过确认每个可交付成果,来提高最终产品、服务或成果获得验收的可能性。本过程应根据需要在整个项目期间定期开展。

控制范围

控制范围是监督项目和产品的范围状态,管理范围基准变更的过程。本过程的主要作用是,在整个项目期间保持对范围基准的维护,且需要在整个项目期间开展。

进度管理

规划进度管理

规划进度管理是为规划、编制、管理、执行和控制项目进度而制定政策、程序和文档的过程。本过程的主要作用是,为如何在整个项目期间管理项目进度提供指南和方向。本过程仅开展一次或仅在项目的预定义点开展。

定义活动

定义活动是识别和记录为完成项目可交付成果而须采取的具体行动的过程。本过程的主要作用是,将工作包分解为进度活动,作为对项目工作进行进度估算、规划、执行、监督和控制的基础。本过程需要在整个项目期间开展。

排列活动顺序

排列活动顺序是识别和记录项目活动之间的关系的过程,本过程的主要作用是定义工作之间的逻辑顺序,以便在既定的所有项目制约因素下获得最高的效率。本过程需要在整个项目期间开展。

估算活动持续时间

估算活动持续时间是根据资源估算的结果,估算完成单项活动所需工作时段数的过程。本过程的主要作用是,确定完成每个活动所需花费的时间量。本过程需要在整个项目期间开展。

制定进度计划

制定进度计划是分析活动顺序、持续时间、资源需求和进度制约因素,创建进度模型,从而落实项目执行和监控的过程。本过程的主要作用是,为完成项目活动而制定具有计划日期的进度模型。本过程需要在整个项目期间开展。

关键路径法主要是在资源有限的条件下,利用缓冲来应对项目进度的不确定性,保障整体进度。想要有限的资源有效的发挥作用,非得在资源需求和资源供给方面取得平衡,才能根据资源的制约条件有效调增人员分派。

控制进度

控制进度是监督项目状态,以更新项目进度和管理进度基准变更的过程。本过程的主要作用是在整个项目期间保持对进度基准的维护,且需要在整个项目期间开展。

成本管理

规划成本管理

规划成本管理是确定如何估算、预算、管理、监督和控制项目成本的过程。本过程的主要作用是,在整个项目期间为如何管理项目成本提供指南和方向。本过程仅开展一次或仅在项目的预定义点开展。

估算成本

估算成本是对完成项目工作所需资源成本进行近似估算的过程。本过程的主要作用是,确定项目所需的资金。本过程应根据需要在整个项目期间定期开展。

制定预算

制定预算是汇总所有单个活动或工作包的估算成本,建立一个经批准的成本基准的过程。本过程的主要作用是,确定可据以监督和控制项目绩效的成本基准。本过程仅开展一次或仅在项目的预定义点开展。

控制成本

控制成本是监督项目状态,以更新项目成本和管理成本基准变更的过程。本过程的主要作用是,在整个项目期间保持对成本基准的维护。本过程需要在整个项目期间开展。

质量管理

规划质量管理

规划质量管理是识别项目及其可交付成果的质量要求和(或)标准,并书面描述项目将如何证明符合质量要求和(或)标准的过程。本过程的主要作用是,为在整个项目期间如何管理和核实质量提供指南和方向。本过程仅开展一次或仅在项目的预定义点开展。

管理质量

管理质量是把组织的质量政策用于项目,并将质量管理计划转化为可执行的质量活动的过程。本过程的主要作用是,提高实现质量目标的可能性,以及识别无效过程和导致质量低劣的原因。管理质量使用控制质量过程的数据和结果向相关方展示项目的总体质量状态。本过程需要在整个项目期间开展。

质量审计是用来确定项目活动是否遵循了组织和项目的政策、过程与程序。质量方针又叫质量政策。它是企业总的质量宗旨和质量方向,是统一和协调企业质量工作的行动指南。质量方针是由组织机构的最高管理者正式发布的该组织总的质量宗旨和方向。如果组织中没有,项目经理应该带领团队制作一份。

控制质量

控制质量是为了评估绩效,确保项目输出完整、正确且满足客户期望,而监督和记录质量管理活动执行结果的过程。本过程的主要作用是,核实项目可交付成果和工作已经达到主要相关方的质量要求,可供最终验收。控制质量过程确定项目输出是否达到预期目的,这些输出需要满足所有适用标准、要求、法规和规范。本过程需要在整个项目期间开展。

资源管理

规划资源管理

规划资源管理是定义如何估算、获取、管理和利用团队以及实物资源的过程。本过程的主要作用是,根据项目类型和复杂程度确定适用于项目资源的管理方法和管理程度。本过程仅开展一次或仅在项目的预定义点开展。

估算活动资源

估算活动资源是估算执行项目所需的团队资源,以及材料、设备和用品的类型和数量的过程。本过程的主要作用是,明确完成项目所需的资源种类、数量和特性。本过程应根据需要在整个项目期间定期开展。

获取资源

获取资源是获取项目所需的团队成员、设施、设备、材料、用品和其他资源的过程。本过程的主要作用是,概述和指导资源的选择,并将其分配给相应的活动。本过程应根据需要在整个项目期间定期开展。

建设团队

建设团队是提高工作能力,促进团队成员互动,改善团队整体氛围,以提高项目绩效的过程。本过程的主要作用是,改进团队协作、增强人际关系技能、激励员工、减少摩擦以及提升整体项目绩效。本过程需要在整个项目期间开展。

管理团队

管理团队是跟踪团队成员工作表现,提供反馈,解决问题并管理团队变更,以优化项目绩效的过程。本过程的主要作用是,影响团队行为、管理冲突以及解决问题。本过程需要在整个项目期间开展。

冲突解决方法:撤退/回避(从实际或潜在冲突中退出,将问题推迟到准备充分的时候,或者将问题推给其他人员解决);缓和/包容(强调一致而非差异;为维持和谐与关系而退让一步,考虑其他方的需要);妥协/调解(为了暂时或部分解决冲突,寻找能让各方都在一定程度上满意的方案,但这种方法有时会导致“双输”局面);强迫/命令(以牺牲其他方为代价,推行某一方的观点;只提供赢 — 输方案。通常是利用权力来强行解决紧急问题,这种方法通常会导致“赢输”局面);合作/解决问题(综合考虑不同的观点和意见,采用合作的态度和开放式对话引导各方达成共识和承诺,这种方法可以带来双赢局面)。

控制资源

控制资源是确保按计划为项目分配实物资源,以及根据资源使用计划监督资源实际使用情况,并采取必要纠正措施的过程。本过程的主要作用是,确保所分配的资源适时适地可用于项目,且在不再需要时被释放。本过程需要在整个项目期间开展。

沟通管理

规划沟通管理

规划沟通管理是基于每个相关方或相关方群体的信息需求、可用的组织资产,以及具体项目的需求,为项目沟通活动制定恰当的方法和计划的过程。本过程的主要作用是,为及时向相关方提供相关信息,引导相关方有效参与项目,而编制书面沟通计划。本过程应根据需要在整个项目期间定期开展。

管理沟通

管理沟通是确保项目信息及时且恰当地收集、生成、发布、存储、检索、管理、监督和最终处置的过程。本过程的主要作用是,促成项目团队与相关方之间的有效信息流动。本过程需要在整个项目期间开展。

监督沟通

监督沟通是确保满足项目及其相关方的信息需求的过程。本过程的主要作用是,按沟通管理计划和相关方参与计划的要求优化信息传递流程。本过程需要在整个项目期间开展。

风险管理

规划风险管理

规划风险管理是定义如何实施项目风险管理活动的过程。本过程的主要作用是,确保风险管理的水平、方法和可见度与项目风险程度,以及项目对组织和其他相关方的重要程度相匹配。本过程仅开展一次或仅在项目的预定义点开展。

识别风险

识别风险是识别单个项目风险以及整体项目风险的来源,并记录风险特征的过程。本过程的主要作用是,记录现有的单个项目风险,以及整体项目风险的来源;同时,汇集相关信息,以便项目团队能够恰当应对已识别的风险。本过程需要在整个项目期间开展。

实施定性风险分析

实施定性风险分析是通过评估单个项目风险发生的概率和影响以及其他特征,对风险进行优先级排序,从而为后续分析或行动提供基础的过程。本过程的主要作用是重点关注高优先级的风险。本过程需要在整个项目期间开展。

实施定量风险分析

实施定量风险分析是就已识别的单个项目风险和不确定性的其他来源对整体项目目标的影响进行定量分析的过程。本过程的主要作用是,量化整体项目风险敞口,并提供额外的定量风险信息,以支持风险应对规划。本过程并非每个项目必需,但如果采用,它会在整个项目期间持续开展。

风险定量分析是针对项目总体目标影响大的风险进行分析。

规划风险应对

规划风险应对是为处理整体项目风险敞口,以及应对单个项目风险,而制定可选方案、选择应对策略并商定应对行动的过程。本过程的主要作用是,制定应对整体项目风险和单个项目风险的适当方法;本过程还将分配资源,并根据需要将相关活动添加进项目文件和项目管理计划。本过程需要在整个项目期间开展。

威胁应对策略有:上报(该威胁超出了项目经理的权限,就采取上报策略)、规避(通过改变项目管理计划以完全消除风险发生的条件,比如:延长进度、改变策略、缩减范围 、取消项目)、转移(将风险的后果连同对应的责任转移给第三方,比如:买保险、担保书、保证书)、减轻(降低不理风险发生的概率、后果,比如:原型试验、更多测试、稳定供应商、简单的流程和工序)、接受(承认风险的存在,但不主动采取措施)。

机会应对策略有:上报(该威胁超出了项目经理的权限,就采取上报策略)、开拓(如果组织想确保把握住高优先级的机会,有采取开拓策略)、分享(将应对机会的责任转移给第三方,比如:建立合作关系、合作团队、合资企业分享机会)、提高(提高机会出现的概率,比如为早日完成活动而增加资源)、接受(承认风险的存在,但不主动采取措施)。

实施风险应对

实施风险应对是执行商定的风险应对计划的过程。本过程的主要作用是,确保按计划执行商定的风险应对措施,来管理整体项目风险敞口、最小化单个项目威胁,以及最大化单个项目机会。本过程需要在整个项目期间开展。

监督风险

监督风险是在整个项目期间,监督商定的风险应对计划的实施、跟踪已识别风险、识别和分析新风险,以及评估风险管理有效性的过程。本过程的主要作用是,使项目决策都基于关于整体项目风险敞口和单个项目风险的当前信息。本过程需要在整个项目期间开展。

采购管理

规划采购

规划采购管理是记录项目采购决策、明确采购方法,及识别潜在卖方的过程。本过程的主要作用是,确定是否从项目外部获取货物和服务,如果是,则还要确定将在什么时间、以什么方式获取什么货物和服务。货物和服务可从执行组织的其他部门采购,或者从外部渠道采购。本过程仅开展一次或仅在项目的预定义点开展。

实施采购

实施采购是获取卖方应答、选择卖方并授予合同的过程。本过程的主要作用是,选定合格卖方并签署关于货物或服务交付的法律协议。本过程的最后成果是签订的协议,包括正式合同。本过程应根据需要在整个项目期间定期开展。

控制采购

控制采购是管理采购关系,监督合同绩效,实施必要的变更和纠偏,以及关闭合同的过程。本过程的主要作用是,确保买卖双方履行法律协议,满足项目需求。本过程应根据需要在整个项目期间开展。

相关方管理

识别相关方

识别相关方是定期识别项目相关方,分析和记录他们的利益、参与度、相互依赖性、影响力和对项目成功的潜在影响的过程。本过程的主要作用是,使项目团队能够建立对每个相关方或相关方群体的适度关注。本过程应根据需要在整个项目期间定期开展。

规划相关方参与

规划相关方参与是根据相关方的需求、期望、利益和对项目的潜在影响,制定项目相关方参与项目的方法的过程。本过程的主要作用是,提供与相关方进行有效互动的可行计划。本过程应根据需要在整个项目期间定期开展。

管理相关方

管理相关方参与是与相关方进行沟通和协作以满足其需求与期望、处理问题,并促进相关方合理参与的过程。本过程的主要作用是,让项目经理能够提高相关方的支持,并尽可能降低相关方的抵制。本过程需要在整个项目期间开展。

监督相关方

监督相关方参与是监督项目相关方关系,并通过修订参与策略和计划来引导相关方合理参与项目的过程。本过程的主要作用是,随着项目进展和环境变化,维持或提升相关方参与活动的效率和效果。本过程需要在整个项目期间开展。

易混淆概念解析

管理质量、控制质量

管理质量(关注过程)的关键词:信心、宏观、过程、审计、持续改进、(不)增值活动/无效活动;

控制质量(关键具体的问题)的关键词:不良、测试、工作包、满足客户期望、检查、核对、核实、核查。

建设团队、管理团队

建设团队是提高工作能力,促进团队成员互动,改善团队整体氛围,以提高项目绩效的过程;

管理团队(本质是监督团队)的主要作用是影响团队行为、管理冲突以及解决问题。

管理沟通、监督沟通

管理沟通主要作用促成项目团队与相关方之间的有效信息流动;

监督沟通主要作用按沟通管理计划和相关方参与计划的要求优化信息传递流程。

规划采购、实施采购

规划采购管理是记录项目采购决策、明确采购方法,及识别潜在卖方的过程。

实施采购是获取卖方应答、选择卖方并授予合同的过程(实施采购可以理解为就是招投标会议)。

管理相关方、监督相关方

管理相关方参与是为了满足相关方的需要而与之沟通和协作,并解决所发生的问题的过程;

监督相关方参与是监督相关相关方关系,并通过修订参与策略和计划来引导相关方参与项目的过程。

沟通管理、相关方管理

一个是信息管理,一个是人的参与。注意区分是信息问题还是人员的支持抵制问题就不会混淆。项目中所有信息的发布、传递都是沟通问题。沟通的本质是信息交互。通知相关方的依据是沟通管理计划。

商业论证、工作说明书、项目章程、项目范围说明书

商业论证(决定项目做或不做):进行企业需求和成本效益分析以论证项目的合理性并确定项目边界。

工作说明书(也叫SOW,是制定项目章程的输入文件)包括:业务需求、产品范围描述和战略目的。如果项目是从外部引入,其就是采购工作说明书。

项目章程的内容:三总(总体制约因素、总体里程碑计划、总体预算),三高(高层级项目描述、高层级需求、高层级风险),三项(项目目的、项目审批要求、项目目标),三人(发起人、项目经理、相关方)。它是高层级的概括。

项目范围说明书的内容: 项目范围描述、验收标准、可交付成果、除外责任、制约因素、假设条件。是定义范围过程的输出,是具体的项目范围说明。

工作绩效数据、工作绩效信息、工作绩效报告

工作绩效数据在执行项目工作的过程中,从每个正在执行的活动中收集到的原始观察结果和测量值。例如包括工作完成百分比、质量和技术绩效测量结果、进度计划活动的开始和结束日期、变更请求的数量、缺陷的数量、实际成本和实际持续时间等。项目数据通常记录在项目管理信息系统(PMIS)和项目文件中;

工作绩效信息从各控制过程收集,并结合相关背景和跨领域关系进行整合分析而得到的绩效数据,绩效信息的例子包括可交付成果的状态、变更请求的落实情况及预测的完工尚需估算;

工作绩效报告为制定决策、提出问题、采取行动或引起关注,而汇编工作绩效信息所形成的实物或电子项目文件。例如包括状况报告、备忘录、论证报告、信息札记、电子仪表盘、推荐意见和情况更新。

执行阶段的指导与管理项目工作输出工作绩效数据,它是原始的数据;监督项目工作输出工作绩效报告,管理质量输出质量报告,识别风险输出风险报告;控制范围、确认范围、控制进度、控制成本、控制质量、控制资源、监督沟通、监督风险、控制采购、监督相关方参与、输出工作绩效信息。

可交付成果、核实的可交付成果、验收的可交付成果

指导与管理项目工作过程输出可交付成果;

控制质量过程输出核实的可交付成果;

确认范围输出验收的可交付成果;

最后在收尾阶段将验收的可交付成果移交给客户。

易混淆的工具和技术

头脑风暴也叫集思广益会,通常有一个主持人,只是把各个想法、意见想出来,但并没有做决策。

德尔菲技术的特征是背靠背、匿名、旨在取得一致意见。

焦点小组技术是围绕焦点问题展开,着重于互动讨论。

名义小组是用于刷选头脑风暴会议的结果,着重于结果的刷选。

引导式研讨会是着重于形成既定目标的一致意见。

质量功能展开的内容:收集客户声音;对客户需求进行分类和排序;为实现客户需求设定目标;确定质量标准。

联合应用开发:适用于软件开发行业,这种 研讨会注重把业务主题专家和开发团队集中在一起,以收集和改进软件开发过程。

产品范围和项目范围

产品范围是某项产品、服务或成果所具有的特征和功能。

项目范围是为交付具有规定特性与功能的产品、服务或成果而必须完成的工作。项目范围有时也包括产品范围。

产品需求文件是衡量产品范围完成情况的依据,尽管项目管理计划和项目范围说明书中也有产品范围描述,但他们包括的范围太广。工作分解结构只是描述项目范围,产品需求文件最具有针对性。

职能型、弱矩阵型、平衡矩阵型、强矩阵型、项目型

职能型矩阵中,各个部门独立地开展各自部门的工作;

在弱矩阵型的组织结构中,项目经理充当项目联络员的角色,没有任何权力;

在平衡矩阵中,项目经理具有了一定的权力,但让隶属于职能经理;

在强矩阵中,项目经理拥有较大职权,与职能经理平级,但让没有人事权;

项目型矩阵中,项目经理的职权最大,高于职能经理,可以全权管理项目和项目资金。

问题日志、风险登记册

问题日志(Issue Log)用于记录和监督问题的解决。它可用来促进沟通,确保对问题的共同理解。问题日志强调的 是 相 关 方 对 项 目 上 的 关 注 和 关 心(concern),这些关注和关心的英文是“Issue”,这些“issue”可能是项目的问题(Problem),也可能是项目的风险(Risk)。PM 在和关键相关方
沟通时会借助问题日志进行沟通,例如针对这个 issue 已经提出了变更请求,
或者已经作为风险进行应对了等等,这些都是针对该 issue 的解决措施。

风险登记册会记录风险分析和风险应对规划的结果。等风险发生状态发生
变化时首先需要更新的文件。注意风险Risk 发 生 了 将 会 变 成 项 目 的 问 题(Problem),针对 Problem 往往需要提出变更(change)

项目组合、项目集、项目

项目组合是指为实现战略目标而协调管理的项目、项目集和子项目组合和运营工作,通常包含各个项目的优先级;

项目集是相互关联且被协调管理的项目、子项目集和项目集活动,以便获得分别管理所无法获得的利益。项目集包括所属单个项目范围之外的相关工作,各个项目之间是有关联的;

项目管理是针对某一个项目而言的。

项目生命周期、产品生命周期、项目管理生命周期

项目生命周期指项目从开始到完成所经历的一系列,包括:开始项目、组织与准备、执行项目工作、结束项目;

产品生命周期与项目生命周期相互独立,前者可能由项目产生。产品生命周期指一个产品从概念、交付、成长、成熟到衰退的整个演变过程的一系列阶段,通常产品生命周期包含一个或多个项目生命周期;

项目管理生命周期可以简单理解成五大过程组:启动、规划、执行、监控、收尾。

事业环境因素、组织过程资产

事业环境因素是指项目团队不能控制的,将对项目产生影响、限制或指令作用的各种条件。如:组织文化、结构和治理设施和资源的地理分布、基础设施、信息技术软件、资源可用性、员工能力等;

组织过程资产是执行组织所特有并使用的计划、过程、政策、程序和知识库,会影响对具体项目的管理。

运营经理、职能经理、项目经理

运营经理负责保证业务运营的高效性;

职能经理专注于某个职能领域或业务部门的管理监督;

项目经理由组织委派、领导团队实现项目目标的个人。

配置管理系统、工作授权系统、变更管理系统

配置管理系统描述如何记录和更新项目的特定信息,以及记录和更新哪些信息,以保持产品、服务或成果的一致性、有效性;

工作授权系统是由正式文件的若干明确核准项目工作的程序组成,确保该项目工作由明确的组织在正确的时间以恰当的顺序完成;

变更管理系统是规定控制、改变和批准项目可交付成果和文件方式的有案可稽的一套正式程序,明确 CCB 的角色和职责,完成变更控制活动,由文字工作、跟踪系统、变更审批层次构成。

领导力、影响力

影响力在矩阵环境中,项目经理对团队成员通常没有或仅有很小的命令职权,所以他们适时影响相关方的能力,对保证项目成功非常关键。影响力主要体现在如下各方面:说服他人、清晰表达观点和立场、积极且有效的倾听、了解并综合考虑各种观点、收集相关信息,在维护相互信任的关系下,解决问题并达成一致意见;

领导力是成功的项目需要强有力的领导技能,领导力是领导团队、激励团队做好本质工作的能力。它包括各种不同的技巧、能力和行动。且领导力在项目生命周期中的所有阶段都很重要。有多种领导力理论,定义了适用于不同情形或团队的领导风格。领导力对沟通愿景及鼓舞项目团队高效工作十分重要。

合同收尾、行政收尾

合同收尾是由买方授权的采购管理员向卖方签发书面确认;

行政收尾是发起人或管理层给项目经理签发书面文件,一般合同收尾在行政收尾之前,但合同收尾中包含行政收尾。

需求管理计划、范围管理计划

范围管理计划描述如何管理总体工作范围,包括由卖方负责的工作范围;

需求管理计划描述将如何分析、记录和管理需求,它可能还包括卖方将如何管理按协议规定应该实现的需求。

项目进度网络图、横道图、里程碑

项目进度网络图(点)表示活动之间的逻辑关系,是详细的进度计划;

横道图(线)是追踪概括性的活动进度;

里程碑(面)是项目进展的概括。

资源平衡、资源平滑

资源平衡通常会导致关键路径边长,如:在同一时间活动A和活动B都需要小明,但是由于资源紧缺,所以项目经理决定要小明干完活动A再来干活动B,这就是资源平衡的策略,但是导致关键路径编长了;

资源平滑不会导致关键路径变长,如:本来这个活动需要2个人力的,但是实际只要1个人力。

赶工、快速跟进

赶工通常需要增加资源,比如加班;

快速跟进通常会增加项目风险,适合对关联性不强的活动进行快速跟进。

几种资源(人)管理理论

Herzberg双因素理论:保健(物质)因素指工资、主管的态度、工作条件,差的保健因素可能破坏人的积极性,改善保健因素并没有激励人的作用;激励因素指积极的动机来源于体验和实现自我成功的机会,并得到对其能力和成就的承认,员工应有自我发展意识和责任感。

戴维.麦克利兰成就理论:认为人工作中工作情境有三种需要(成就需要、权力需要、亲和需要),管理者应该根据各人更重视的需要来制定激励措施。

Maslow 需求层次理论:生理需求、安全保障、社会归属、自尊人尊、自我价值,只有前一种实现了才去有上一层次的需要。

弗鲁姆期望理论:认为积极性和努力程度,等于所能得到结果的全部预期价值乘以他认为达到该结果的期望概率。

麦克雷格XY理论:X理论认为人是消极。懒惰的,设法逃避工作,缺乏进取心;而Y理论则相反,认为人是积极的,愿意工作,愿意进步,愿意承担责任等。传统的管理比较偏向于X理论,现代管理越来越偏向于Y理论。

威廉.大内Z理论:认为一切企业的成功离不开信任、敏感与亲密,因此主张以坦白、开放、沟通作为基本原则来实行民主管理。

亚当斯公平理论:认为要使组织保持较高的工作热情,必须使工作报酬公平合理,使组织的成员感到组织的分配是公平的。

光环效应:对一个事物的好感转移到与该事物相关的其他事物上去。

号角效应:员工在某方面表现不好,就认为他在其他方面也表现不佳。

总价合同、工料合同、成本补偿合同

总价合同适合需求清楚、具体的项目;

工料合同兼具总价合同和成本补偿合同的特点,通常强调专家、紧迫性和快速;

成本补偿合同适合需求不清楚的。

变更管理流程

问题处理流程

项目或阶段收尾流程

启动会议、开工会议

启动会议(Initiating Meeting)的完成意味着启动过程组的结束,会议主要是授权项目经理并正式启动项目;

开工会议(Kick-Off Meeting)的完成也意味着规划过程组的结束,会议主要目的是了解项目计划,使各相关方对项目有公共理解,活动各相关方对项目的承诺,宣告项目进入实施阶段。

2. Unity3D 结合实际项目实现单元测试

Posted on 2019-03-19 | Post modified: 2019-05-01   |   post.updated 2019-05-01 | In Unity3D 单元测试 |

被测项目背景介绍

这里只介绍项目中的一个 UI 模块。如下图所示,在整个 UI 模块中有很多子模块,在业务上各个子 UI 模块之间会经常需要来回切换。那么如何去实现该功能呢?在频繁的 UI 状态切换中又如何保证其正确性呢?

状态机的概念

答案就是:状态机。举个例子来帮助大家了解状态机的含义。每个上班族大概每天要干的事就是:早上起来去上班、到下班时间下班回家、困了累了上床睡觉。那么上班族一天的状态就有:上班、下班、睡觉;状态的转换条件有:早上会去上班、晚上下班、12点后睡觉;触发状态的转换条件有:到早上了就起床去上班、到晚上了就下班、12点了就睡觉。

项目中的状态机

根据我们对状态机的理解,假设在项目中 UI 子模块有三个:老弱帮扶专题、智慧人房专题和视频监控专题,那么它的状态机(包括各个状态及其转换条件)可以用下图表示:

EditMode 模式下的测试

被测函数说明

其实上面 UI 各个专题状态的转换,有个核心函数,它的流程图大致如下:

对应的实际代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void ChangeTo(string type)
{
string lastID = string.Empty;
this.GetLastID(ref lastID);
if(type == lastID)
{
this.EndAllStates();
}
else
{
var entry = m_entries.TryGetNullableValue(type);
if(entry != null)
{
this.EndAllStates();
this.AddState(entry);
}
}
}

测试用例设计

针对这个核心函数,对其进行测试用例设计:

  1. 点击某个状态,且上一状态为空;
  2. 点击某个状态,且与上一状态相同;
  3. 点击某个状态,且与上一状态不相同;
  4. 点击某个状态,但状态为Null;
  5. 点击某个状态,但状态未注册。

然后编写测试代码:

1~3 条测试用例代码
  1. 1~3 测试用例代码如下所示:
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System.Collections.Generic;
using UnityEngine;
using Module.FSM;
using InternalModule.FSM;

using NUnit.Framework;
using Assets.UnitTest.Utils;
//using NSubstitute;

namespace Assets.UnitTest.Editor.EditeModeTestDemo
{
[TestFixture]
class ThemeEntryManagerNormalTest
{
ThemeEntryManager entryManager;
List<FSMState<string>> themeStates;
Dictionary<string, ThemeEntry> themeEntries;
ThemeEntry themeEntryTest1Instance;
ThemeEntry themeEntryTest1;
string type1;
string lastID;

[SetUp]
public void SetUp()
{
type1 = "ThemeEntry1";
themeEntryTest1 = ThemeEntry.Create(type1, "test1", new GameObject("test1"));
entryManager = ThemeEntryManager.Instance;
//注册ThemeEntry
entryManager.SetEntry(type1, themeEntryTest1);

themeStates = ReflectionUtils
.CallPrivateField<List<FSMState<string>>>(ThemeEntryManager.Instance, "m_subStates", null);
themeEntries = ReflectionUtils
.CallPrivateField<Dictionary<string, ThemeEntry>>(ThemeEntryManager.Instance, "m_entries", null);
themeEntryTest1Instance = themeEntries.TryGetNullableValue(type1);
lastID = null;
entryManager.GetLastID(ref lastID);

//校验是否注册成功,上一状态是否为空
Assert.IsFalse(themeStates.Contains(themeEntryTest1));
Assert.IsTrue(themeEntryTest1 == themeEntryTest1Instance);
Assert.IsNull(lastID);

//模拟点击,初始化数据
entryManager.ChangeTo(type1);
entryManager.GetLastID(ref lastID);
themeStates = ReflectionUtils
.CallPrivateField<List<FSMState<string>>>(ThemeEntryManager.Instance, "m_subStates", null);
themeEntries = ReflectionUtils
.CallPrivateField<Dictionary<string, ThemeEntry>>(ThemeEntryManager.Instance, "m_entries", null);
themeEntryTest1Instance = themeEntries.TryGetNullableValue(type1);

//校验当前状态是否切换成功,是否成功转为上一状态
Assert.IsTrue(string.Equals(type1, lastID));
Assert.AreEqual(1, themeStates.Count);
Assert.IsTrue(themeStates.Contains(themeEntryTest1));
Assert.IsTrue(themeEntryTest1 == themeEntryTest1Instance);
}
[TearDown]
public void TearDown()
{
Object.DestroyImmediate(ThemeEntryManager.Instance);
}

[TestCase]
[Description("点击某个状态,且上一状态为空")]
public void ChangeThemeEntryStatusButLastThemeEntryStatusIsNullTest()
{
//set SetUp();
}
[TestCase]
[Description("点击某个状态,且与上一状态相同")]
public void ChangeThemeEntryStatusCurrentThemeEntryStatusIsSameAsTheLastStatusTest()
{
//模拟与上一状态一样时的点击
entryManager.ChangeTo(type1);
entryManager.GetLastID(ref lastID);
themeStates = ReflectionUtils
.CallPrivateField<List<FSMState<string>>>(ThemeEntryManager.Instance, "m_subStates", null);
themeEntries = ReflectionUtils
.CallPrivateField<Dictionary<string, ThemeEntry>>(ThemeEntryManager.Instance, "m_entries", null);
themeEntryTest1Instance = themeEntries.TryGetNullableValue(type1);

//校验状态是否回到初始状态
Assert.IsTrue(string.Equals(type1, lastID));
Assert.AreEqual(0, themeStates.Count);
Assert.IsTrue(themeEntryTest1 == themeEntryTest1Instance);
}

[TestCase]
[Description("点击某个状态,且与上一状态不相同")]
public void ChangeThemeEntryStatusCurrentThemeEntryStatusIsDifferentTheLastStatusTest()
{
string type2 = "ThemeEntryTwo";
ThemeEntry themeEntryTest2 = ThemeEntry.Create(type2, "test2", new GameObject("test2"));
//注册ThemeEntry
entryManager.SetEntry(type2, themeEntryTest2);

//模拟与上一状态不一样时的点击
entryManager.ChangeTo(type2);
entryManager.GetLastID(ref lastID);
themeStates = ReflectionUtils
.CallPrivateField<List<FSMState<string>>>(ThemeEntryManager.Instance, "m_subStates", null);
themeEntries = ReflectionUtils
.CallPrivateField<Dictionary<string, ThemeEntry>>(ThemeEntryManager.Instance, "m_entries", null);
ThemeEntry themeEntryTest2Instance = themeEntries.TryGetNullableValue(type2);

//校验状态是否回到初始状态
Assert.IsTrue(string.Equals(type2, lastID));
Assert.AreEqual(1, themeStates.Count);
Assert.IsTrue(themeStates.Contains(themeEntryTest2));
Assert.IsTrue(themeEntryTest2 == themeEntryTest2Instance);
}
}
}
4~5 条测试用例代码
  1. 4~5 测试用例代码如下所示:
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
using System.Collections.Generic;
using UnityEngine;
using Module.FSM;
using InternalModule.FSM;

using NUnit.Framework;
using Assets.UnitTest.Utils;
//using NSubstitute;

namespace Assets.UnitTest.Editor.EditeModeTestDemo
{
[TestFixture]
class ThemeEntryManagerAbnormalTest
{
[TearDown]
public void TearDown()
{
Object.DestroyImmediate(ThemeEntryManager.Instance);
}

[TestCase]
[Description("点击某个状态,但状态为Null")]
public void ChangeThemeEntryStatusButTheThemeEntryStatusIsNullTest()
{
string type1 = null ;
//string type1 = string.Empty ;
ThemeEntryManager.Instance.ChangeTo(type1);

ThemeEntryManager entryManager = ThemeEntryManager.Instance;
string lastID = null;
entryManager.GetLastID(ref lastID);
List<FSMState<string>> themeStates = ReflectionUtils
.CallPrivateField<List<FSMState<string>>>(ThemeEntryManager.Instance, "m_subStates", null);
Dictionary<string, ThemeEntry> themeEntries = ReflectionUtils
.CallPrivateField<Dictionary<string, ThemeEntry>>(ThemeEntryManager.Instance, "m_entries", null);
ThemeEntry themeEntryTest1Instance = themeEntries.TryGetNullableValue(type1);

Assert.AreEqual(0, themeStates.Count);
Assert.IsNull(lastID);
Assert.IsNull(themeEntryTest1Instance);
}

[TestCase]
[Description("点击某个状态,但状态未注册")]
public void ChangeThemeEntryStatusButTheThemeEntryStatusNoRegisteredTest()
{
string type1 = "ThemeEntry1";
ThemeEntryManager.Instance.ChangeTo(type1);

ThemeEntryManager entryManager = ThemeEntryManager.Instance;
string lastID = null;
entryManager.GetLastID(ref lastID);
List<FSMState<string>> themeStates = ReflectionUtils
.CallPrivateField<List<FSMState<string>>>(ThemeEntryManager.Instance, "m_subStates", null);
Dictionary<string, ThemeEntry> themeEntries = ReflectionUtils
.CallPrivateField<Dictionary<string, ThemeEntry>>(ThemeEntryManager.Instance, "m_entries", null);
ThemeEntry themeEntryTest1Instance = themeEntries.TryGetNullableValue(type1);

Assert.AreEqual(0, themeStates.Count);
Assert.IsNull(lastID);
Assert.IsNull(themeEntryTest1Instance);
}
}
}

执行测试

测试结果

测试结果分析

从测试结果可以看出,5 条测试用例都成功了 4 条,失败了 1 条。失败的原因是因为 ChangeTo() 函数对传入的参数没有校验是否为 null 的原因。

PlayMode 模式的测试

测试用例设计

这里我们以”老弱帮扶”专题为被测试对象,并对其设计测试用例:

  1. 老弱帮扶信息概览;
  2. 统计栏下点击老弱分布开关;
  3. 服务栏下点击数字设备开关;
  4. 分别在统计栏和服务拦下按右上角关闭按钮;
  5. 在老弱帮扶专题状态下点击其他专题按钮;
  6. 多次重复点击老弱帮扶专题;
  7. 从其他专题状态下点击老弱帮扶专题。

对应的测试代码

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using Module.UI;
using Module.FSM;
using Core;

using NUnit.Framework;
using UnityEngine.TestTools;
using Assets.UnitTest.Utils;
using Assets.UnitTest.PageObject;
//using NSubstitute;

namespace Assets.UnitTest.Player
{
class OldWeakThemeTest
{
public GameObject OldWeakBtn;
public GameObject Service;
public OldWeakServiceProCtr oldWeakServiceProCtr;
bool hasLoaded = false;

[OneTimeSetUp]
public void OnTimeSetUp()
{
//加载基础场景
Module.Controller.SceneResourcesLoad.Instance.LoadScenes();

UnityEngine.SceneManagement.SceneManager.sceneLoaded += (_scene, _mode) =>
{
Common.CommonDebug.Log(_scene.name, Color.green);
//BasicesScene为最后一个加载的场景
if (_scene.name == "BasicsScene")
{
hasLoaded = true;
Debug.Log("场景加载完成.");
}
};
}

[TearDown]
public void TearDown()
{
GameObject oldWeakMenu = GameObject.Find(OldWeakPage.OldWeakMenuPath);
if (oldWeakMenu.activeSelf)
{
GameObject oldWeakMenuCloseBtn = GameObject.Find(OldWeakPage.OldWeakMenuCloseBtn);
UserSimulateUtils.Click(oldWeakMenuCloseBtn);
}
}

[UnityTest]
//[Timeout(1000000)]
[Description("老弱帮扶信息概览")]
public IEnumerator OldWeakInfoOverviewTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//点击老弱帮扶专题按钮
clickOldWeakThemeBtn();

bool isTrue = checkStatisticsState();
bool isFalse = checkServiceState();
Assert.IsTrue(isTrue);
Assert.IsFalse(isFalse);

//统计栏和服务栏重复点击
for (int i = 0; i < 2; i++)
{
//点击OldWeakBtn后,默认切换到统计栏
clickServiceBtn();
isTrue = checkServiceState();
isFalse = checkStatisticsState();
Assert.IsTrue(isTrue);
Assert.IsFalse(isFalse);

clickStatisticsBtn();
isTrue = checkStatisticsState();
isFalse = checkServiceState();
Assert.IsTrue(isTrue);
Assert.IsFalse(isFalse);
}
}

[UnityTest]
[Description("统计栏下点击老弱分布开关")]
public IEnumerator ClickOldWeakDistributionSwitchTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//点击老弱帮扶专题按钮,默认老弱分布开关打开
clickOldWeakThemeBtn();
yield return null;
bool isTrue = checkOldWeakDistributionState();
Assert.IsTrue(isTrue);

for (int i = 1; i <= 4; i++)
{
if (i % 2 == 0)
{
//点偶数次,老弱分布开关打开
clickOldWeakDistributionBtn();
yield return null;
isTrue = checkOldWeakDistributionState();
Assert.IsTrue(isTrue);
}
else
{
//点奇数次,老弱分布开关关闭
clickOldWeakDistributionBtn();
yield return null;
bool isFalse = checkOldWeakDistributionState();
Assert.IsFalse(isFalse);
}
}
}

[UnityTest]
[Description("服务栏下点击数字设备开关")]
public IEnumerator ClickDigitalDeviceSwitchTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//点击老弱帮扶专题按钮,然后再点击服务按钮,切换到服务栏下
clickOldWeakThemeBtn();
yield return null;
clickServiceBtn();
yield return null;
bool isTrue = checkDigitalDeviceState();
Assert.IsTrue(isTrue);

for (int i = 1; i <= 4; i++)
{
if (i % 2 == 0)
{
//点偶数次,数字设备开关打开
clickDigitalDeviceBtn();
yield return null;
isTrue = checkDigitalDeviceState();
Assert.IsTrue(isTrue);
}
else
{
//点奇数次,数字设备开关关闭
clickDigitalDeviceBtn();
yield return null;
bool isFalse = checkDigitalDeviceState();
Assert.IsFalse(isFalse);
}
}
}

[UnityTest]
[Description("分别在统计栏和服务拦下按右上角关闭按钮")]
public IEnumerator ClickCloseBtnInStatisticsAndServiceTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//统计栏下按右上角关闭按钮
clickOldWeakThemeBtn();
yield return null;
clickCloseBtn();
yield return null;
bool isfalse = checkOldWeakMenuState();
Assert.IsFalse(isfalse);

//在服务拦下按右上角关闭按钮
clickOldWeakThemeBtn();
yield return null;
clickServiceBtn();
yield return null;
clickCloseBtn();
yield return null;
isfalse = checkOldWeakMenuState();
Assert.IsFalse(isfalse);
}

[UnityTest]
[Description("在老弱帮扶专题状态下点击其他专题按钮")]
public IEnumerator ClickOtherThemeInOldWeakStateTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//在老弱帮扶的统计栏下点击其他专题
clickOldWeakThemeBtn();
yield return null;
clickOtherThemeBtn();
yield return null;
bool isfalse = checkOldWeakMenuState();
Assert.IsFalse(isfalse);
bool isTrue = checkOtherThemeMenuState();
Assert.IsTrue(isTrue);

//在老弱帮扶的服务栏下点击其他专题
clickOldWeakThemeBtn();
yield return null;
clickServiceBtn();
yield return null;
isTrue = checkServiceState();
Assert.IsTrue(isTrue);
clickOtherThemeBtn();
yield return null;
isfalse = checkOldWeakMenuState();
Assert.IsFalse(isfalse);
isTrue = checkOtherThemeMenuState();
Assert.IsTrue(isTrue);
}

[UnityTest]
[Description("多次重复点击老弱帮扶专题")]
public IEnumerator RepeatedlyClickOldWeakThemeTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//多次重复点击老弱帮扶专题
for (int i = 1; i <= 10; i++)
{
clickOldWeakThemeBtn();
yield return null;
bool isOn = checkOldWeakMenuState();
if (i % 2 == 0)
Assert.IsFalse(isOn);
else
Assert.IsTrue(isOn);
}
}

[UnityTest]
[Description("从其他专题状态下点击老弱帮扶专题")]
public IEnumerator ClickOldWeakThemeInOtherThemeTest()
{
//等待场景加载完成
while (hasLoaded == false)
{
Debug.Log("场景还没加载完成.");
yield return null;
}

//从其他专题状态下点击老弱帮扶专题
clickOtherThemeBtn();
yield return null;
bool isTrue = checkOtherThemeMenuState();
bool isFalse = checkOldWeakMenuState();
Assert.IsTrue(isTrue);
Assert.IsFalse(isFalse);
clickOldWeakThemeBtn();
yield return null;
isTrue = checkOldWeakMenuState();
isFalse = checkOtherThemeMenuState();
Assert.IsTrue(isTrue);
Assert.IsFalse(isFalse);
}

//点击老弱帮扶专题按钮
private bool clickOldWeakThemeBtn()
{
OldWeakBtn = GameObject.Find(OldWeakPage.OldWeakBtnPath);
if (OldWeakBtn)
{
UserSimulateUtils.Click(OldWeakBtn);
return true;
}
return false;
}

//点击老弱帮扶菜单的统计按钮
private void clickStatisticsBtn()
{
GameObject statistics = GameObject.Find(OldWeakPage.StatisticsPath);
if (statistics)
{
UserSimulateUtils.Click(statistics);
}
}

//点击老弱帮扶菜单的服务按钮
private void clickServiceBtn()
{
GameObject service = GameObject.Find(OldWeakPage.ServicePath);
if (service)
{
UserSimulateUtils.Click(service);
}
}

//点击老弱帮扶菜单的关闭按钮
private void clickCloseBtn()
{
GameObject closeBtn = GameObject.Find(OldWeakPage.OldWeakMenuCloseBtn);
if (closeBtn)
{
UserSimulateUtils.Click(closeBtn);
}
}

//点击老弱帮扶专题统计栏下的老弱分布按钮
private void clickOldWeakDistributionBtn()
{
GameObject closeBtn = GameObject.Find(OldWeakPage.OldWeakDistributionPath);
if (closeBtn)
{
bool b = UserSimulateUtils.Click(closeBtn);
}
}

//点击老弱帮扶专题服务栏下的数字设备按钮
private void clickDigitalDeviceBtn()
{
GameObject closeBtn = GameObject.Find(OldWeakPage.DigitalDevicePath);
if (closeBtn)
{
UserSimulateUtils.Click(closeBtn);
}
}

//点击其他主题按钮
private void clickOtherThemeBtn()
{
GameObject otherThemeBtn = GameObject.Find(OldWeakPage.OtherThemeBtnPath);
if (otherThemeBtn)
{
UserSimulateUtils.Click(otherThemeBtn);
}
}

//检查当前是否在统计状态
private bool checkStatisticsState()
{
GameObject statistics = GameObject.Find(OldWeakPage.StatisticsPath);
Toggle statisticsToggle = statistics.GetComponent<Toggle>();
return statisticsToggle.isOn;
}

//检查当前是否在服务状态
private bool checkServiceState()
{
GameObject service = GameObject.Find(OldWeakPage.ServicePath);
Toggle serviceToggle = service.GetComponent<Toggle>();
return serviceToggle.isOn;
}

//检查老弱分布的状态
private bool checkOldWeakDistributionState()
{
GameObject building = GameObject.Find(OldWeakPage.BuildingPath);
return building.activeSelf;
}

//检查数字设备的状态
private bool checkDigitalDeviceState()
{
GameObject projection = GameObject.Find(OldWeakPage.OldWeakServiceProjectionPath);
return projection.activeSelf;
}

//检查老弱帮扶菜单的状态
private bool checkOldWeakMenuState()
{
GameObject oldWeakMenu = GameObject.Find(OldWeakPage.OldWeakMenuPath);
return oldWeakMenu.activeSelf;
}

//检查其他主题的状态
private bool checkOtherThemeMenuState()
{
GameObject otherThemeMenu = GameObject.Find(OldWeakPage.OtherThemeMenuPath);
return otherThemeMenu.activeSelf;
}
}
}

执行测试

总结

在 U3D 中进行单元测试,我们是这么定位 Editmode 和 PlayMode 的:EditMode用于模块内的单元测试(与业务无关);PlayMode用于模拟用户的UI测试(结合业务)。另外关于如何设计测试框架,如何编写测试用例,如何才能方便后续代码的维护等等问题就是以后要考虑的问题了。

1. Unity3D 单元测试框架介绍

Posted on 2019-03-07 | Post modified: 2019-05-03   |   post.updated 2019-05-03 | In Unity3D 单元测试 |

Unity3D 简要介绍

如果是游戏行业的同学估计就没有不知道 Unity3D 的,腾讯的王者荣耀就是基于 Unity3D(简称:U3D)来开发的。在 U3D 中,有三个基本的概念:游戏场景(Scene)、游戏物体(GameObject)和组件(Component)。我们可以借用电影来理解这三个概念,整部电影从开始到结束,由很多场景组成,比如动作电影中的打斗场景在废旧的工厂中进行,废旧的工厂就是游戏场景;废旧工厂中的打斗场景中又由很多的物体组成,比如人、荒废的车床、被吓飞的白鸽等,这些就是游戏物体;废弃的车床又由轮子、各个钢铁部件组成,这里的轮子和部件就是游戏物体的组件。换成 U3D 中的术语就如下图所示:

以上就是 U3D 的简要介绍,如果对 U3D 有兴趣但自己又没有基础的,可以去 SiKi学院 上找免费的入门视频来学习,想要看书的话可以去看看《Unity 5.X 从入门到精通》。

Unity3D 开发环境简要说明

U3D 的开发工具叫 Unity 编辑器,在 下载地址 里选择对应的版本和平台下的 Unity 编辑器即可,比如我这里选择 Windows 平台的最新版本,如截图所示:

U3D 目前只推荐使用 C# 作为脚本开发语言,为了方便编写和调试代码通常不直接使用 Unity 编辑器作为 IDE,我们使用 Microsoft Visual Studio(简称:VS)作为 C# 脚本的开发工具,可以点击 下载链接 下载 VS,具体的安装不再详述。

安装完成 VS 后,打开 Unity 编辑器,在菜单栏上选择 Edit > Preferences…,在弹出的 Unity Preferences 对话框中选择 External Tools > External Script Editor,最后选择 VS 即可,如下图所示:

到此为止开发环境配置完成。

基于 Unity 编辑器的测试框架介绍

NUnit.Framework 介绍

在 Unity 编辑器中已经集成了单元测试框架 NUnit,关于 NUnit 可以 点击链接 了解更多,下面基于一个例子对它进行基本的介绍。

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
using UnityEngine; //基于 Unity 引擎,必须引用
using NUnit.Framework; //引用NUnit测试框架

[TestFixture, Description("测试套")] //一个类对应一个测试套,通常一个测试特性对应一个测试套。
public class UnitTestDemoTest
{
[OneTimeSetUp] //在执行该测试套时首先会执行该函数,在整个测试套中只执行一次。
public void OneTimeSetUp()
{
Debug.Log("OneTimeSetUp");
}

[OneTimeTearDown] //在执行该测试套时最后会执行该函数,在整个测试套中只执行一次。
public void OneTimeTearDown()
{
Debug.Log("OneTimeTearDown");
}

[SetUp]
public void SetUp() //在执行每个用例之前都会执行一次该函数
{
Debug.Log("SetUp");
}

[TearDown] //在执行完每个用例之后都会执行一次该函数
public void TearDown()
{
Debug.Log("TearDown");
}

[TestCase, Description("测试用例1")] //这个函数内部写测试用例
public void TestCase1()
{
Debug.Log("TestCase1");
}

[TestCase, Description("测试用例2")] //这个函数内部写测试用例
public void TestCase2()
{
Debug.Log("TestCase2");
}
}

以上是NUnit的一个例子,我们在 Unity 编辑器上执行看下效果。

可以看到,执行的情况就如代码注释里的说明一样。通常,对于测试用例执行需要的必备条件的代码可以写在 OneTimeSetUp 里面,比如启动测试环境;对于测试用例执行完最后需要的清理工作可以写在 OneTimeTearDown 里面,比如退出测试环境;每个测试用例都需要初始化的公共代码写在 SetUp 里面;跑完每个测试用例都需要清理的公共代码写在 TearDown 里面。

Unity3D 单元测试的两种模式

打开 Unity 编辑器,在菜单栏依次选择 Window > Test Runner,在弹出的对话中可以看到 PlayMode 和 EditMode,这里的 Test Runner 对话框就是执行单元测试的 UI 界面,如果想进一步了解可以点击 Test Runner 官网介绍 进行深入了解。又或者在 Project 视图下依次执行 按下鼠标右键 > Create > Testing 也可以看到有 PlayMode 和 EditMode 字眼,下面是关于它两的截图。

EditMode 测试对于 Unity 编辑器而言,就是指在编辑状态下去测试,而 PlayMode 测试对于 Unity 编辑器而言,就是指在 Unity 运行时的测试。我们可以这么理解,EditMode 是代码的静态测试,测试时不需要被测代码跑起来,其实这里的 EditMode 就是跟其他编程语言的单元测试是一个意思;相对来说,PlayMode 就是代码的动态测试,被测代码需要跑起来,这时的代码环境跟业务场景结合起来。

EditMode 测试模式

上文提到 EditMode 就是传统意义上的单元测试,这里结合个例子介绍下。下面的代码是被测代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//被测代码
public class BuildManager : MonoBehaviour {
public Text moneyText;
public int money = 1500;
......

//被测试函数
public void ChangeMoney(int change = 0)
{
money += change;
moneyText.text = "¥" + money;
}
......
}

在编写 EditMode 模式的测试代码时有一点需要注意下,测试代码需要放在以 Editor 命名的文件夹下(子文件下也行,反正得在 Editor 下)才行,不然 Unity 编辑器无法识别。用例应该放在如下图所示的地方:

然后编写测试 BuildManager 对象的用例:

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
using UnityEngine;
using NUnit.Framework;
using UnityEditor.SceneManagement; //加载场景,实例化被测脚本时需要

[TestFixture]
public class BuildManagerTest : BuildManager
{
private BuildManager buildManager;

[OneTimeSetUp] //打开场景,目的是获取被测对象 BuildManager 实例
public void OneTimeSetUp()
{
EditorSceneManager.OpenScene("Assets/Scenes/MainScene.unity");
buildManager = GameObject.Find("GameManager").GetComponent<BuildManager>();
}

[OneTimeTearDown] //测试完成后销毁测试对象
public void OneTimeTearDown()
{
buildManager = null;
}

[SetUp] //初始化被测对象的属性值
public void SetUp()
{
buildManager.money = 1500;
}

[TearDown] //恢复被测对象的属性值
public void TearDown()
{
buildManager.money = 1500;
}

[TestCase] //测试被测对象的函数逻辑
public void AddMoneyTest()
{;
buildManager.ChangeMoney(100);
Assert.AreEqual(1600, buildManager.money);
}
[TestCase] //测试被测对象的函数逻辑
public void SubMoneyTest()
{
buildManager.ChangeMoney(-1000);
Assert.AreEqual(500, buildManager.money);
}
}

打开 Test Runner 对话框,选中测试用例 AddMoneyTest() 和 SubMoneyTest(),然后点击 Run Selected 即可。如下图所示:

PlayMode 测试模式

如果之前没有创建过 PlayMode 模式下的测试用例,那么打开 Test Runner 对话框,并切换到 PlayMode 页签下,你会看到如下图所示的提示:

然后点击 Enable playmode tests 按钮,再点击 Enable 确定按钮。

继续点击 OK 按钮,接着点击 Create Playmode Test with methods 按钮,发现创建了一测试脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class NewPlayModeTest {

[Test]
public void NewPlayModeTestSimplePasses() {
// Use the Assert class to test conditions.
}

// A UnityTest behaves like a coroutine in PlayMode
// and allows you to yield null to skip a frame in EditMode
[UnityTest]
public IEnumerator NewPlayModeTestWithEnumeratorPasses() {
// Use the Assert class to test conditions.
// yield to skip a frame
yield return null;
}
}

这里重点介绍下 PlayMode 模式下测试用例的用法,如上代码所示,其实 [test] 的注解就是普通的测试标签,[UnityTest]标签才是 PlayMode 测试用例的标签,同时该注解下的函数返回类型是个迭代器 IEnumerator,我们注意到该函数内部还有一条语句 yield return null。其实还有类似的写法,如 yield return new WaitForSeconds()、 yield return new WaitForEndOfFrame()、 yield return new WaitForFixedUpdate() 等。如果学过 Python 我们知道在函数内部中多了 yield 语句它就是生成器,生成器不会一下子返回可迭代对象的所有数据,而是每次返回一条数据,直至迭代完成数据。这里的 yield return 语句有点类似的意思。我们之前说过,PlayMode 测试模式是在代码运行中去测试的,在 U3D 中运行的场景、物体或组件(代码脚本也是一种组件)是每一帧每一帧持续去刷新的,可以把每一帧理解成一张图片,随着时间每一帧每一帧的刷新就形成了视频动画的效果。说回这里的 yield return的作用,它可以等待运行中的场景物体刷新一段时间(可以是等一帧、等一秒等)后再继续执行下面的测试代码。这样的好处就是可以随着时间(帧不断刷新)来操作不同时期的场景、获取不同时期的场景物体信息等,来实现模拟用户或模拟场景的测试。

下面结合Demo来具体介绍下,先说说被系统:敌人(球)会在指定的路线下跑,跑到终点敌人就赢。玩家可以在指定的位置放置炮台,只要敌人走到攻击范围内,炮台就射击。现在用 PlayMode 模式简单测试下炮台的子弹是否会移动。下面是测试的代码。

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
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class playmodetest
{
Waypoints waypoints;
Bullet bullet;
Enemy target;
Vector3 initPosition;
[SetUp] //初始化测试环境,然后获取子弹的初始位置
public void SetUp()
{
GameObject waypointsGo = new GameObject("Waypoints");
waypoints = waypointsGo.AddComponent<Waypoints>();
GameObject bulletGo = new GameObject("bullet");
bullet = bulletGo.AddComponent<Bullet>();
initPosition = bullet.transform.position;

GameObject Enemy1 = Resources.Load<GameObject>("Prefab/Enemy1/Enemy1");
Enemy enemy = Enemy1.GetComponent<Enemy>();
target = GameObject.Instantiate(enemy, enemy.transform.position, enemy.transform.rotation);
bullet.SetTarget(target.transform);
}


[UnityTest] //等待下一帧,然后比较子弹的当前位置是否与初始位置一致
public IEnumerator CheckBullet()
{
yield return null;
Assert.AreNotEqual(initPosition, bullet.transform.position);
}
}

测试结果显示测试通过:

好了,U3D的单元测试框架就先介绍到这里,后面结合实际的项目再做介绍。

ChenLiangtang

好好学习掌握知识,多多省思提高认知。

8 posts
5 categories
13 tags
© 2019 ChenLiangtang |
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4