Notes¶
Table of content¶
ExtJS¶
Ext JS¶
项目管理 创建项目:指定SDK,指定仅生成Classic项目 sencha -sdk ~/ext-6.2.0/ generate app classic BeApp ./BeApp
项目结构
bootstrap.* 仅开发环境使用,微加载文件
ext/ 仅开发环境使用,库文件夹
build/ 仅开发环境使用,构建文件夹
以上文件不需要进行代码版本管理,可以通过install + build命令重建。
sencha app install –framework=~/ext-6.2.0/ sencha app build
项目编辑 添加模块: sencha generate model User id:int,name,email
添加视图: sencha generate view foo.Thing
添加视图:指定基类 sencha generate view -base Ext.tab.Panel foo.Thing
添加控制器: sencha generate controller Central
预览项目 sencha app watch
构建项目: sencha app build production
// 查询数据
onClickSearch: function () {
console.log('查询数据');
var grid = this.lookupReference('gridDictionaryForMain');
var gridStore = grid.getStore();
gridStore.getProxy().setExtraParam('sex', '1');
gridStore.reload(
{
callback: function (record, option, success) {
if (!success) {
Ext.Msg.alert('提示信息', '操作失败');
}
}
});
},
HTML,CSS,JavaScript¶
HTML¶
总结¶
- 一套规则,浏览器认识的规则
- 开发者
- 学习HTML规则
- 开发后台程序
- 写HTML文件(充当模板的作用) *****
- 数据库获取数据,然后替换到HTML文件的指定位置(web框架)
- 本地测试
- 找到文件路径,直接使用浏览器打开
- PyCharm打开测试
- 编写HTML文件
- doctype对应关系
- html标签,标签内部可以写属性 (整个html文件只有一个html标签)
- 注释:
- 标签分类
- 自闭合标签(
<meta charset="UTF-8">
) 自闭合标签可以使用<br/>
或<br>
推荐在>
前面写/
- 主动闭合标签(
<title>长廊月</title>
)
- 自闭合标签(
- head标签中
<meta>
-> 编码,跳转,刷新,关键字,描述,IE兼容<meta http-equiv="X-UA-Compatible" content="IE=IE9;IE=IE8;"/>
- body标签
- p标签,段落
- br,换行
- 所有标签分为两类: 块级标签,行内标签(内联标签)
- 块级标签
- H系列(加大加粗),p标签(段落和段落之间有间距),div(白板)
- 行内标签
- span(白板)
- 标签之间可以嵌套
- 标签存在的意义,css操作,js操作
- 20个常用标签
<!DOCTYPE html>
<!-- html标签, lang为标签内部的属性 -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="http://www.baidu.com">baidu</a>
</body>
</html>
网页特殊符号¶
空格
>
<
标签¶
meta¶
提供有关页面的元信息,例如: 页面编码,刷新,跳转,针对搜索引擎和更新频度的描述和关键词
<meta http-equiv="Refresh" content="30">
3秒自动跳转(可临时用于将原网站跳转到指定网站)
<meta http-equiv="Refresh" content="3;Url=http://www.baidu.com">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Refresh" content="2">
<!--<meta http-equiv="Refresh" content="3;Url=http://www.baidu.com">-->
<title>Title</title>
</head>
<body>
<a href="http://www.baidu.com">baidu</a>
</body>
</html>
<meta name="keywords" content="小色,xxx">
例如 : 作者
<meta http-equiv="X-UA-Compatible" content="IE=IE9;IE=IE8;"/>
Title¶
网页头部信息
link¶
style¶
script¶
p¶
表示段落,默认段落之间是有间隔的
a¶
- 锚 href=“#某个标签的ID” 标签的ID不允许重复
- target 属性,
_black
表示在新的页面打开 - 菜单跳转
<a href="http://www.baidu.com" target="_black">baidu</a>
跳转,id不能重复
<body>
<a href="#i1"> 第一章 </a>
<a href="#i2"> 第二章 </a>
<a href="#i3"> 第三章 </a>
<a href="#i4"> 第四章 </a>
<div id="i1" style="height: 600px;"> 第一章内容 </div>
<div id="i2" style="height: 600px;"> 第二章内容 </div>
<div id="i3" style="height: 600px;"> 第三章内容 </div>
<div id="i4" style="height: 600px;"> 第四章内容 </div>
</body>
img¶
- src
- alt
- title
默认有一个1px的边框
<body>
<!--a标签里面嵌套img标签,实现点击图片跳转-->
<a href="http://www.baidu.com">
<!--不设置图片大小,将显示整张图片-->
<!--<img src="1.jpg">-->
<!--设置图片大小-->
<img src="1.jpg" style="height: 200px;width: 200px;">
</a>
</body>
当图片不存在,使用alt属性可以出现下面效果
<img src="1.jg" style="height: 200px;width: 200px;" alt="风景">

html-03-a
使用title属性,当鼠标悬停图片上的时候,会显示title属性
<img src="1.jpg" style="height: 200px;width: 200px;" alt="风景" title="大风景">

html-04-a
H标签¶
H1
H2
H3
H4
H5
H6
input¶
- input type=“text” name属性,value=“xx”,内部默认值
- input type=“password” name属性,value=“xx”
- input type=“button” value=“登录”,按钮
- input type=“submit” value=“提交”,提交按钮,表单
- input type=“radio” 单选框 value, checked=“checked”,name属性(name相同则互斥)
- input type=“checkbox” 复选框 value, checked=“checked”,name属性(批量获取数据)
- input type=“file” 依赖form表单的一个属性
<form enctype="multipart/form-data">
- 此属性会将文件一点点发给服务端
- input type=“reset” 重置
- placeholder 属性 输入框添加提示
<body>
<input type="text">
<input type="password">
<input type="button" value="登录1">
<input type="submit" value="登录2">
</body>
placeholder示例
<!-- placeholder 输入框里面添加提示 -->
<input type="text" placeholder="用户名">
form¶
form 表单
action,动作,将内容提交给后台,可以是一个url
使用name属性,将用户输入的内容组成一个字典提交给后台
<body>
<!--method 指定GET POST,GET会将输入的内容放在url里面,提交后台-->
<!--跟POST相比,没有安全不安全的区别-->
<form action="http://localhost:8888/index" method="POST">
<input type="text" name="user">
<input type="text" name="email">
<input type="password" name="pwd">
<!-- {'user': '用户名', 'email': '邮箱','pwd': '密码'} -->
<input type="button" value="登录1">
<input type="submit" value="登录2">
</form>
</body>

html-01
<body>
<form action="https://www.sogou.com/web">
<input type="text" name="query">
<input type="submit" value="搜索">
</form>
</body>
<form>
<div>
<p>请选择性别: </p>
男: <input type="radio" name="gender" value="1">
女: <input type="radio" name="gender" value="2">
</div>
<input type="submit" value="提交">
</form>
<body>
<form>
<div>
<p>请选择性别: </p>
<!-- name属性相同则互斥,即二选一 -->
男: <input type="radio" name="gender" value="1">
女: <input type="radio" name="gender" value="2">
<p>爱好</p>
<!-- name 属性,批量获取数据 -->
篮球: <input type="checkbox" name="favor" value="1">
<!-- checked="checked" 表示默认选中-->
台球: <input type="checkbox" name="favor" value="2" checked="checked">
足球: <input type="checkbox" name="favor" value="3">
网球: <input type="checkbox" name="favor" value="4">
<p>技能</p>
写代码 <input type="checkbox" name="skill" checked="checked">
xx <input type="checkbox" name="skill">
<p>上传文件</p>
<input type="file" name="fname">
</div>
<input type="submit" value="提交">
<!-- 重置所有选择 -->
<input type="reset" value="重置">
</form>
</body>
name,内部option value,提交到后台,size,multiple
<div>
<select name="city">
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">南京</option>
<!-- 默认选择 -->
<option value="4" selected="selected">天津</option>
<option value="5">成都</option>
</select>
</div>
<div>
<!-- 同时显示多少个, 10个 -->
<select name="city" multiple="multiple" size="10">
<option value="1">北京</option>
<option value="2">上海</option>
<option value="3">南京</option>
<option value="4" selected="selected">天津</option>
<option value="5">成都</option>
</select>
</div>
<div>
<select>
<optgroup label="北京市">
<option>海淀区</option>
<option>朝阳区</option>
</optgroup>
<optgroup label="湖北省">
<option>武汉市</option>
<option>咸宁市</option>
</optgroup>
</select>
</div>

html-02
列表¶
- ul
- li
- ol
- li
- dl
- dt -dd
<body>
<!--无序列表-->
<ul>
<li>dfa</li>
<li>fda</li>
<li>fda</li>
<li>fadf</li>
</ul>
<!--有序列表-->
<ol>
<li>dafd</li>
<li>dafd</li>
<li>dafd</li>
<li>dafd</li>
</ol>
</body>

html-05-li
<dl>
<dt>ttt</dt>
<dd>ddd</dd>
<dd>ddd</dd>
<dd>ddd</dd>
<dt>ttt</dt>
<dd>ddd</dd>
<dd>ddd</dd>
<dd>ddd</dd>
</dl>

html-06-li
表格¶
- table
- thead
- tr
- th
- tr
- tbody
- tr
- td
- tr
- thead
- colspan = “” 横向合并
- rowspan = “” 纵向合并
<body>
<!-- border 表格增加边框 -->
<table border="1">
<tr>
<td>第一行,第1列</td>
<td>第一行,第2列</td>
<td>第一行,第3列</td>
</tr>
<tr>
<td>第二行,第1列</td>
<td>第二行,第2列</td>
<td>第二行,第3列</td>
</tr>
</table>
</body>
完整的table有 thead
和 tbody
<body>
<table border="1">
<thread>
<tr>
<!-- th 表头 会加粗居中-->
<th>IP</th>
<th>端口</th>
<th>操作</th>
</tr>
</thread>
<tbody>
<tr>
<td>1.1.1.1</td>
<td>80</td>
<td>
<a href="1.html">查看详情</a>
<a href="#">修改</a>
</td>
</tr>
<tr>
<td>1.1.1.1</td>
<td>80</td>
<td>
<a href="1.html">查看详情</a>
<a href="#">修改</a>
</td>
</tr>
</tbody>
</table>
</body>
合并单元格
<body>
<table border="1">
<tr>
<th>表头</th>
<th>表头</th>
<th>表头</th>
<th>表头</th>
</tr>
<tr>
<td colspan="2">1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
<td rowspan="3">1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
</table>
</body>

html-07-table
label¶
用于点击文件,使得关联的标签获取光标
<body>
<label for="username">用户名: </label>
<input id="username" type="text" name="user">
</body>

html-08-label
fieldset¶
不常用,知道就行
- filedset
- legend
<body>
<filedset>
<legend>登录</legend>
<label for="usernmae">用户名: </label>
<input id="username" type="text" name="user"/>
<br/>
<label for="pwd">密 码: </label>
<input id="pwd" type="password" name="pwd"/>
</filedset>
</body>
CSS¶
总结¶
- 在标签上设置style属性
background-color: #2459a2;
height: 48px;
- 编写css样式
- 标签的style属性
- style标签,写在head标签里面,style标签中写样式
- 选择器
- id选择器
#i1 {}
- class选择器 (最常用) *****
.c1 {}
- 标签选择器 (所有div设置此样式)
div {}
- 关联选择器
span div {}
- 层级选择器(空格)
.c1 .c2 div {}
- 组合选择器(逗号)
#c1,.c2,div {}
- 属性选择器
.c1[name="xxx"] { background-color: #ff6fa6; }
- 属性优先级: 标签上style优先,编写顺序,就近原则(或者使用!important,绝对生效)
- id选择器
- css样式也可以写在单独的文件中
<link rel="stylesheet" href="xxx.css"/>
- 注释
/* */
设置id之后,会使用head里面的style设置的样式
选择器¶
- id选择器
- class选择器
- 标签选择器
- 关联选择器
- 层级选择器
- 组合选择器
- 属性选择器
id选择器¶
class选择器¶
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
<!-- id 选择器 -->
#i1{
background-color: #2459a2;
height: 48px;
}
<!-- class 选择器 -->
.c1 {
background-color: #ffff5a;
height: 20px;
}
</style>
</head>
<body>
<div id="i1">xx</div>
<span class="c1">23</span>
<div class="c1">32</div>
</body>
</html>

css-01-选择器
标签选择器¶
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
<!-- 标签选择器 在所有div上设置此样式-->
div {
background-color: deeppink;
height: 20px;
}
</style>
</head>
<body>
<div>123</div>
<span>dfd</span>
<div>1xxx</div>
</body>

css-02-标签选择器
关联选择器¶
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
<!-- span 下的div 标签才会用此样式 -->
span div {
background-color: #00a7d0;
height: 30px;
}
</style>
</head>
<body>
<div>fdsfsd</div>
<span>
<div>span div</div>
</span>
</body>
层级选择器¶
最底层的才会应用style
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.c1 .c2 div {
background-color: #00a7d0;
height: 30px;
}
</style>
</head>
<body>
<div>fdsfsd</div>
<span>
<div class="c1">
<span class="c2">
<div>1234</div>
</span>
</div>
</span>
</body>
</html>

css-03-层级选择器
组合选择器¶
<style>
.c1,.c2,#i1,div {
background-color: #00a7d0;
height: 30px;
}
</style>
属性选择器¶
对选择到的标签通过属性再进行一次筛选
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.c1[name="xxx"] {
background-color: #ff6fa6;
}
</style>
</head>
<body>
<div class="c1" name="xxx">name=xxx</div>
<div class="c1">class=c1</div>
</body>
</html>

css-04-属性选择器
属性优先级¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.c2 {
font-size: 58px;
color: black;
}
.c1 {
background-color: red;
color: white;
}
</style>
</head>
<body>
<div class="c1 c2" style="color: darkslateblue;">qwe</div>
</body>
</html>

css-05-属性优先级
从文件调用css¶
本质就是把文件里面的css 样式拿到head里面
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 加载css文件 如果是上层目录就是../-->
<link rel="stylesheet" href="css/commons.css">
</head>
<body>
<div class="c1 c2" style="color: darkslateblue;">qwe</div>
</body>
</html>
css/commons.css文件内容
.c2 {
font-size: 58px;
color: black;
}
.c1 {
background-color: red;
color: white;
}
sytle¶
边框¶
- border: 1px solid red; 宽度,样式,颜色
- border-left
- height 高度,百分比,像素
- width 宽度,百分比,像素
- text-align:center 水平方向居中
- line-height 垂直方向根据标签高度
- color 字体颜色
- font-size 字体大小
- font-weight 字体加粗
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="width: 80%;
height: 48px;
border: 1px solid red;
font-size: 16px;
text-align: center;
line-height: 48px;
font-weight: bold;
">qwe</div>
</body>
</html>
<body>
<div style="width: 20%; background-color: red; float: left;">1</div>
<div style="width: 80%; background-color: black; float: right;">2</div>
</body>
背景¶
float¶
- 让标签浮起来,块级标签也可以堆叠
- 老子管不住
<div style="clear: both;"></div>
<body>
<div style="width: 20%; background-color: red; ">1</div>
<div style="width: 80%; background-color: pink;">2</div>
<div style="width: 20%; background-color: red; float: left;">1</div>
<div style="width: 80%; background-color: pink; float: right;">2</div>
</body>

css-06-float
下面的示例,子孙没有将父容器撑起来
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="width: 300px;border: 1px solid red;">
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
</div>
</body>
</html>

css-07-border
解决办法 加一个<div style="clear: both;"></div>
即可
<body>
<div style="width: 300px;border: 1px solid red;">
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
...省略...
<div style="width: 96px; height: 30px; border: 1px solid green;float: left;"></div>
<!-- 使用 clear: both; -->
<div style="clear: both;"></div>
</div>
</body>
display¶
- display: inline;
- display: block;
- display: inline-block;
- 具有inline,默认自己有多少占多少
- 具有block,可以设置高度,宽度,padding margin
- display: none;
- 让标签消失,视频网站开灯关灯就是这样实现的
- 行内标签: 无法设置高度,宽度,padding margin
- 块级标签: 设置高度,宽度,padding margin
让块级标签具有行内标签的属性
<body>
<div style="background-color: red; display: inline;">qwe</div>
<div style="background-color: red; display: block;">qwe</div>
</body>

css-08-display
padding margin(0,auto)¶
边距
- margin: 外边距
- padding: 内边距
padding: 0 10px 0 10px;
上右下左
auto 左右两边居中
margin:0 auto; 顶端跟浏览器没有间隙
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.pg-header {
height: 38px;
background-color: purple;
}
</style>
</head>
<body style="margin: 0 auto;">
<div class="pg-header">1</div>
</body>
</html>
position¶
- fiexd 固定在页面的某个位置,滚轮滚动也会在该位置
- relative + absolute
relative + absolute
<div style="position: relative;">
<div style="position: absolute; left: 0;bottom: 0;"></div>
</div>
以父标签为基准布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body style="margin: 0 auto;">
<div style="position: relative; width: 500px; height: 200px; border: 1px solid red; margin: 0 auto;">
<div style="position: absolute; left: 0;bottom: 0; width: 50px; height: 50px;background-color: black;"></div>
</div>
<div style="position: relative;width: 500px;height: 200px;border: 1px solid red;margin: 0 auto;">
<div style="position: absolute;right: 0;bottom: 0;width: 50px;height: 50px;background-color: lightskyblue;"></div>
</div>
<div style="position: relative;width: 500px;height: 200px; border: 1px solid red; margin: 0 auto;">
<div style="position: absolute;right: 0;top: 0;width: 50px;height: 50px;background-color: black ;"></div>
</div>
</body>
</html>

css-10-position
opacity¶
透明 0~1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- z-index 数字越大就越在上层 -->
<!-- 可以加上 display:none 先隐藏, 后面使用JavaScript实现,点击之后出现 -->
<!-- margin-left: -250px;margin-top: -250px; 配合实现容器居中 -->
<div style="z-index: 10;position: fixed;top: 50%;left: 50%;background-color: white;width: 500px;height: 500px;margin-left: -250px;margin-top: -250px;">
<!-- placeholder 输入框里面添加提示 -->
<input type="text" placeholder="用户名">
<input type="password" placeholder="密码">
</div>
<!-- 遮罩层 opacity 透明度 -->
<div style="z-index: 9; position: fixed; background-color: black;
top: 0;
bottom: 0;
right: 0;
left: 0;
opacity: 0.5;
"></div>
<div style="height: 5000px;background-color: green;">aqweq</div>
</body>
</html>

css-11-opacity
background¶
hover¶
当鼠标移动到标签上是,设置的属性才生效
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.pg-header {
position: fixed;
right:0;
left:0;
top:0;
height: 48px;
background-color: cadetblue;
line-height: 48px;
}
.pg-body {
margin-top: 50px;
}
.w{
/* 固定宽度,防止窗口拖小之后页面变形 */
width: 980px;
margin:0 auto
}
.pg-header .menu {
display: inline-block;
/* 设置内边距属性 上右下左 */
padding: 0 10px 0 10px;
background-color: cadetblue;
color: black;
}
/* hover 当鼠标移动到当前标签上时,以下css属性才生效 */
.pg-header .menu:hover {
background-color: lightskyblue;
}
</style>
</head>
<body>
<div class="pg-header">
<div class="w">
<a class="logo">LOGO</a>
<a class="menu">全部</a>
<a class="menu">xx</a>
<a class="menu">qq</a>
</div>
</div>
<div class="pg-body">
<div class="w">a</div>
</div>
</body>
</html>
实例¶
导航栏¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.pg-header {
height: 38px;
background: lightskyblue;
position: fixed;
top: 0;
right: 0;
left: 0;
}
.c1 {
line-height: 38px;
float: left;
margin-left: 20%;
}
</style>
</head>
<body style="margin: 0 auto;">
<div class="pg-header">
<div style="width: 980px; margin: 0;">
<div style="float: left; line-height: 38px;">收藏本站</div>
<div style="float: right;">
<a style="line-height: 38px;">登录</a>
<a style="line-height: 38px;">注册</a>
</div>
<div style="clear: both;"></div>
</div>
</div>
<div style="height: 5000px;"></div>
</body>
</html>
页面右下角添加 返回顶端 按钮¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.pg-header {
height: 38px;
background: lightskyblue;
}
.c1 {
line-height: 38px;
float: left;
margin-left: 20%;
}
</style>
</head>
<body style="margin: 0 auto;">
<div class="pg-header">
<div style="width: 980px; margin: 0;">
<div style="float: left; line-height: 38px;">收藏本站</div>
<div style="float: right;">
<a style="line-height: 38px;">登录</a>
<a style="line-height: 38px;">注册</a>
</div>
<div style="clear: both;"></div>
</div>
</div>
<div style="height: 5000px;"></div>
<div onclick="goTop();" style="width: 70px; height: 48px;
line-height: 50px;
background-color: #00a7d0; color: white;
position: fixed;
bottom: 20px;
right: 20px;
margin: 0 auto;
">返回顶端</div>
<script>
function goTop() {
document.body.scrollTop = 0;
}
</script>
</body>
</html>

css-09-返回顶端
DOM¶
文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口。它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式。我们最为关心的是,DOM把网页和脚本以及其他的编程语言联系了起来。DOM属于浏览器,而不是JavaScript语言规范里的规定的核心内容。
总结¶
- 查找标签
- 直接查找
document.getElementById('i1')
根据ID获取一个标签document.getElementsByName
根据name属性获取标签集合document.getElementsByClassName(c1)
根据class属性获取标签集合 (返回列表)document.getElementsByTagName('div')
根据标签名获取标签集合
- 间接查找
tag = document.getElementById('i2')
- parentElement // 父节点标签元素
- children // 所有子标签
- firstElementChild // 第一个子标签元素
- lastElementChild // 最后一个子标签元素
- nextElementtSibling // 下一个兄弟标签元素
- previousElementSibling // 上一个兄弟标签元素
- 直接查找
- 操作标签
- innerText
- 获取标签中的文本内容
标签.innerText
- 设置标签中的文本内容
标签.innerText = "xxx"
- 获取标签中的文本内容
- className
- tag.className 直接整体做操作
- tag.classList.add(‘样式名’) 添加指定样式
- tag.classList.remove(‘样式名’) 删除指定样式
- checkbox
- 获取值 checkbox对象.checked
- 设置值 checkbox对象.checked = true (或false)
- innerText
查找元素¶
直接查找¶
document.getElementById
根据ID获取一个标签document.getElementsByName
根据name属性获取标签集合document.getElementsByClassName
根据class属性获取标签集合document.getElementsByTagName
根据标签名获取标签集合
间接查找¶
- parentNode // 父节点
- childNodes // 所有子节点
- firstChild // 第一个子节点
- lastChild // 最后一个子节点
- nextSibling // 下一个兄弟节点
- previousSibling // 上一个兄弟节点
- parentElement // 父节点标签元素
- children // 所有子标签
- firstElementChild // 第一个子标签元素
- lastElementChild // 最后一个子标签元素
- nextElementtSibling // 下一个兄弟标签元素
- previousElementSibling // 上一个兄弟标签元素
<body>
<div>
<div>
1
</div>
<div></div>
</div>
<div>
<div id="i2">2</div>
<div></div>
</div>
<div>
<div>3</div>
<div></div>
</div>
</body>
tag = document.getElementById('i2')
<div id="i2">2</div>
tag.parentElement
tag.parentElement.children
tag.parentElement.firstElementChild
tag.parentElement.lastElementChild
tag.parentElement.lastElementChild.nextElementSibling
tag.parentElement.nextElementSibling
tag.parentElement.lastElementChild
tag.parentElement.lastElementChild.previousElementSibling
tag.previousElementSibling
tag.nextElementSibling
操作¶
内容¶
- innerText 文本
- inneHTML HTML内容
- value 值
- outerText
<body>
<div id="i1">我是i1</div>
<a>qwe</a>
<a>q32</a>
<a>qwe312</a>
</body>

js-03-get

js-04-get
属性¶
- attributes 获取所有标签属性
- setAttribute(key,value) 设置标签属性
- getAttribute(key) 获取指定标签属性
实例¶
模态框¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide {
display: none;
}
.c1 {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: black;
opacity: 0.5;
z-index: 9;
}
.c2 {
width: 500px;
height: 500px;
background-color: white;
position: fixed;
left: 50%;
top: 50%;
margin-left: -250px;
margin-top: -250px;
z-index: 10;
}
</style>
</head>
<body>
<div>
<input type="button" value="添加" onclick="showModel()"/>
<input type="button" value="全选" onclick="chooseAll()"/>
<input type="button" value="反选" onclick="reverseAll()"/>
<input type="button" value="取消" onclick="cancleAll()"/>
<table>
<thead>
<tr>
<th>选择</th>
<th>IP</th>
<th>端口</th>
</tr>
</thead>
<tbody id="tb">
<tr>
<td>
<input type="checkbox"/>
</td>
<td>192.168.1.2</td>
<td>8080</td>
</tr>
<tr>
<td><input type="checkbox"/></td>
<td>192.168.2.1</td>
<td>80</td>
</tr>
</tbody>
</table>
</div>
<!-- 遮罩层开始 -->
<div id="i1" class="c1 hide"></div>
<!-- 遮罩层结束 -->
<!-- 弹出框 -->
<div id="i2" class="c2 hide">
<p>
<input id="d1" type="text" name="ip" value="" placeholder="IP"/>
<input id="d2" type="text" name="port" value="" placeholder="端口"/>
</p>
<p>
<input type="button" value="取消" onclick="hideModel()">
<input type="button" value="确定" onclick="addServer()">
</p>
</div>
<script>
// 显示弹出框
function showModel() {
document.getElementById('i1').classList.remove('hide');
document.getElementById('i2').classList.remove('hide');
}
// 全选
function chooseAll(){
var tbody = document.getElementById('tb');
var tr_list = tbody.children;
for(var i=0;i<tr_list.length;i++){
var current_tr = tr_list[i];
var checkbox = current_tr.children[0].children[0];
checkbox.checked = true;
}
}
// 反选
function reverseAll(){
var tbody = document.getElementById('tb');
var tr_list = tbody.children;
for(var i=0;i<tr_list.length;i++){
var current_tr = tr_list[i];
var checkbox = current_tr.children[0].children[0];
if(checkbox.checked){
checkbox.checked = false;
}else{
checkbox.checked = true;
}
}
}
// 取消所有
function cancleAll() {
var tbody = document.getElementById('tb');
var tr_list = tbody.children;
for(var i=0;i<tr_list.length;i++){
var current_tr = tr_list[i];
var checkbox = current_tr.children[0].children[0];
checkbox.checked = false;
}
}
// 隐藏弹出框
function hideModel() {
document.getElementById('i1').classList.add('hide');
document.getElementById('i2').classList.add('hide');
}
// 往表格添加内容
function addServer(){
var tag = document.getElementById('tb');
// 创建标签
var new_tag = document.createElement('tr');
// 获取用户输入内容
var ip = document.getElementById("d1").value;
var port = document.getElementById("d2").value;
// 设置标签内容
new_tag.innerHTML = '<td><input type="checkbox"/></td><td>' + ip + '</td><td>' + port +'</td>';
// 添加到表格
tag.appendChild(new_tag);
hideModel()
}
</script>
</body>
</html>
左侧菜单栏¶
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide {
display: none;
}
.item .header {
background-color: cadetblue;
height: 35px;
color: white;
line-height: 35px;
}
</style>
</head>
<body>
<div style="height: 48px;"></div>
<div style="width: 300px;">
<div class="item">
<div id="i1" class="header" onclick="changeMenu('i1');">菜单一</div>
<div class="content">
<div>内容1</div>
<div>内容2</div>
<div>内容3</div>
</div>
</div>
<div class="item">
<div id="i2" class="header" onclick="changeMenu('i2');">菜单二</div>
<div class="content hide">
<div>内容1</div>
<div>内容2</div>
<div>内容3</div>
</div>
</div>
<div class="item">
<div id="i3" class="header" onclick="changeMenu('i3');">菜单三</div>
<div class="content hide">
<div>内容1</div>
<div>内容2</div>
<div>内容3</div>
</div>
</div>
<div class="item">
<div id="i4" class="header" onclick="changeMenu('i4');">菜单四</div>
<div class="content hide">
<div>内容1</div>
<div>内容2</div>
<div>内容3</div>
</div>
</div>
</div>
<script>
function changeMenu(nid) {
var tag = document.getElementById(nid);
var item_list = tag.parentElement.parentElement.children;
// 隐藏所有
for(var i=0;i<item_list.length;i++){
var current_item = item_list[i];
current_item.children[1].classList.add('hide');
}
// 打开当前标签
tag.nextElementSibling.classList.remove('hide')
}
</script>
</body>
</html>
JavaScript¶
独立的语言,浏览器带有JavaScript解释器
总结¶
- JavaScript代码存在形式
- head标签中,body代码块底部(推荐)
<script></script>
- 文件中
<script src="JavaScript文件路径"></script>
- PS: HTML代码从上往下执行,如果head中的js代码耗时严重,会导致用户长时间无法看到页面,而写在body标签最后(最后加载),不会影响到用户看到页面,只是js实现的一些效果较慢.
- head标签中,body代码块底部(推荐)
- 注释
- 单行注释
// 注释内容
- 多行注释
/* 注释内容 */
- 单行注释
- 基本数据类型
- 数字
- 字符串
- charAt(索引位置)
- substring(起始位置,结束位置) 不包含结束位置
- lenght 获取当前字符串长度
- 布尔类型
- true
- false
- 数组
- 字典(JavaScript实际上没有字典,而是一个对象)
- null 关键字,特殊值,描述“空值”
- undefined 特殊值,表示变量未定义
- 注: 数字,布尔值,null,undefined,字符串是不可变的
- 变量
- 全局变量
name="xxx"
- 局部变量
var name="xxx"
- 全局变量
- 条件语句
- 循环
- for循环(两种)
- while循环
- 定时器
setInterval('func()',间隔时间,单位为秒);
变量¶
- 全局变量
name="xxx"
- 局部变量
var name="xxx"
基本数据类型¶
数字¶
JavaScript中不区分整数值和浮点数值,JavaScript中所有数字均用浮点数值表示。
转换
- parseInt(..) 将某值转换成数字,不成功则NaN
- parseFloat(..) 将某值转换成浮点数,不成功则NaN
特殊值
- NaN,非数字。可使用 isNaN(num) 来判断。
- Infinity,无穷大。可使用 isFinite(num) 来判断。
字符串¶
字符串是由字符组成的数组,但在JavaScript中字符串是不可变的:可以访问字符串任意位置的文本,但是JavaScript并未提供修改已知字符串内容的方法。
常见功能
age = 18
18
name = "yangxxx"
"yangxxx"
name.charAt()
"y"
/* 索引位置,从0开始 */
name.charAt(0)
"y"
name.charAt(3)
"g"
/* 起始位置,结束位置(不包含结束位置) */
name.substring(0,3)
"yan"
name.length
7
数组¶
JavaScript中的数组类似于python中的列表
obj.length 数组的大小
obj.push(ele) 尾部追加元素
obj.pop() 尾部获取一个元素
obj.unshift(ele) 头部插入元素
obj.shift() 头部移除元素
obj.splice(start, deleteCount, value, ...) 插入、删除或替换数组的元素
obj.splice(n,0,val) 指定位置插入元素
obj.splice(n,1,val) 指定位置替换元素
obj.splice(n,1) 指定位置删除元素
obj.slice( ) 切片
obj.reverse( ) 反转
obj.join(sep) 将数组元素连接起来以构建一个字符串
obj.concat(val,..) 连接数组
obj.sort( ) 对数组元素进行排序
其他¶
序列化¶
JSON.stringify(obj) 序列化
JSON.parse(str) 反序列化
转义¶
decodeURI( ) URl中未转义的字符
decodeURIComponent( ) URI组件中的未转义字符
encodeURI( ) URI中的转义字符
encodeURIComponent( ) 转义URI组件中的字符
escape( ) 对字符串转义
unescape( ) 给转义字符串解码
URIError 由URl的编码和解码方法抛出
正则表达式¶
- /…/ 用于定义正则表达式
- /…/g 表示全局匹配
- /…/i 表示不区分大小写
- /…/m 表示多行匹配
JS正则匹配时本身就是支持多行,此处多行匹配只是影响正则表达式^和来匹配换行的内容)
var pattern = /^Java\w*/gm;
var text = "JavaScript is more fun than \nJavaEE or JavaBeans!";
result = pattern.exec(text)
result = pattern.exec(text)
result = pattern.exec(text)
注:定义正则表达式也可以 reg= new RegExp()
JavaScript中支持正则表达式,其主要提供了两个功能:
test(string) 检查字符串中是否和正则匹配
n = 'uui99sdf'
reg = /\d+/
reg.test(n) ---> true
# 只要正则在字符串中存在就匹配,如果想要开头和结尾匹配的话,就需要在正则前后加 ^和$
exec(string) 获取正则表达式匹配的内容,如果未匹配,值为null,否则,获取匹配成功的数组。
获取正则表达式匹配的内容,如果未匹配,值为null,否则,获取匹配成功的数组。
非全局模式
获取匹配结果数组,注意:第一个元素是第一个匹配的结果,后面元素是正则子匹配(正则内容分组匹配)
var pattern = /\bJava\w*\b/;
var text = "JavaScript is more fun than Java or JavaBeans!";
result = pattern.exec(text)
var pattern = /\b(Java)\w*\b/;
var text = "JavaScript is more fun than Java or JavaBeans!";
result = pattern.exec(text)
全局模式
需要反复调用exec方法,来一个一个获取结果,直到匹配获取结果为null表示获取完毕
var pattern = /\bJava\w*\b/g;
var text = "JavaScript is more fun than Java or JavaBeans!";
result = pattern.exec(text)
var pattern = /\b(Java)\w*\b/g;
var text = "JavaScript is more fun than Java or JavaBeans!";
result = pattern.exec(text)
3、字符串中相关方法
obj.search(regexp) 获取索引位置,搜索整个字符串,返回匹配成功的第一个位置(g模式无效)
obj.match(regexp) 获取匹配内容,搜索整个字符串,获取找到第一个匹配内容,如果正则是g模式找到全部
obj.replace(regexp, replacement) 替换匹配替换,正则中有g则替换所有,否则只替换第一个匹配项,
$数字:匹配的第n个组内容;
$&:当前匹配的内容;
$`:位于匹配子串左侧的文本;
$':位于匹配子串右侧的文本
$$:直接量$符号
滚动字幕¶
<body>
<ul>
<li id="l1">欢迎xxx莅临指导</li>
</ul>
</body>

js-01-滚动字幕
<body>
<ul>
<li id="l1">欢迎xxx莅临指导</li>
</ul>
<script>
function func(){
var tag = document.getElementById('l1');
var content = tag.innerText;
var f = content.charAt(0);
var s = content.substring(1,content.length);
var new_content = s + f;
tag.innerText = new_content;
}
setInterval('func()',300);
</script>
</body>
循环¶
for循环¶
a = [11,22,33,44]
for (var item in a){
console.log(item);
}
a = {"k1":"v1","k2":"v2"}
for (var item in a){
console.log(a[item]);
}
for循环第二种
for (var i=0;i<10;i++){
console.log(i)
}

js-02-for
while¶
while(条件){
// break;
// continue;
}
异常处理¶
try {
//这段代码从上往下运行,其中任何一个语句抛出异常该代码块就结束运行
}
catch (e) {
// 如果try代码块中抛出了异常,catch代码块中的代码就会被执行。
//e是一个局部变量,用来指向Error对象或者其他抛出的对象
}
finally {
//无论try中代码是否有异常抛出(甚至是try代码块中有return语句),finally代码块中始终会被执行。
}
注:主动跑出异常 throw Error('xxxx')
条件语句¶
if¶
if(条件){
}else if(条件){
}else{}
switch¶
switch(name){
case '1':
age = 123;
break;
case '2':
age = 456;
break;
default :
age = 777;
}
函数¶
函数基本分为下面三类
// 普通函数
function func(arg){
return true;
}
// 匿名函数
var func = function(arg){
return "tony";
}
// 自执行函数
(function(arg){
console.log(arg);
})('123')
注意:对于JavaScript中函数参数,实际参数的个数可能小于形式参数的个数,函数内的特殊值arguments中封装了所有实际参数。
作用域¶
JavaScript中每个函数都有自己的作用域,当出现函数嵌套时,就出现了作用域链。当内层函数使用变量时,会根据作用域链从内到外一层层的循环,如果不存在,则异常。
切记:所有的作用域在创建函数且未执行时候就已经存在。
function f2(){
var arg= 111;
function f3(){
console.log(arg);
}
return f3;
}
ret = f2();
ret();
function f2(){
var arg= [11,22];
function f3(){
console.log(arg);
}
arg = [44,55];
return f3;
}
ret = f2();
ret();
注:声明提前,在JavaScript引擎“预编译”时进行。
jQuery¶
JavaScript库
总结¶
- 调用(两种方式)
jQuery.
$().
- 转换
- jQuery对象[0] –> DOM对象
- DOM对象 –> $(DOM对象)
查找元素¶
<body>
<div id="i10" class="c1">
<a>f</a>
<a>f</a>
</div>
<div class="c2">
<div class="c3"></div>
</div>
<script src="jquery.js"></script>
</body>
id¶
jQuery('#i10')
$('#i10')
[div#i10.c1, context: document, selector: "#i10"]
DOM对象
$('#i10')[0]
class¶
$('.c1')
标签¶
$('a')
组合¶
$('a,.c2')
$('a,.c2,#i10')
层级¶
// 子子孙孙
$('#i10 a')
// 儿子
$('#i10>a')
基本筛选器¶
:first
// 获取匹配的第一个元素:last
// 获取匹配的最后一个元素:eq(index)
// 匹配一个给定索引值得元素
$('#i10>a:first')
$('#i10>a:first')[0]
$('#i10>a:eq(1)') // 索引值从0开始计算
示例
<ul>
<li>list item 1</li>
<li>list item 2</li>
<li>list item 3</li>
<li>list item 4</li>
<li>list item 5</li>
</ul>
jQuery 代码:
$('li:first');
属性¶
$('[name]') // 具有name属性的所有标签
$('[name="123"]') // name属性等于123的标签
<body>
<input type="text">
<input type="text">
<input type="file">
<input type="password">
<script src="jquery.js"></script>
</body>
$('[type="text"]')
$('input[type="text"]')
// 针对表单,有如下方法
$(':text')
$(':password')
操作元素¶
技巧¶
jQuery内置循环¶
$(':checkbox').each(function (k) {
// k 当前索引
if(this.checked){
this.checked = false; // this,DOM当前循环的元素
}else{
this.checked = true;
}
})
三目运算¶
var v = 条件 ? 真值 : 假值
实例¶
复选框(全选,反选,取消)¶
<div id="i1">
<input type="checkbox" value="1">daf
<input type="checkbox">ew
<input type="checkbox">daf
<input type="checkbox">ewqe
<input type="checkbox">ewq
<input type="checkbox">ewq
<input id="b1" type="button" value="全选">
<input id="b2" type="button" value="取消">
<input id="b3" type="button" value="反选">
</div>
使用DOM 跟 使用jQuery绑定时间的方式不一样
<script src="jquery.js"></script>
<script>
/* $('#i1>input[type="button"]')[0].onclick = function () {
alert('123');} */
// 全选
$('#b1').click(function () {
$(':checkbox').prop('checked',true)
});
// 取消
$('#b2').click(function () {
$(':checkbox').prop('checked',false);
});
// 反选
$('#b3').click(function () {
$(':checkbox').each(function (k) {
// k表示当前索引
// this,DOM,当前循环的元素 $(this)
if(this.checked){
this.checked = false;
}else{
this.checked = true;
}
})
})
// 反选
/* 三元运算
$('#b3').click(function () {
$(':checkbox').each(function (k) {
$(this).prop('checked', $(this).prop('checked') ? false : true);
})}
)*/
xx¶
$(this).next().removeClass('hide');
// 链式编程
$(this).parent().siblings().find('.content').addClass('hide')
前端相关¶
HTML¶
HTML 超文本标签语言。
CSS¶
CSS 层叠样式表(Cascading Style Sheets)。
JavaScript¶
JavaScript 是脚本语言。属于 web 的语言,它适用于 PC、笔记本电脑、平板电脑和移动电话。JavaScript 被设计为向 HTML 页面增加交互性。
HTML DOM¶
HTML DOM 定义了访问和操作 HTML 文档的标准方法。DOM 以树结构表达 HTML 文档。
jQuery¶
jQuery 是一个 JavaScript 库,极大地简化了 JavaScript 编程。
AJAX¶
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。
book¶
Linux From Scratch¶
II 构建准备¶
宿主系统准备¶
In this chapter, the host tools needed for building LFS are checked and, if necessary, installed. Then a partition which will host the LFS system is prepared. We will create the partition itself, create a file system on it, and mount it.
如下指定版本或更高
- Bash-3.2 (/bin/sh should be a symbolic or hard link to bash)
- Binutils-2.17 (Versions greater than 2.29 are not recommended as they have not been tested)
- Bison-2.3 (/usr/bin/yacc should be a link to bison or small script that executes bison)
- Bzip2-1.0.4
- Coreutils-6.9
- Diffutils-2.8.1
- Findutils-4.2.31
- Gawk-4.0.1 (/usr/bin/awk should be a link to gawk)
- GCC-4.7 including the C++ compiler, g++ (Versions greater than 7.2.0 are not recommended as they have not been tested)
- Glibc-2.11 (Versions greater than 2.26 are not recommended as they have not been tested)
- Grep-2.5.1a
- Gzip-1.3.12
- Linux Kernel-3.2
- M4-1.4.10
- Make-3.81
- Patch-2.5.4
- Perl-5.8.8
- Sed-4.1.5
- Tar-1.22
- Texinfo-4.7
- Xz-5.0.0
检查脚本
cat > version-check.sh << "EOF"
#!/bin/bash
# Simple script to list version numbers of critical development tools
export LC_ALL=C
bash --version | head -n1 | cut -d" " -f2-4
MYSH=$(readlink -f /bin/sh)
echo "/bin/sh -> $MYSH"
echo $MYSH | grep -q bash || echo "ERROR: /bin/sh does not point to bash"
unset MYSH
echo -n "Binutils: "; ld --version | head -n1 | cut -d" " -f3-
bison --version | head -n1
if [ -h /usr/bin/yacc ]; then
echo "/usr/bin/yacc -> `readlink -f /usr/bin/yacc`";
elif [ -x /usr/bin/yacc ]; then
echo yacc is `/usr/bin/yacc --version | head -n1`
else
echo "yacc not found"
fi
bzip2 --version 2>&1 < /dev/null | head -n1 | cut -d" " -f1,6-
echo -n "Coreutils: "; chown --version | head -n1 | cut -d")" -f2
diff --version | head -n1
find --version | head -n1
gawk --version | head -n1
if [ -h /usr/bin/awk ]; then
echo "/usr/bin/awk -> `readlink -f /usr/bin/awk`";
elif [ -x /usr/bin/awk ]; then
echo awk is `/usr/bin/awk --version | head -n1`
else
echo "awk not found"
fi
gcc --version | head -n1
g++ --version | head -n1
ldd --version | head -n1 | cut -d" " -f2- # glibc version
grep --version | head -n1
gzip --version | head -n1
cat /proc/version
m4 --version | head -n1
make --version | head -n1
patch --version | head -n1
echo Perl `perl -V:version`
sed --version | head -n1
tar --version | head -n1
makeinfo --version | head -n1
xz --version | head -n1
echo 'int main(){}' > dummy.c && g++ -o dummy dummy.c
if [ -x dummy ]
then echo "g++ compilation OK";
else echo "g++ compilation failed"; fi
rm -f dummy.c dummy
EOF
# 执行脚本
bash version-check.sh
LFS被设计为在一个会话中构建, 整个过程中不会关机. 这并不意味着必须一次完成, 存在的问题是从不同的位置重新开始LFS, 某些程序可能必须得重新编译.
章节 1-4
在宿主系统完成, 当重新启动的时候, 需要注意
程序使用root用户完成, 在2.4节之后, 需要为root用户设置LFS环境
章节 5
- 必须挂载
/mnt/lfs
分区. - 章节 5 的所有指令必须在
lfs
用户下执行.su - lfs
. - The procedures in Section 5.3, “General Compilation Instructions” are critical. If there is any doubt about installing a package, ensure any previously expanded tarballs are removed, re-extract the package files, and complete all instructions in that section.
章节 6-8
/mnt/lfs
必须被挂载- 当切换根目录, 必须为root用户设置LFS环境. 其他地方不需要使用LFS环境
- The virtual file systems must be mounted. This can be done before or after entering chroot by changing to a host virtual terminal and, as root, running the commands in Section 6.2.2, “Mounting and Populating /dev” and Section 6.2.3, “Mounting Virtual Kernel File Systems”.
LFS通常安装在专用分区, 推荐使用一个空的分区用来构建LFS系统
最小系统需要6GB空间, 如果构建LFS系统成为一个基本Linux系统, 则需要安装一些额外的包, 提供20G空间比较合理. LFS系统不会使用这么多空间, 这些空间被用来临时存储, 以及LFS完成后添加额外的功能. 另外, 编译包的过程中需要大量的磁盘空间, 包安装之后, 可以回收.
记住分区的名字(比如, sdb1
), 本书称之为LFS分区
,
同时记住交换分区的名字, 这些名字之后会写到/etc/fstab
文件
本书中, LFS
变量会多次使用, 在LFS构建之前, 确保LFS变量已经定义.
它用来设置你用来构建LFS系统的目录,
我们使用/mnt/lfs
(最终用什么取决于你), 如果使用一个单独的分区,
这个目录将会是你的挂载点.
export LFS=/mnt/lfs
echo $LFS
另外, 确保LFS变量总是被设置, 可以将变量设置写入.bash_profile
# 创建挂载点, 并挂载
root@ubuntu:~# mkdir -pv $LFS
mkdir: created directory '/mnt/lfs'
root@ubuntu:~# mount -v -t ext4 /dev/sdb1 $LFS
mount: /dev/sdb1 mounted on /mnt/lfs.
如果使用多个分区 (比如一个分区挂载 /
另一个挂载 /usr
), 可以这样:
mkdir -pv $LFS
mount -v -t ext4 /dev/sdb1 $LFS
mkdir -v $LFS/usr
mount -v -t ext4 /dev/sdb2 $LFS/usr
如果有使用 swap
分区, 使用 swapon
命令, 确保正确使用:
root@ubuntu:~# swapon -v /dev/sdb3
swapon /dev/sdb3
swapon: /dev/sdb3: found swap signature: version 1d, page-size 4, same byte order
swapon: /dev/sdb3: pagesize=4096, swapsize=17518559232, devsize=17518559232
包和补丁¶
本章节包含一个用于构建基础Linux系统的包列表.
创建$LFS/sources
目录, 用于存放源码包, 补丁, 并作为工作目录
使用root用户执行命令, 创建目录, 用于存放源代码
root@ubuntu:~# mkdir -v $LFS/sources
mkdir: created directory '/mnt/lfs/sources'
修改目录权限
root@ubuntu:~# chmod -v a+wt $LFS/sources
mode of '/mnt/lfs/sources' changed from 0755 (rwxr-xr-x) to 1777 (rwxrwxrwt)
下载包列表
root@ubuntu:~# wget http://www.linuxfromscratch.org/lfs/view/8.1/wget-list
通过wget
, 指定文件, 下载所有包
wget --input-file=wget-list --continue --directory-prefix=$LFS/sources
从LFS-7.0
开始,
有一个校验文件md5sums,
我们可以用它来校验所有源码包的正确性.
将该文件下载到$LFS/sources
目录, 执行下面命令
cd $LFS/sources
root@ubuntu:/mnt/lfs/sources# wget http://www.linuxfromscratch.org/lfs/view/8.1/md5sums
也可以直接到作者提供的镜像站点下载
http://www.linuxfromscratch.org/lfs/packages.html#packages -> http://mirrors-usa.go-parts.com/lfs/lfs-packages/ 下载对应版本需要的包
直接下载作者打包好的, 更方便, 比如http://mirrors-usa.go-parts.com/lfs/lfs-packages/lfs-packages-8.1.tar
下载完之后, 验证
pushd $LFS/sources
# 如果没有验证文件, 需要先下载
# wget http://www.linuxfromscratch.org/lfs/view/8.1/md5sums
md5sum -c md5sums
popd
最后的准备工作¶
$LFS/tools
目录¶所有第五章节编译的包都将会安装到$LFS/tools
, 这只是一个临时目录,
最后完成LFS系统之后, 将被丢弃.
# 创建目录
mkdir -v $LFS/tools
# 创建一个软链接
ln -sv $LFS/tools /
groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
# 设置登录密码
passwd lfs
# 修改 $LFS/tools 属主
chown -v lfs $LFS/tools
# 修改 $LFS/sources 属主
chown -v lfs $LFS/sources
# 切换到 lfs 用户
su - lfs
cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF
cat > ~/.bashrc << "EOF"
set +h
umask 022
LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=$(uname -m)-lfs-linux-gnu
PATH=/tools/bin:/bin:/usr/bin
export LFS LC_ALL LFS_TGT PATH
EOF
生效
source ~/.bash_profile
Standard Build Unit (SBU), 标准构建单元, 用来测量构建安装单个包的时间.
SBU测量工作如下, 本书第五章第一个包编译的是Binutils
,
编译该包所花的时间将作为SBU,
其他包编译所需时间可以根据该包得出一个相对值.
比如, 考虑到一个包编译时间为 4.5 SBUs. 这意味着,
如果系统花了10分钟编译这个包, 那么编译示例里面的包将需要45分钟,
幸运的是. 大多数其他包的编译时间少于Binutils
.
一般来说, SBUs 并不完全准确, 因为编译时间会受很多因素影响, 包括宿主机 GCC 版本.
针对多核计算机, 编译的时候, 可以指定多进程编译, 使用
export MAKEFLAGS='-j 2'
或者, 编译的时候使用
make -j2
很多包会提供对应测试组件, 运行测试组件, 可以知道包的编译情况. 有一些包编译之后, 进行测试是很有必要的, 比如: GCC, Binutils, Glibc等, 测试过程可能会花费不少时间, 但是强烈推荐进行测试.
构建一个临时系统¶
http://www.linuxfromscratch.org/lfs/view/8.1/chapter05/toolchaintechnotes.html
对包的构建, 做一些约定.
有一些包在构建之前, 需要打补丁, 但只有要规避某些问题的时候才需要打补丁. 补丁通常在本章和下章使用. 因此, 不用担心指令执行之后, 似乎下载的补丁不见了. 在应用补丁的时候, 出现的警告消息也不用担心, 补丁仍然应用成功.
大多数软件编译过程中, 屏幕上会不断滚动一些警告信息, 这些信息可以忽略, 这些警告一般都是提示某些标准将被弃用等, 但并不是无效. C标准经常变化, 而有些包还是使用的旧的标准. 这不是问题, 只是一些警告的提示.
再次检查 LFS
环境变量:
echo $LFS
两个重要项
构建指令假设宿主机依赖, 包括软链接都设置正确:
- 使用的shell为
bash
sh
链接到bash
/usr/bin/awk
链接到gawk
/usr/bin/yacc
链接到bison
或执行bison
的脚本.
强调构建过程
- 将所有源代码和补丁放置在chroot环境可访问的目录, 例如
/mnt/lfs/sources/
. 不要将源代码放在/mnt/lfs/tools/
. - 切换到源代码路径
- 对于每个包
- 使用
tar
命令, 解压, 在第5章, 解压的时候确保你使用的是lfs用户 - 切换到解压后的包路径
- 跟随本书的指令, 进行编译
- 返回源代码路径
- 除非有其他指示, 否咋删除解压目录
- 使用
Binutils
包含链接器, 汇编程序, 一起其他一些工具处理对象文件.
Installation of Cross Binutils
It is important that Binutils be the first package compiled because both Glibc and GCC perform various tests on the available linker and assembler to determine which of their own features to enable.
The Binutils documentation recommends building Binutils in a dedicated build directory:
mkdir -v build
cd build
SBU的测量数据, 是从配置开始, 到第一次安装, 总共花的时间,
即{ ./configure ... && ... && make install; }
.
Now prepare Binutils for compilation:
../configure --prefix=/tools \
--with-sysroot=$LFS \
--with-lib-path=/tools/lib \
--target=$LFS_TGT \
--disable-nls \
--disable-werror
The meaning of the configure options:
--prefix=/tools
配置
Binutils
安装到/tools
目录.--with-sysroot=$LFS
交叉编译, 告诉编译器在
$LFS
目录找需要的系统库.--with-lib-path=/tools/lib
This specifies which library path the linker should be configured to use.
--target=$LFS_TGT
Because the machine description in the
LFS_TGT
variable is slightly different than the value returned by the config.guess script, this switch will tell the configurescript to adjust Binutil’s build system for building a cross linker.--disable-nls
This disables internationalization as i18n is not needed for the temporary tools.
--disable-werror
This prevents the build from stopping in the event that there are warnings from the host’s compiler.
Continue with compiling the package:
make
# make -j2
编译完成, 通常我们需要测试, make test
, 但是在早起阶段,
我们的测试组件还没有安装到指定位置 (Tcl, Expect, and DejaGNU) . The
benefits of running the tests at this point are minimal since the
programs from this first pass will soon be replaced by those from the
second.
如果在 x86_64
的架构上构建, 创建软链接, 确保工具链的完整性:
case $(uname -m) in
x86_64) mkdir -v /tools/lib && ln -sv lib /tools/lib64 ;;
esac
Install the package:
make install
Details on this package are located in Section 6.16.2, “Contents of Binutils.”
包含C和C++编译器
GCC 依赖 GMP, MPFR 和 MPC. 解压每个包到gcc源代码目录, 并重命名, 编译gcc的时候, 构建程序会自动使用它们.
tar xf gcc-7.2.0.tar.xz
cd gcc-7.2.0
tar xf ../mpfr-3.1.5.tar.xz
mv -v mpfr-3.1.5 mpfr
tar xf ../gmp-6.1.2.tar.xz
mv -v gmp-6.1.2 gmp
tar xf ../mpc-1.0.3.tar.gz
mv -v mpc-1.0.3 mpc
lfs@ubuntu:/mnt/lfs/sources$ tar xf gcc-7.2.0.tar.xz
lfs@ubuntu:/mnt/lfs/sources$ cd gcc-7.2.0
lfs@ubuntu:/mnt/lfs/sources/gcc-7.2.0$ tar xf ../mpfr-3.1.5.tar.xz
lfs@ubuntu:/mnt/lfs/sources/gcc-7.2.0$ mv -v mpfr-3.1.5 mpfr
'mpfr-3.1.5' -> 'mpfr'
lfs@ubuntu:/mnt/lfs/sources/gcc-7.2.0$ tar xf ../gmp-6.1.2.tar.xz
lfs@ubuntu:/mnt/lfs/sources/gcc-7.2.0$ mv -v gmp-6.1.2 gmp
'gmp-6.1.2' -> 'gmp'
lfs@ubuntu:/mnt/lfs/sources/gcc-7.2.0$ tar xf ../mpc-1.0.3.tar.gz
lfs@ubuntu:/mnt/lfs/sources/gcc-7.2.0$ mv -v mpc-1.0.3 mpc
'mpc-1.0.3' -> 'mpc'
下面的命令将改变GCC默认动态链接器的位置到/tools
,
同时会将/usr/include
, 从gcc的搜索目录中移除.
for file in gcc/config/{linux,i386/linux{,64}}.h
do
cp -uv $file{,.orig}
sed -e 's@/lib\(64\)\?\(32\)\?/ld@/tools&@g' \
-e 's@/usr@/tools@g' $file.orig > $file
echo '
#undef STANDARD_STARTFILE_PREFIX_1
#undef STANDARD_STARTFILE_PREFIX_2
#define STANDARD_STARTFILE_PREFIX_1 "/tools/lib/"
#define STANDARD_STARTFILE_PREFIX_2 ""' >> $file
touch $file.orig
done
最后, 在 x86_64 主机上, 设置 64-bit libraries 默认目录为 “lib”:
case $(uname -m) in
x86_64)
sed -e '/m64=/s/lib64/lib/' \
-i.orig gcc/config/i386/t-linux64
;;
esac
The GCC documentation recommends building GCC in a dedicated build directory:
mkdir -v build
cd build
Prepare GCC for compilation:
../configure \
--target=$LFS_TGT \
--prefix=/tools \
--with-glibc-version=2.11 \
--with-sysroot=$LFS \
--with-newlib \
--without-headers \
--with-local-prefix=/tools \
--with-native-system-header-dir=/tools/include \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-decimal-float \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libmpx \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
The meaning of the configure options:
--with-newlib
Since a working C library is not yet available, this ensures that the inhibit_libc constant is defined when building libgcc. This prevents the compiling of any code that requires libc support.
--without-headers
When creating a complete cross-compiler, GCC requires standard headers compatible with the target system. For our purposes these headers will not be needed. This switch prevents GCC from looking for them.
--with-local-prefix=/tools
The local prefix is the location in the system that GCC will search for locally installed include files. The default is /usr/local. Setting this to /tools helps keep the host location of /usr/local out of this GCC’s search path.
--with-native-system-header-dir=/tools/include
By default GCC searches /usr/include for system headers. In conjunction with the sysroot switch, this would translate normally to $LFS/usr/include. However the headers that will be installed in the next two sections will go to $LFS/tools/include. This switch ensures that gcc will find them correctly. In the second pass of GCC, this same switch will ensure that no headers from the host system are found.
--disable-shared
This switch forces GCC to link its internal libraries statically. We do this to avoid possible issues with the host system.
--disable-decimal-float, --disable-threads, --disable-libatomic, --disable-libgomp, --disable-libmpx, --disable-libquadmath, --disable-libssp, --disable-libvtv, --disable-libstdcxx
These switches disable support for the decimal floating point extension, threading, libatomic, libgomp, libmpx, libquadmath, libssp, libvtv, and the C++ standard library respectively. These features will fail to compile when building a cross-compiler and are not necessary for the task of cross-compiling the temporary libc.
--disable-multilib
On x86_64, LFS does not yet support a multilib configuration. This switch is harmless for x86.
--enable-languages=c,c++
This option ensures that only the C and C++ compilers are built. These are the only languages needed now.
Compile GCC by running:
make
Compilation is now complete. At this point, the test suite would normally be run, but, as mentioned before, the test suite framework is not in place yet. The benefits of running the tests at this point are minimal since the programs from this first pass will soon be replaced.
Install the package:
make install
Details on this package are located in Section 6.20.2, “Contents of GCC.”
The Linux API Headers (in linux-4.12.7.tar.xz) expose the kernel’s API for use by Glibc.
Approximate build time:less than 0.1 SBU
Required disk space:861 MB
Installation of Linux API Headers
The Linux kernel needs to expose an Application Programming Interface (API) for the system’s C library (Glibc in LFS) to use. This is done by way of sanitizing various C header files that are shipped in the Linux kernel source tarball.
Make sure there are no stale files embedded in the package:
make mrproper
Now extract the user-visible kernel headers from the source. They are placed in an intermediate local directory and copied to the needed location because the extraction process removes any existing files in the target directory.
make INSTALL_HDR_PATH=dest headers_install
cp -rv dest/include/* /tools/include
Details on this package are located in Section 6.7.2, “Contents of Linux API Headers.”
The Glibc package contains the main C library. This library provides the basic routines for allocating memory, searching directories, opening and closing files, reading and writing files, string handling, pattern matching, arithmetic, and so on.
Approximate build time:4.2 SBU
Required disk space:790 MB
Installation of Glibc
The Glibc documentation recommends building Glibc in a dedicated build directory:
mkdir -v build
cd build
Next, prepare Glibc for compilation:
../configure \
--prefix=/tools \
--host=$LFS_TGT \
--build=$(../scripts/config.guess) \
--enable-kernel=3.2 \
--with-headers=/tools/include \
libc_cv_forced_unwind=yes \
libc_cv_c_cleanup=yes
The meaning of the configure options:
–host=:math:`LFS_TGT, –build=`(../scripts/config.guess)
The combined effect of these switches is that Glibc’s build system configures itself to cross-compile, using the cross-linker and cross-compiler in
/tools
.--enable-kernel=3.2
This tells Glibc to compile the library with support for 3.2 and later Linux kernels. Workarounds for older kernels are not enabled.
–with-headers=/tools/include
This tells Glibc to compile itself against the headers recently installed to the tools directory, so that it knows exactly what features the kernel has and can optimize itself accordingly.
libc_cv_forced_unwind=yes
The linker installed during Section 5.4, “Binutils-2.29 - Pass 1” was cross-compiled and as such cannot be used until Glibc has been installed. This means that the configure test for force-unwind support will fail, as it relies on a working linker. The libc_cv_forced_unwind=yes variable is passed in order to inform configure that force-unwind support is available without it having to run the test.
libc_cv_c_cleanup=yes
Similarly, we pass libc_cv_c_cleanup=yes through to the configure script so that the test is skipped and C cleanup handling support is configured.
During this stage the following warning might appear:
configure: WARNING: *** These auxiliary programs are missing or *** incompatible versions: msgfmt *** some features will be disabled. *** Check the INSTALL file for required versions.
The missing or incompatible msgfmt program is generally harmless. This msgfmt program is part of the Gettext package which the host distribution should provide.
Note
There have been reports that this package may fail when building as a “parallel make”. If this occurs, rerun the make command with a “-j1” option.
Compile the package:
make
Install the package:
make install
Caution
At this point, it is imperative to stop and ensure that the basic functions (compiling and linking) of the new toolchain are working as expected. To perform a sanity check, run the following commands:
echo 'int main(){}' > dummy.c $LFS_TGT-gcc dummy.c readelf -l a.out | grep ': /tools'If everything is working correctly, there should be no errors, and the output of the last command will be of the form:
[Requesting program interpreter: /tools/lib/ld-linux.so.2]Note that for 64-bit machines, the interpreter name will be
/tools/lib64/ld-linux-x86-64.so.2
.If the output is not shown as above or there was no output at all, then something is wrong. Investigate and retrace the steps to find out where the problem is and correct it. This issue must be resolved before continuing on.
Once all is well, clean up the test files:
rm -v dummy.c a.out
Note
Building Binutils in the section after next will serve as an additional check that the toolchain has been built properly. If Binutils fails to build, it is an indication that something has gone wrong with the previous Binutils, GCC, or Glibc installations.
Details on this package are located in Section 6.9.3, “Contents of Glibc.”
Libstdc++ is the standard C++ library. It is needed for the correct operation of the g++ compiler.
Approximate build time:0.4 SBU
Required disk space:750 MB
Installation of Target Libstdc++
Note
Libstdc++ is part of the GCC sources. You should first unpack the GCC tarball and change to thegcc-7.2.0
directory.
Create a separate build directory for Libstdc++ and enter it:
mkdir -v build
cd build
Prepare Libstdc++ for compilation:
../libstdc++-v3/configure \
--host=$LFS_TGT \
--prefix=/tools \
--disable-multilib \
--disable-nls \
--disable-libstdcxx-threads \
--disable-libstdcxx-pch \
--with-gxx-include-dir=/tools/$LFS_TGT/include/c++/7.2.0
The meaning of the configure options:
–host=…
Indicates to use the cross compiler we have just built instead of the one in
/usr/bin
.–disable-libstdcxx-threads
Since we have not yet built the C threads library, the C++ one cannot be built either.
–disable-libstdcxx-pch
This switch prevents the installation of precompiled include files, which are not needed at this stage.
–with-gxx-include-dir=/tools/$LFS_TGT/include/c++/7.2.0
This is the location where the standard include files are searched by the C++ compiler. In a normal build, this information is automatically passed to the Libstdc++ configure options from the top level directory. In our case, this information must be explicitly given.
Compile libstdc++ by running:
make
Install the library:
make install
Details on this package are located in Section 6.20.2, “Contents of GCC.”
The Binutils package contains a linker, an assembler, and other tools for handling object files.
Approximate build time:1.1 SBU
Required disk space:582 MB
Installation of Binutils
Create a separate build directory again:
mkdir -v build
cd build
Prepare Binutils for compilation:
CC=$LFS_TGT-gcc \
AR=$LFS_TGT-ar \
RANLIB=$LFS_TGT-ranlib \
../configure \
--prefix=/tools \
--disable-nls \
--disable-werror \
--with-lib-path=/tools/lib \
--with-sysroot
The meaning of the new configure options:
CC=:math:`LFS_TGT-gcc AR=`LFS_TGT-ar RANLIB=$LFS_TGT-ranlib
Because this is really a native build of Binutils, setting these variables ensures that the build system uses the cross-compiler and associated tools instead of the ones on the host system.
–with-lib-path=/tools/lib
This tells the configure script to specify the library search path during the compilation of Binutils, resulting in
/tools/lib
being passed to the linker. This prevents the linker from searching through library directories on the host.–with-sysroot
The sysroot feature enables the linker to find shared objects which are required by other shared objects explicitly included on the linker’s command line. Without this, some packages may not build successfully on some hosts.
Compile the package:
make
Install the package:
make install
Now prepare the linker for the “Re-adjusting” phase in the next chapter:
make -C ld clean
make -C ld LIB_PATH=/usr/lib:/lib
cp -v ld/ld-new /tools/bin
The meaning of the make parameters:
-C ld clean
This tells the make program to remove all compiled files in the
ld
subdirectory.-C ld LIB_PATH=/usr/lib:/lib
This option rebuilds everything in the
ld
subdirectory. Specifying theLIB_PATH
Makefile variable on the command line allows us to override the default value of the temporary tools and point it to the proper final path. The value of this variable specifies the linker’s default library search path. This preparation is used in the next chapter.
Details on this package are located in Section 6.16.2, “Contents of Binutils.”
III Building the LFS System¶
安装基本系统软件¶
The Linux API Headers (in linux-4.12.7.tar.xz) expose the kernel’s API for use by Glibc.
进入到 sources
目录 解压 linux-4.12.7.tar.xz
,
进入到解压后的目录
The Linux kernel needs to expose an Application Programming Interface (API) for the system’s C library (Glibc in LFS) to use. This is done by way of sanitizing various C header files that are shipped in the Linux kernel source tarball.
Make sure there are no stale files and dependencies lying around from previous activity:
make mrproper
Now extract the user-visible kernel headers from the source. They are placed in an intermediate local directory and copied to the needed location because the extraction process removes any existing files in the target directory. There are also some hidden files used by the kernel developers and not needed by LFS that are removed from the intermediate directory.
make INSTALL_HDR_PATH=dest headers_install
find dest/include \( -name .install -o -name ..install.cmd \) -delete
cp -rv dest/include/* /usr/include
Installed headers:
/usr/include/asm/*.h, /usr/include/asm-generic/*.h, /usr/include/drm/*.h, /usr/include/linux/*.h, /usr/include/misc/*.h, /usr/include/mtd/*.h, /usr/include/rdma/*.h, /usr/include/scsi/*.h, /usr/include/sound/*.h, /usr/include/video/*.h, and /usr/include/xen/*.h
Installed directories:
/usr/include/asm, /usr/include/asm-generic, /usr/include/drm, /usr/include/linux, /usr/include/misc, /usr/include/mtd, /usr/include/rdma, /usr/include/scsi, /usr/include/sound, /usr/include/video, and /usr/include/xen
Short Descriptions
/usr/include/asm/*.h The Linux API ASM Headers
/usr/include/asm-generic/*.h The Linux API ASM Generic Headers
/usr/include/drm/*.h The Linux API DRM Headers
/usr/include/linux/*.h The Linux API Linux Headers
/usr/include/mtd/*.h The Linux API MTD Headers
/usr/include/rdma/*.h The Linux API RDMA Headers
/usr/include/scsi/*.h The Linux API SCSI Headers
/usr/include/sound/*.h The Linux API Sound Headers
/usr/include/video/*.h The Linux API Video Headers
/usr/include/xen/*.h The Linux API Xen Headers
The Man-pages package contains over 2,200 man pages.
Approximate build time: less than 0.1 SBU Required disk space: 27 MB
Installed files: various man pages
Short Descriptions
man pages Describe C programming language functions, important device files, and significant configuration files
Linux From Scratch¶
Linux From Scratch (LFS), 从源代码一步一步构建自己的Linux系统.
记录时, 本书最新版本
LFS Stable Version 8.1 Release
Bruce Dubbs - 2017/09/01
问题记录¶
makeinfo: Command not found¶
apt-get install texinfo
mount: wrong fs type, bad option, bad superblock on /dev/sdb1,¶
mount: wrong fs type, bad option, bad superblock on /dev/sdb1, missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so.
原因, 没有格式化
sudo mkfs -t ext4 /dev/sdb
ci¶
TeamCity¶
安装 TeamCity¶
使用Archive with bundled Tomcat (any platform)¶
TeamCity采用了构建服务器和构建代理的概念。服务器负责管理和构建配置。实际的构建过程(编译、打包、部署等等)是由一个或多个代理执行的。
TeamCity需要JAVA环境,从 TeamCity 10.0
开始,
依赖Java 1.8 JDK
或更高,确保已经安装成功
java -version
echo $JAVA_HOME
cd /usr/local
wget https://download.jetbrains.com/teamcity/TeamCity-10.0.5.tar.gz
tar xf TeamCity-10.0.5.tar.gz
启动停止
cd /usr/local/Teamcity/bin
./runAll.sh start
./runAll.sh stop
Teamcity 的数据目录除了存在于安装目录,在用户的家目录又一个隐藏目录(/root/.BuildServer)也保存部分。Teamcity迁移时,将安装目录和这个隐藏目录一起拷贝到其它主机,就可以完全保留数据 /root/.BuildServer
快速开始¶
参考¶
TeamCity Web¶
访问 8111
端口(TeamCity默认端口),如果访问不了,可能因为防火墙阻隔
修改端口,配置文件: /usr/local/TeamCity/conf/server.xml
<Connector port="8111" ...

teamcity-01-setup
TeamCity将构建历史,用户信息,构建结果等存放在数据库中,初次使用,我们选择默认配置就行Internal(HSQLDB)
.
创建的过程会需要一些时间
接下来: 接受协议
-> 创建管理账号
-> 进入个人界面,OK
个人界面,具体信息可填可不填
创建项目¶
创建项目的时候,可以根据需求,选择手动配置,还是指向仓库地址等等
Administration
-> Projects
-> Create project
-> Manually

teamcity-06-project-01
一个项目包含如下内容

teamcity-07-project-02
- 通用设置
构建配置
,构建配置定义项目如何获取和构建源代码。一个项目可以有多个构建配置
,构建配置
下又有多项配置- 通用设置,包含该构建配置的名称,ID,描述,文件路径等等
- 版本控制设置
- 构建步骤
- 触发器
- 参数
- 等等
- VCS
- Parameters
- 等等
TeamCity¶
TeamCity is a Java-based build management and continuous integration server from JetBrains.
teamcity 插件安装¶
官方 Installing Additional Plugins
两种方法
web
界面上传,重启teamcity- 手动上传
zip
包到数据目录,重启teamcity
重启teamcity之后,在插件界面可以看到是否安装成功,以及相关信息.
通过web界面上传¶

teamcity-05-plugins
teamcity 构建参数¶
动态构建参数¶

teamcity-02-parameter

teamcity-03-parameter-02
保存

teamcity-04-parameter-03
只需要在后面的构建过程中引用 select 参数就行
teamcity从命令行设置构建参数¶
To set build parameters from command line you need to use the following commands:
Build Step #1:
#!/bin/bash
echo "##teamcity[setParameter name='env.ddd' value='fff']"
echo "##teamcity[setParameter name='env.datetime' value='$(date)']"
The values of initialized parameters will be avaliable on the next build step:
Build Step #2:
#!/bin/bash
echo $ddd
echo $datetime
使用web提供动态参数¶
有时间补充
jenkins¶
Jenkins¶
安装Jenkins¶
本文使用Docker方式使用Jenkins
- 使用包安装
- 使用brew安装
- 下载 pkg 包 http://mirrors.jenkins.io/osx/latest
- 安装
使用brew安装
Install the latest release version
brew install jenkins
Install the LTS version
brew install jenkins-lts
下载镜像
docker pull jenkins
启动,宿主机$PWD/jenkins映射到容器里的/var/jenkins_home
容器8080端口映射到宿主机49001端口
docker run -d -p 49001:8080 -v $PWD/jenkins:/var/jenkins_home --name jenkins jenkins
设置容器时区
docker run -d -p 49001:8080 -v $PWD/jenkins:/var/jenkins_home -e TZ="Asia/Shanghai" -v /etc/localtime:/etc/localtime:ro --name jenkins jenkins
如果容器获取到的仍然是容器默认的时区,则需要使用参数,-e TZ="Asia/Shanghai"
浏览器访问
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins
Docker启动¶
docker run -d -p 49001:8080 -v $PWD/jenkins:/var/jenkins_home -e TZ="Asia/Shanghai" -v /etc/localtime:/etc/localtime:ro --name jenkins jenkins
Setup Wizard¶
设置管理员及密码
浏览器访问 http://localhost:49001/

jenkins-01-Getting-Started
密码在容器中 /var/jenkins_home/secrets/initialAdminPassword
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
649be62674884a85851261888c0df824
将密码复制到密码框,下一步 -> 使用推荐的插件(网络比较慢,可能会出现安装不上的情况,未安装成功的可以进入之后继续安装) -> 输入管理员信息,保存

jenkins-02-Create-Admin-User
配置完成

jenkins-03-Jenkins-is-ready
使用Jenkins¶
持续集成的环境应当尽量保持独立,当多个用户共用同一个 Jenkins Master 节点的时候,很容易因为一个成员改变了机器配置而对另一个构建造成影响。
所以,能用 Slave 做的事情尽量用 Slave 去做, 何况 Docker 里创建一个 Slave 是非常容易的事情。

jenkins-04-Manage-Nodes

jenkins-05-New-Node

jenkins-06-Node-Name

jenkins-07-Node-2

jenkins-08-Node-3

jenkins-09-Node-4
-secret后面跟的随机密码很重要,下面一步只有 secret
和 name
都一致才能连接成功。
需要下载镜像 jenkinsci/jnlp-slave
docker run --link jenkins -d jenkinsci/jnlp-slave -url http://jenkins:8080 ab96387cc533b8be663ccfc57fce9f0e41b11cd6b07f96941e2b51164832f610 slave01
启动容器后刷新 Jenkins 的节点列表, 很快 slave01 节点就变成可用的啦。
nodejs.org
方式¶- 选择版本(没用通过Extract
*.zip/*.tar.gz
方式之前,有可能出现没有版本可供选择的情况,可能是网络原因) - 设置全局安装的包,比如
gitbook-cli@2.3.0 gitbook@3.2.2
- 保存

jenkins-14-Install-from-nodejs
*.zip/*.tar.gz
方式¶- 安装和配置 NodeJS Plugin 管理多个版本的 Node.js
- 新建 Pipeline 项目,验证 Node.js 安装
使用NodeJS Plugin插件来安装Node.js
插件管理中,安装 NodeJS Plugin
插件
Manage Jenkins
-> Manage Plugins
-> Available
->
NodeJS Plugin
-> Install without restart
Manage Jenkins
-> Global Tool Configuration
-> NodeJS

jenkins-10-NodeJS-01
http://npm.taobao.org/mirrors/node/v7.8.0/node-v7.8.0-linux-x64.tar.gz
NodeJS所有版本: http://npm.taobao.org/mirrors/node/
使用 NodeJS Plugin
来安装Node.js的好处
有这样几个好处:
- 和 Jenkins 集成得最好,新添加的 Slave 节点会自动安装 Node.js 依赖
- 避免了登录到 Slave 安装 Node.js 可能改变操作系统配置的问题
- 可以在不同的构建里使用不同的 Node.js 版本
New Item
-> Enter an item name
-> Pipeline
-> OK
配置 Pipeline demo

jenkins-11-Pipeline-demo
代码内容(如果不安装GitBook,可以删除后面两个状态)
后面构建如果报错,命令不存在的话,可以在构建执行的命令里面,添加环境变量
node ('master')
表示选中具有master
标签的节点,如果有slave,配置的标签带有 node
,并且配置为 node (‘node’),那这些slave都会被选中
tool name: 'v7.8.0', type: 'jenkins.plugins.nodejs.tools.NodeJSInstallation'
表示构建的过程会用到之前配置的 Node.js
工具的 v7.8.0
版本
env.PATH = "${tool 'v7.8.0'}/node-v7.8.0-linux-x64/bin:${env.PATH}"
上面这行会修改构建的PATH
,否则会提示找不到node
和npm
命令
点击保存
,然后立即构建
,等待完成.Node.JS
环境就搭建成功

jenkins-12-demo-status

jenkins-13-demo-console-output
常用插件¶
EnvInject 环境变量
使用 Jenkins 持续集成 GitBook¶
准备:¶
系统管理 -> 系统设置 -> 浏览器搜索“Publish over SSH” -> 配置 “SSH Servers” ,准备部署的服务器地址“Remote Directory” 即拷贝到远程主机的目标目录
插件:
- NodeJS Plugin
- Publish Over SSH
配置:¶
- jenkins 创建一个自由风格Project
- 源码管理 配置页面往下拉 -> “源码管理” -> 选择“Git” -> 在“Repository URL” 填入“项目地址” -> 点击“Add” 加入用户名密码 -> “Branches to build” 选择分支
- 构建触发器
勾选“Build when a change is pushed to GitLab. GitLab CI Service URL: http://jenkins.xxx.cn/project/xxx”
勾选“Filter branches by name” 这个决定那个分支有提交时才触发构建
Include -> master
Exclude -> develop
- 构建环境
勾选“Provide Node & npm bin/ folder to PATH” 默认就选中 NodeJS(这个只有用到才需要选,gitbook 需要用npm 安装)
- 构建
点击“增加构建步骤” -> 选中 “Execute shell” -> “Command” 内容为“gitbook build .”
- 构建后操作
点击“增加构建后操作步骤” -> 选中“Send build artifacts over SSH” -> “Name” 选中在准备步骤中配置的主机 -> “Sourve files” 填入“_book” -> “Remove prefix” 填入“__book” -> “点击保存”
- jenkins 配置完成
- 为了实现当项目 master 分支有提交时,jenkins自动触发构建操作,需要在gitlab 配置“Webhooks”
在gitlab 项目页面点击“Webhooks” ,把配置步骤的第三小步中出现的URL 地址填到本页面的“URL”,其它默认,点击“Add Webhooks” 完成配置
- 现在提交一下代码到项目master , jenkins就会自动构建
Jenkins配置邮件发送¶
安装“Email Extension Plugin” 插件¶
系统管理-> 管理插件 -> 安装 “Email Extension Plugin” 这个插件
配置系统 Email Extension Plugin 插件¶
系统管理-> 系统设置 ->找到“Extended E-mail Notification”
具体配置参数:
SMTP server smtp.exmail.qq.com
...点击**高级**...(配置发信)
勾选"Use SMTP Authentication"
User Name brave0517@163.com
Password xxx
Use SSL ☑️
SMTP Port 465
Charset UTF-8
Default Content Type 选择“Plain Text(text/plain)”
Default Recipients brave0517@163.com
Default Subject
Default Subject:构建通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!
Default Content
点击 “Apply” -> “保存“
配置项目(只需要在发送邮件通知的项目配置)¶
增加一个构建后操作选择“Email Extension Plugin”
具体配置参数:
Project Recipient List
493535459@qq.com,xxx@qq.com (收件人,是一个列表,逗号分割)
点击“Advanced Settins…”
点击“Add Trigger”分别选择 “Success”,“Failure - Any”, “Not Built” 3个触发器(成功,失败,都会发邮件)
Jenkins¶
Jenkins是一个用Java编写的开源的持续集成工具。在与Oracle发生争执后,项目从Hudson项目复刻。
Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中(例如Apache Tomcat)。它支持软件配置管理(SCM)工具(包括AccuRev SCM、CVS、Subversion、Git、Perforce、Clearcase和RTC),可以执行基于Apache Ant和Apache Maven的项目,以及任意的Shell脚本和Windows批处理命令。Jenkins的主要开发者是川口耕介。Jenkins是在MIT许可证下发布的自由软件。
可以通过各种手段触发构建。例如提交给版本控制系统时被触发,也可以通过类似Cron的机制调度,也可以在其他的构建已经完成时,还可以通过一个特定的URL进行请求。
jenkins通过GitBook生成HTML推送到GitHub Pages¶
步骤¶
- 从coding等私有仓库拉取md文件
- 生成HTML
- 发布到GitHub Pages
jenkins配置¶
- 搭建Node.js环境
- 创建,配置自由风格项目
New Item
-> Enter an item name -- Freestyle project
配置内容:
配置项目名称

jenkins-15-SCM
由于在本机使用的Jenkins,没有公网IP,所以使用如下触发器
表示每五分钟检查SCM是否有新的变更,如果有则构建,没有不构建

jenkins-16-Build-Triggers
由于上面配置的Provide Node & npm bin/ folder to PATH
构建的时候发现,环境变量有问题,所以下面构建时执行的命令有配置node的环境变量,其中NODEJS
为配置全局变量(/var/jenkins_home/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/v7.8.0/bin/
)
Manage Jenkins -> Configure System

jenkins-19-nodejs-env
继续配置构建环境

jenkins-17-Build-Environment
export PATH=${NODEJS}:$PATH
bash summary_create.sh
sed -i "s#Updated: 2017-[0-9][0-9]-[0-9][0-9]#Updated: $(date +%F)#g" README.md
sed -ri 's#(\S+* \[)[0-9]+-(.*$)#\1\2#g' SUMMARY.md
cd python && bash summary_create.sh && sed -ri 's#(\S+* \[)[0-9]+-(.*$)#\1\2#g' SUMMARY.md && cd ..
gitbook build .
使用一个项目即可,构建时执行的脚本需要修改,这里使用下面的项目将HTML提交到GitHub Pages
为了避免麻烦,先将GitHub Pages仓库克隆到宿主机映射到容器里面的目录比如: jenkins/workspace下,而后将该仓库名目录下的文件拷贝到jenkins项目工作目录下(最好先测试手动能够推送)
注意
Project name
-> gitbook-github Source Code Management
-> None
Build Triggers
-> Build after other projects are built(Projects to
watch note_gitbook)(Trigger only if build is stable)
- 必须要配置user.email,user.name
- 使用ssh的方式推送代码,私钥放置于宿主机映射目录下的.ssh目录下,公钥配置到GitHub
Pages仓库下,并允许该
ssh key
推送. - 在宿主机将项目克隆到宿主机映射目录下的jenkins工作目录下,并配置相关信息
配置参考
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = 仓库地址 # 使用ssh key方式
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[user]
name = xxx # 用户
email = xx@xx.com # 邮箱
[push]
default = matching
Build
->

jenkins-20-Build-2
/bin/cp -r /var/jenkins_home/workspace/note_gitbook/_book/* .
rm -rf assets && rm -rf _other
#git config user.email "xxx@qq.com"
#git config user.name "xx"
#git config push.default matching
git add .
git commit -m "Site updated: $(date +%F-%H-%M)"
git push origin master
附:docker-compose¶
➜ cat docker-compose.yml
jenkins-xxx:
image: 'jenkins:latest'
restart: always
environment:
- TZ=Asia/Shanghai
volumes:
- $PWD/jenkins:/var/jenkins_home
- /etc/localtime:/etc/localtime:ro
ports:
- '49001:8080'
手动安装NodeJS¶
如果使用上面自动安装NodeJS的方式, 最后还是提示gitbook命令不存在
可以使用root
身份进入容器, 创建软链接
docker以root身份进入容器
docker exec -it -u root 62044c564952 bash
或者直接全局安装npm
, gitbook
等命令
那样项目就不需要选择NodeJS了
持续集成(Continuous integration)¶
摘自百度百科
定义¶
持续集成是一种软件开发实践 每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。
价值¶
- 减少风险
- 减少重复过程
- 任何时间、任何地点生成可部署的软件
- 增强项目的可见性
- 有效决策
- 注意到趋势
- 建立团队开发产品的信心
要素¶
- 统一的代码库
- 自动构建
- 自动测试
- 每个人每天都要向代码库主干提交代码
- 每次代码递交后都会在持续集成服务器上出发一次构建
- 保证快速构建
- 模拟生产环境的自动测试
- 每个人都可以很容易的获取最新可执行的应用程序
- 每个人都清楚正在发生的状况
- 自动化的部署
原则¶
- 所有的开发人员需要在本地机器上做本地构建,然后再提交到版本控制库中,从而确保他们的变更不会导致持续集成失败。
- 开发人员每天至少向版本控制库中提交一次代码。
- 开发人员每天至少需要从版本控制库中更新一次代码到本地机器。
- 需要有专门的服务器来执行集成构建,每天要执行多次构建。
- 每次构建都要100%通过。
- 每次构建都可以生成可发布的产品。
- 修复失败的构建是优先级最高的事情。
- 测试是未来,未来是测试。
常用构建工具¶
- Jenkins
- Travis
- Codeship
- Strider
- TeamCity
travis¶
安装 Travis CI 命令行工具¶
# 如果提示没有权限则使用 sudo
sudo gem install travis
加密¶
➜ ~ travis encrypt -r LyonYang/blogs GH_TOKEN=7a7c6ff62d0061.....
➜ blogs git:(master) cat .travis.yml
env:
global:
- GH_REF: github.com/LyonYang/blogs.git
- secure: "19/hXRSX06+LU0/O9JmvDOKOuZsKXv0yPvtgAQ1SJr0v0yqFUN7a5XqtkbHsqZIl+AQvT2tq9KHJAiuIpDoEPDc4tWxqz7JQlw1W4d74D38FFUsRVsPUGZCH+5F/jry7vndq04xoRVu3SC23asZudZ6kJExiQdFwtiVG+mC0hk3Ae8RXyjWqC8JjuQzOlyXeJir6yatIdaqYnZH6Mk7fXTmrx3EcOrTzZ+CsxrrPaR0t6Xy7mxprB9+20XE/hp94dcfXqyFkF9McWy5Xg4cbyy6jR67jGpZzCfiILm7jmRD/DCa/2iSlNvy5HNzr0Qt7ckgiugLMX7CytwdsX7B1S3adkSuoRjDglumyuz2oNFgVw4eqKJ4vMm4PfTl4gkrybFfh9roJozk02VBrX0tUaj/HUmYu5z0F5oSXoH4PWAmjUCL4o5NPp/CCEbAKJ4TXLD1d/uGcqV9vfyPghVIMwONfkPi3sxHI+TjSWHCVwDediQ38UKYnJEOvwMG1DbMpSr4nLnzt2gz1DasSNi32FMHkutoiF6RwbgJmN9VKjtOIlihvzIleVNYxv2DP9XgMdh2ks9fSYY2+SNv270C0U25Q8hwIJ5cpmkPKzobEtxTa8Cq47MHaVfCqaO4qvb4/9RX6O3yY84J8SaUdgnF2y+4nS+bZqwXV109g7Y4QXzo="
#sudo: false
script:
- bash summary_create.sh
after_script:
- git config user.name "Lyon"
- git config user.email "547903993@qq.com"
- git status
- git add SUMMARY.md
- git commit -m "[Travis] Update SUMMARY.md"
- git push -f "https://${GH_TOKEN}@${GH_REF}" HEAD:master
Travis CI仓库简单配置¶

travis-setting
cloud¶
docker¶
Docker Compose¶
Install Docker Compose¶
Docker Compose¶
Compose是定义和运行多容器Docker应用程序的工具.你可以使用Compose文件来定义你的应用服务.然后,使用单个命令,您可以从配置创建并启动所有服务.
Compose使用于开发,测试和分期环境以及CI工作流.
使用Compose基本上是如下三个流程:
- 使用
Dockerfile
定义你的应用的环境,这样你可以在任何地方重新生成你的应用环境 - 在
docker-compose.yml
文件中定义组成你应用的服务,以便于它们可以在孤立的环境中一起运行 - 最后,执行
docker compose
,Compose将启动并运行你的app
docker-compose.yml
示例:
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
- logvolume01:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume01: {}
Compose有用于管理整个应用生命周期的命令
- 启动,停止和重新构建服务
- 查看运行服务的状态
- 输出运行服务的日志
- 运行一次性命令
marathon¶
Marathon¶
它是一个mesos框架,能够支持运行长服务,比如web应用等。是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat Play等等,可以集群的多进程管理。也是一种私有的Pass,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。
docker¶
安装¶
系统要求
- Ubuntu 14.04、16.04
- Debian 7.7、8.0
- CentOS 7.X
- Fedora 20、21、22
- OracleLinux 6、7
If you would like to use Docker as a non-root user, you should now consider adding your user to the “docker” group with something like:
sudo usermod -aG docker your-user
Remember that you will have to log out and back in for this to take effect!
docker-compose¶
由于GitHub的访问问题,Linux系统的Docker Compose下载也不稳定,所以可以从阿里云镜像站下载
下载对应系统即可
wget http://mirrors.aliyun.com/docker-toolbox/linux/compose/1.10.0/docker-compose-Linux-x86_64
cp docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
root@ubuntu47:~/src# docker-compose -v
docker-compose version 1.10.0, build 4bd6f1a
docker常用命令¶
镜像操作¶
docker search 镜像名 #从docker官方仓库搜索镜像
docker pull centos #下载centos镜像
docker save centos > /opt/centos.tar.gz #镜像导出
docker load < /opt/centos.tar.gz #镜像导入
docker images #查看本机存在的镜像
【示例】
docker search centos
docker pull centos:6 ### 如果不指定tag,默认下载latest
# 导出镜像
docker save -o tensorflow-serving-devel.tar boxfish/tensorflow-serving-devel
# 导入镜像
docker load -i tensorflow-serving-devel.tar
注:
镜像pull失败,是因为网络原因,可以使用代理上网,比如使用shadowsocks(有命令行客户端)
容器操作¶
docker run -i -t centos:6 /bin/bash
-i:开启交互式shell
-t:为容器分配一个伪tty终端
-d:以守护进程方式运行docker容器
--name:
centos:指定镜像的名字
docker start 5eb5ee832f57 ##启动刚刚退出的容器,也可以使用NAME
进入容器
docker attach 77079090e085
docker inspect --format "{{ .State.Pid }}" d09969389b95
nsenter --target 43964 --mount --uts --ipc --net --pid
编写脚本,进入容器
[root@vir ~]# cat docker_in.sh
#!/bin/bash
# Use nsenter to access docker
docker_in(){
NAME_ID=$1
PID=$(docker inspect -f "{{ .State.Pid }}" $NAME_ID)
nsenter -t $PID -m -u -i -n -p
}
docker_in $1
提交镜像¶
docker commit -m "centos6_http_server" d09969389b95 yjj/httpd:v1
docker的使用¶
启动docker¶
/etc/init.d/docker start
docker镜像¶
搜索镜像
docker search centos
下面是部分结果,分别对应,镜像名称,详细信息,星级(受欢迎程度),是否官方镜像,是否自动创建。
根据是否是官方提供,可将镜像资源分为两类。
一种是类似 centos 这样的基础镜像,被称为基础或根镜像。这些基础镜像是由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。
还有一种类型,比如 ansible/centos7-ansible 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。
镜像名称 详细信息 星级 是否官方镜像 是否自动创建
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
centos The official build of CentOS. 2592 [OK]
ansible/centos7-ansible Ansible on Centos7 83 [OK]
jdeathe/centos-ssh CentOS-6 6.8 x86_64 / CentOS-7 7.2.1511 x8... 28 [OK]
nimmis/java-centos This is docker images of CentOS 7 with dif... 14 [OK]
gluster/gluster-centos Official GlusterFS Image [ CentOS7 + Glus... 12 [OK]
million12/centos-supervisor Base CentOS-7 with supervisord launcher, h... 12 [OK]
获取centos的docker镜像,由于cento7版本的systemd问题,导致安装的服务无法正常启动,这里选择centos:6版本,版本在search时无法找到,只能通过https://hub.docker.com 查找,下图为centos的版本,不过centos7的systemd问题,官方给的也有解决方案。 centos7的systemd问题官方文档
Dockerfile for systemd base image
FROM centos:7
MAINTAINER "you" <your@email.here>
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i ==
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
This Dockerfile deletes a number of unit files which might cause issues. From here, you are ready to build your base image.
$ docker build --rm -t local/c7-systemd .
Example systemd enabled app container
In order to use the systemd enabled base container created above, you will need to create your Dockerfile similar to the one below.
FROM local/c7-systemd
RUN yum -y install httpd; yum clean all; systemctl enable httpd.service
EXPOSE 80
CMD ["/usr/sbin/init"]
Build this image:
$ docker build --rm -t local/c7-systemd-httpd
Running a systemd enabled app container
In order to run a container with systemd, you will need to mount the cgroups volumes from the host. Below is an example command that will run the systemd enabled httpd container created earlier.
$ docker run -ti -v /sys/fs/cgroup:/sys/fs/cgroup:ro -p 80:80 local/c7-systemd-httpd
This container is running with systemd in a limited context, with the cgroups filesystem mounted. There have been reports that if you’re using an Ubuntu host, you will need to add -v /tmp/$(mktemp -d):/run in addition to the cgroups mount.
获取centos6镜像¶
docker pull centos:6
列出已经获取到的docker镜像¶
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
centos 6 273a1eca2d3a 4 weeks ago 194.6 MB
说明
- REPOSITORY:来自于哪个仓库,比如 centos
- TAG:镜像的标记,一般修改版本号
- IMAGE ID:镜像的id号
- CREATED:创建镜像的时间
- VIRTUAL SIZE:镜像的大小
启动一个docker容器¶
[root@localhost ~]# docker run -i -t centos:6 /bin/bash
-i:开启交互式shell
-t:为容器分配一个伪tty终端
centos:指定镜像的名字,后面的数字表示版本
/bin/bash:运行/bin/bash
运行命令测试
[root@65b6cf94133f /]# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:01
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe11:1/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:6 errors:0 dropped:0 overruns:0 frame:0
TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:468 (468.0 b) TX bytes:558 (558.0 b)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
[root@65b6cf94133f /]# cat /etc/centos-release
CentOS release 6.8 (Final)
查看docker镜像的状态¶
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
65b6cf94133f centos:6 "/bin/bash" About a minute ago Exited (0) 16 seconds ago evil_engelbart
-a表示列出所有的容器,STATUS如果为Exited为退出,UP为运行。
启动刚才退出的docker镜像¶
启动时跟ID启动,也可以使用NAME启动。
[root@localhost ~]# docker start 65b6cf94133f
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
65b6cf94133f centos:6 "/bin/bash" 4 minutes ago Up 53 seconds evil_engelbart
停止docker容器¶
[root@localhost ~]# docker stop 65b6cf94133f
自动重启¶
故障处理, --restart
参数, 支持三种逻辑实现
no:容器退出时不重启
on-failure:容器故障退出(返回值非零)时重启
always:容器退出时总是重启
示例
--restart=always
以守护进程的方式运行docker容器¶
使用-d参数
[root@localhost ~]# docker run -d --name docker-daemon centos:6 /bin/bash -c "while true; do echo hello world; sleep 1; done"
# -l 查看最近一次修改的docker容器
[root@localhost ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5f117a57a13c centos:6 "/bin/bash -c 'while 25 seconds ago Up 24 seconds docker-daemon
进入docker容器¶
以守护进程的方式运行时,进入容器对容器进行操作。
首先启动docker
[root@localhost ~]# docker run -dit centos:6
[root@localhost ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
22fa39e2bb08 centos:6 "/bin/bash" 12 seconds ago Up 11 seconds stoic_mayer
使用docker自带的命令进入容器,可以跟名字,或者CONTAINER ID登录
[root@localhost ~]# docker attach stoic_mayer
docker attach 是Docker自带的命令,但是使用 attach 命令,开多个窗口同时,所有窗口都会同步显示操作。当某个窗口因命令阻塞时,其他窗口也无法执行操作了,退出时如果使用exit或者ctrl+c也会关闭docker容器,使用快捷键先按ctrl+p,再按ctrl+q。
docker exec -it 22fa39e2bb08 bash
查询是否安装util-linux软件包,如果没有需要安装。
[root@localhost ~]# rpm -qf `which nsenter`
util-linux-ng-2.17.2-12.18.el6.x86_64
安装
wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz; tar xzvf util-linux-2.24.tar.gz
cd util-linux-2.24
./configure --without-ncurses && make nsenter
cp nsenter /usr/local/bin
使用nsenter命令的话,需要找到运行的docker容器的pid才可进入。
[root@localhost ~]# docker inspect --format "{{ .State.Pid }}" 22fa39e2bb08
2759
进入容器
[root@localhost ~]# nsenter --target 2759 --mount --uts --ipc --net --pid
自定义docker镜像¶
使用已有的容器生成镜像
[root@localhost ~]# nsenter --target 2759 --mount --uts --ipc --net --pid
在镜像里安装httpd
yum -y install httpd
chkconfig httpd on
exit
docker commit -m "centos http server" 22fa39e2bb08 yy/httpd:v1
-m:指定提交的说明信息
22fa39e2bb08:容器的id
yy/httpd:v1:指定仓库名和TAG信息
[root@localhost ~]# docker run -dit -p 80:80 yy/httpd:v1 /sbin/init
‘-p’:端口映射,第一个80为本地端口,第二个80为docker容器端口
yy/httpd:v1:刚才提交的镜像名称
/sbin/init:启动init程序,由它去进行chkconfig http on的操作
正常情况下访问本机的ip即可访问docker容器中的http服务器
[root@localhost ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0c5dcf434a01 yy/httpd:v1 "/sbin/init" 4 minutes ago Up 4 minutes 0.0.0.0:80->80/tcp sleepy_engelbart
[root@localhost ~]# netstat -tnlp|grep 80
tcp 0 0 :::80 :::* LISTEN 4499/docker-proxy
使用 Dockerfile 来创建镜像¶
mkdir docker-file
cd docker-file/
touch dockerfile
文件内容
[root@localhost docker-file]# cat dockerfile
# This is a comment
FROM centos:6
MAINTAINER Docker New <new@docker.com>
RUN yum -y install httpd
RUN chkconfig httpd on
EXPOSE 80
CMD ["/sbin/init"]
说明
[root@localhost docker-file]# docker build -t "yy/httpd:v2" .
其中 -t 标记来添加 tag,指定新的镜像的用户信息。“.”是 Dockerfile 所在的路径(当前目录),也可以替换为一个具体的 Dockerfile 的路径。
[root@localhost docker-file]# docker run -dit -p 81:80 yy/httpd:v2
查看状态
[root@localhost docker-file]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a2ef76ef4694 yy/httpd:v2 "/sbin/init" 49 seconds ago Up 48 seconds 0.0.0.0:81->80/tcp determined_colden
[root@localhost docker-file]# netstat -tunlp|egrep "81|80"
tcp 0 0 :::80 :::* LISTEN 4499/docker-proxy
tcp 0 0 :::81 :::* LISTEN 6987/docker-proxy
导入导出¶
查看有哪些镜像
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
yy/httpd v2 05f550f29746 8 hours ago 292.4 MB
yy/httpd v1 beda3a6b9e94 8 hours ago 292.4 MB
centos 6 273a1eca2d3a 4 weeks ago 194.6 MB
centos latest d83a55af4e75 4 weeks ago 196.7 MB
导出镜像
[root@localhost ~]# docker save -o httpd.tar yy/httpd:v2
[root@localhost ~]# docker load < httpd.tar
查看镜像列表,选择最近一次的容器,也可使用-a选择任意容器
[root@localhost ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a2ef76ef4694 yy/httpd:v2 "/sbin/init" 8 hours ago Up 8 hours 0.0.0.0:81->80/tcp determined_colden
停止该容器
[root@localhost ~]# docker stop a2ef76ef4694
导出容器
[root@localhost ~]# docker export a2ef76ef4694 > httpdv2.tar
删除容器
[root@localhost ~]# docker rm a2ef76ef4694
[root@localhost ~]# docker import - yy/httpd:v3 < httpdv2.tar
*注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
数据卷¶
[root@localhost ~]# docker run -dit -h apache -v /opt:/opt yy/httpd:v3 /bin/bash
e4c0213e8e0b0c067296abc6f84a83ea00ea71dd287e49b827e88871ed27ad30
在本地创建文件到容器内查看
[root@localhost ~]# cd /opt/
[root@localhost opt]# touch good
[root@localhost opt]# docker attach e4c0213e8e0b
[root@apache /]# ls opt/
good rh
[root@localhost opt]# docker run -it --rm -h apache -v /etc/hosts:/opt/hosts:ro yy/httpd:v3 /bin/bash
ro:可以设置为只读
也可以在dockerfile中指定,参考上面的官方文档例子
VOLUME [ "/sys/fs/cgroup" ]
网络¶
把docker里面的端口映射成为本机端口,可以通过 -P 或 -p 参数来指定端口映射。
映射端口到全部ip的80端口
[root@localhost opt]# docker run -dit -p 80:80 yy/httpd:v3 /sbin/init
如果物理机有多个ip,映射到某一ip的80端口
[root@localhost opt]# docker run -dit -p 127.0.0.1:80:80 yy/httpd:v3 /sbin/init
使用 udp 标记来指定 udp 端口
[root@localhost opt]# docker run -dit -p 127.0.0.1:80:80/udp yy/httpd:v3 /sbin/init
创建容器时–name对容器命名。
[root@localhost opt]# docker run -dit --name web1 yy/httpd:v3 /bin/bash
查看命名的容器
[root@localhost ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ef5abf27a922 yy/httpd:v3 "/bin/bash" 7 seconds ago Up 7 seconds web1
也可使用docker inspect查看
[root@localhost opt]# docker inspect -f "{{ .Name }}" ef5abf27a922
/web1
注意:容器的名称是唯一的。如果已经命名了一个叫 web 的容器,当你要再次使用 web 这个名称的时候,需要先用docker rm来删除之前创建的同名容器。
在执行docker run的时候如果添加–rm标记,则容器在终止后会立刻删除。注意,–rm和 -d 参数不能同时使用。
[root@localhost ~]# docker run -dit --name web2 --link web1:web2toweb1 yy/httpd:v3 /bin/bash
登录测试
[root@localhost ~]# docker attach web2
[root@86a512d57ac7 /]# ping web1
PING web2toweb1 (172.17.0.21) 56(84) bytes of data.
64 bytes from web2toweb1 (172.17.0.21): icmp_seq=1 ttl=64 time=1.19 ms
64 bytes from web2toweb1 (172.17.0.21): icmp_seq=2 ttl=64 time=0.058 ms
Dockerfile 语法¶
一个简单的例子:
# Print "Hello docker!"
RUN echo "Hello docker!"
LABEL¶
LABEL maintainer=“SvenDowideit@home.org.au”
CMD¶
和RUN命令相似,CMD可以用于执行特定的命令。和RUN不同的是,这些命令不是在镜像构建的过程中执行的,而是在用镜像构建容器后被调用。
# Usage 1: CMD application "argument", "argument", ..
CMD "echo" "Hello docker!"
支持三种格式
- CMD [“executable”,”param1″,”param2″] 使用 exec 执行,推荐方式;
- CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
- CMD [“param1″,”param2”] 提供给 ENTRYPOINT 的默认参数;
ENTRYPOINT¶
配置容器启动后执行的命令,并且不可被 docker run
提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT
,
当指定多个时,只有最后一个起效。
ENTRYPOINT 帮助你配置一个容器使之可执行化,如果你结合CMD命令和ENTRYPOINT命令,你可以从CMD命令中移除“application”而仅仅保留参数,参数将传递给ENTRYPOINT命令。
# Usage: ENTRYPOINT application "argument", "argument", ..
# Remember: arguments are optional. They can be provided by CMD
# or during the creation of a container.
ENTRYPOINT echo
# Usage example with CMD:
# Arguments set with CMD can be overridden during *run*
CMD "Hello docker!"
ENTRYPOINT echo
docker¶
Docker is the world’s leading software container platform.
docker 安装¶
linux使用代理上网¶
因为docker被墙,有时候访问不了,使用代理上网,可以使用shadowsock共享上网
然后在~/.bashrc里添加,如果匿名代理无需user和password,直接地址和端口
export http_proxy=http://username:password@proxyserver:port/
使代理生效
source ~/.bashrc
docker tips¶
docker以root身份进入容器¶
docker exec -it -u root 62044c564952 bash
docker容器中文乱码, 修改容器编码¶
locale -a
查看容器语言环境
临时修改 LANG=C.UTF-8
永久修改, 在Dockerfile添加一行内容
ENV LANG C.UTF-8
重新构建镜像即可
修改容器时区¶
比如, 修改为Asia/Shanghai
/usr/share/zoneinfo/Asia/Shanghai
文件不存在则需要安装 tzdata
# 根据实际系统, 调整命令, 安装之后清楚缓存
RUN apt-get update && apt-get install tzdata cron && apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 如果使用创建软链接的方式, 镜像构建的时候文件不存在的时候不会报错, 只有进入容器才能发现时区没有修改成功, 软链接无效
RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone
# 使用cp, 文件不存在, 构建镜像的时候就会报错提示
# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
容器中使用定时任务¶
Dockerfile
RUN echo "定时任务" > /var/spool/cron/crontabs/root && \
touch /var/log/cron.log && \
# 权限及属主
chmod 600 /var/spool/cron/crontabs/root && \
chown root:crontab /var/spool/cron/crontabs/root && \
此外, 需要保证cron一直运行, 可以使用supervisor
,
或者构建镜像的时候添加一行
RUN cron # (未测试)
定时任务中如果涉及到中文问题, 需要设置编码, 可以在执行命令之前,
修改一下编码 export LANG="C.UTF-8"
使用docker部署TensorFlow Serving¶
Dockerfile¶
FROM ubuntu:16.04
MAINTAINER Jeremiah Harmsen <jeremiah@google.com>
ADD sources.list /etc/apt/sources.list
RUN apt-get clean && \
apt-get update && apt-get install -y \
build-essential \
curl \
git \
libfreetype6-dev \
libpng12-dev \
libzmq3-dev \
mlocate \
pkg-config \
python-dev \
python-numpy \
python-pip \
software-properties-common \
swig \
zip \
zlib1g-dev \
libcurl3-dev \
openjdk-8-jdk\
openjdk-8-jre-headless \
wget \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security main restricted universe multiverse
# Set up grpc
RUN pip install mock grpcio
# Set up Bazel. 使用apt-get安装, 不需要Bazel, 具体自行修改
ENV BAZELRC /root/.bazelrc
# Install the most recent bazel release.
ENV BAZEL_VERSION 0.5.1
WORKDIR /
RUN echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | tee /etc/apt/sources.list.d/tensorflow-serving.list && \
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | apt-key add - && \
apt-get update && apt-get install tensorflow-model-server
# 程序没有使用, 不需要安装tensorflow-serving-api
RUN pip install tensorflow-serving-api
COPY boot.sh /usr/local/boot.sh
# 没有如下脚本, 则自行删除
COPY start.sh /usr/local/start.sh
COPY stop.sh /usr/local/stop.sh
RUN chmod +x /usr/local/boot.sh /usr/local/start.sh /usr/local/stop.sh
ENTRYPOINT ["/usr/local/boot.sh"]
163源¶
sources.list
deb http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse
boot.sh¶
该脚本可以写入tensorflow_model_server
启动命令,
启动容器的时候将model映射到容器内.
如果不启动守护进程, 又不想容器退出, 可以使用死循环或者tail避免容器退出.
#!/bin/bash
while true
do sleep 100 ;
done
# tail -f /dev/null
觉得添加脚本麻烦, 可以使用如下命令
docker run -d boxfish/tensorflow-serving-devel:1.0 /bin/bash -c "while true;do sleep 100; done"
docker镜像构建命令¶
docker build --pull -t $USER/tensorflow-serving-devel:1.0 -f Dockerfile.devel .
导出镜像¶
docker save -o tensorflow-serving-devel.tar boxfish/tensorflow-serving-devel
导入镜像¶
docker load -i tensorflow-serving-devel.tar
使用registry镜像,搭建docker私有库¶
docker pull registry
docker run -d -p 5000:5000 -v $PWD/registry:/tmp/registry registry
访问私有仓库
root@ubuntu66:~/data# curl 127.0.0.1:5000/v2
<a href="/v2/">Moved Permanently</a>.
kubernetes¶
Kubernetes集群¶
简单创建及测试¶
对于初学或者简单验证测试的用户, 可以使用下面几种方法
macOS
brew cask install minikube
Linux
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
Windows
Download the [minikube-windows-amd64.exe](https://storage.googleapis.com/minikube/releases/latest/minikube-windows-amd64.exe) file, rename it to `minikube.exe` and add it to your path.
- kubectl
- macOS
- Hyperkit driver, xhyve driver, VirtualBox, or VMware Fusion
- Linux
- VirtualBox or KVM
- NOTE: Minikube also supports a –vm-driver=none option that runs the Kubernetes components on the host and not in a VM. Docker is required to use this driver but no hypervisor. If you use –vm-driver=none, be sure to specify a bridge network for docker. Otherwise it might change between network restarts, causing loss of connectivity to your cluster.
- Windows
- VirtualBox or Hyper-V
- VT-x/AMD-v virtualization must be enabled in BIOS
- Internet connection on first run
The Hyperkit driver will eventually replace the existing xhyve driver. It is built from the minikube source tree, and uses moby/hyperkit as a Go library.
To install the hyperkit driver:
curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit \
&& chmod +x docker-machine-driver-hyperkit \
&& sudo mv docker-machine-driver-hyperkit /usr/local/bin/ \
&& sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit \
&& sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit
启动
$ minikube start --vm-driver hyperkit
Starting local Kubernetes v1.9.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
查看状态
$ kubectl cluster-info
Kubernetes master is running at https://192.168.64.2:8443
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
Katacoda playground提供了一个免费的2节点Kuberentes体验环境,网络基于WeaveNet,并且会自动部署整个集群。但要注意,刚打开Katacoda playground页面时集群有可能还没初始化完成,可以在master节点上运行launch.sh等待集群初始化完成。
部署并访问kubernetes dashboard
的方法:
# 在master node上面运行
kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
kubectl proxy --address='0.0.0.0' --port=8080 --accept-hosts='^*$'&
然后点击Terminal Host 1右边的➕,从弹出的菜单里选择View HTTP port 8080 on Host 1,即可打开Kubernetes的API页面。在该网址后面增加/ui即可访问dashboard。
Kubernetes部署¶
Kubeadm¶
Kubeadm解决TLS加密配置问题, 部署核心Kubernetes组件并确保新增节点可以很容易的加入集群. 集群通过RBAC等机制确保安全.
更多细节参考 https://github.com/kubernetes/kubeadm
初始化Master¶
Helm¶
Kubernetes¶
Kubernetes is an open source system for managing containerized applications across multiple hosts, providing basic mechanisms for deployment, maintenance, and scaling of applications. The open source project is hosted by the Cloud Native Computing Foundation (CNCF).
设计架构¶

架构图
Kubernetes节点¶
核心组件
- etcd 保存整个集群状态
- apiserver 提供了资源操作的唯一入口, 并提供认证, 授权, 访问控制, API注册和发现等机制
- controller manager 负责维护集群的状态, 比如故障检测, 自动扩展, 滚动更新等
- scheduler 负责资源的调度, 按照预定的调度策略将Pod调度到相应的机器上
- kubelet 负责维护容器的生命周期, 同时也负责Volume(CVI)和网络(CNI)的管理
- Container runtime 负责镜像管理及Pod和容器的真正运行(CRI)
- kube-proxy 负责Service提供cluster内部的服务发现和负载均衡
其他推荐的Add-ons
- kube-dns 负责为整个集群提供DNS服务
- Ingress Controller 为服务提供外网入口
- Heapster 提供资源监控
- Dashboard 提供GUI
- Federation 提供跨可用区的集群
- Fluentd-elasticsearch 提供集群日志采集, 存储与查询
kvm¶
kvm
openstack¶
openstack
db¶
SQL Server¶
SQL Server¶
端口

sqlserver-01-port
使用Mac连接SQL Server¶
The following instructions assume you already have a SQL Server database running somewhere that your Mac has network access to.
The connection to SQL Server will be made using the unixODBC driver
manager and the FreeTDS driver. Installing them is most easily done
using homebrew
, the Mac package manager:
brew update
brew install unixodbc
brew install freetds --with-unixodbc
The freetds.conf
file is usually located in directory
/usr/local/etc/
. However, Homebrew will create this file as a soft
link to the real file located here
/usr/local/Cellar/freetds/<version>/etc/freetds.conf
. Check this
location with tsql -C
. The default file already contains a bunch of
stuff, but all you need to do is add your server information to the end,
as follows:
[MYMSSQL]
host = mssqlhost.xyz.com
port = 1433
tds version = 7.3
There are other key/value pairs that can be added but this shouldn’t
usually be necessary, see
here for details.
The host
parameter should be either the network name (or IP address)
of the database server, or “localhost” if SQL Server is running directly
on your Mac (e.g. using
Docker).
A TDS version of 7.3 should be OK for SQL Server 2008 and newer, but
bear in mind that
you might need a different value for older versions of SQL Server
TDS version 8.0 is just an alias for TDS version 7.1, so
- TDS version “8.0” is not newer than TDS versions 7.2, 7.3, or 7.4, and
- specifying TDS version “8.0” is discouraged because of possible future compatibility issues.
(For more information on TDS protocol versions see Choosing a TDS protocol version.)
Test the connection using the tsql
utility, e.g.
tsql -S MYMSSQL -U myuser -P mypassword
. If this works, you should
see the following:
locale is "en_US.UTF-8"
locale charset is "UTF-8"
using default charset "UTF-8"
1>
At this point you can run SQL queries, e.g. “SELECT @@VERSION” but
you’ll need to enter “GO” on a separate line to actually execute the
query. Type exit
to get out of the interactive session.
Run odbcinst -j
to get the location of the odbcinst.ini
and
odbc.ini
files (probably in directory
/usr/local/Cellar/unixodbc/<version>/etc/
). Edit odbcinst.ini
to
include the following:
[FreeTDS]
Description=FreeTDS Driver for Linux & MSSQL
Driver=/usr/local/lib/libtdsodbc.so
Setup=/usr/local/lib/libtdsodbc.so
UsageCount=1
Edit odbc.ini
to include the following:
[MYMSSQL]
Description = Test to SQLServer
Driver = FreeTDS
Servername = MYMSSQL
Note, the “Driver” is the name of the entry in odbcinst.ini
, and the
“Servername” is the name of the entry in freetds.conf
(not a network
name). There are other key/value pairs that can be included, see
here for
details.
Check that all is OK by running isql MYMSSQL myuser mypassword
. You
should see the following:
+---------------------------------------+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| quit |
| |
+---------------------------------------+
You can enter SQL queries at this point if you like. Type quit
to
exit the interactive session.
It should now be possible to connect to your SQL Server database using pyodbc, for example:
import pyodbc
# the DSN value should be the name of the entry in odbc.ini, not freetds.conf
conn = pyodbc.connect('DSN=MYMSSQL;UID=myuser;PWD=mypassword')
crsr = conn.cursor()
rows = crsr.execute("select @@VERSION").fetchall()
print(rows)
crsr.close()
conn.close()
mongodb¶
mongodb快速入门¶
安装¶
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
echo "deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo service mongod start
mongo shell¶
root@ubuntu66:~# mongo
MongoDB shell version v3.4.2
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.2
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user
Server has startup warnings:
2017-03-06T21:35:50.232+0800 I STORAGE [initandlisten]
...
use DATABASE_NAME
用于创建数据库,数据库不存在,创建,存在,切换到现有数据库
查看当前所在数据库,使用db
> db
test
创建数据库
> use mydb
switched to db mydb
> db
mydb
查看数据库列表,使用dbs
> show dbs
admin 0.000GB
local 0.000GB
创建的数据库(mydb)不存在列表中,要显示数据库,需要至少插入一个文档.
> db.movie.insert({"x":1})
WriteResult({ "nInserted" : 1 })
> show dbs
admin 0.000GB
local 0.000GB
mydb 0.000GB
MongoDB的默认数据库是test。 如果没有创建任何数据库,那么集合将被保存在测试数据库。
> db
mydb
> db.dropDatabase()
{ "dropped" : "mydb", "ok" : 1 }
> show dbs
admin 0.000GB
local 0.000GB
db.createCollection(name,options)
参数 | 类型 | 描述 |
---|---|---|
name | String | 要创建的集合的名称 |
options | Document | (可选)指定有关内存大小和索引选项 |
> use test
switched to db test
> db.createCollection("mycollection")
{ "ok" : 1 }
查看集合
> show collections
mycollection
选项列表
> db.createCollection("mycol", { capped : true, autoIndexId:true, size : 6142800, max : 10000 } )
{
"note" : "the autoIndexId option is deprecated and will be removed in a future release",
"ok" : 1
}
> show collections
mycol
mycollection
db.COLLECTION_NAME.drop()
> db.mycollection.drop()
true
将数据插入到Mongodb集合
db.COLLECTION_NAME.insert(document)
> db.mycol.insert({
... title: 'MongoDB Overview',
... description: 'MongoDB is no sql database',
... likes: 100
... })
WriteResult({ "nInserted" : 1 })
这里 mycol 是我们的集合名称,它是在之前的教程中创建。如果集合不存在于数据库中,那么MongoDB创建此集合,然后插入文档进去。
在如果我们不指定_id参数插入的文档,那么 MongoDB 将为文档分配一个唯一的ObjectId。
要以单个查询插入多个文档,可以通过文档insert()命令的数组方式
要从集合查询Mongodb数据,需要使用find()方法
db.COLLECTION_NAME.find()
find() 方法将在非结构化的方式显示所有的文件。 如果显示结果是格式化的,那么可以用pretty() 方法。
> db.mycol.find()
{ "_id" : ObjectId("58be1aaaf84bcc15e691533b"), "title" : "MongoDB Overview", "description" : "MongoDB is no sql database", "likes" : 100 }
> db.mycol.find().pretty()
{
"_id" : ObjectId("58be1aaaf84bcc15e691533b"),
"title" : "MongoDB Overview",
"description" : "MongoDB is no sql database",
"likes" : 100
}
>
除了find()方法,还有findOne()方法,仅返回一个文档
查询文档在一些条件的基础上,可以使用下面的操作
操作: Equality
语法: {<key>:<value>}
示例: db.mycol.find({"by":"yiibai tutorials"}).pretty()
RDBMS等效语句: where by = 'yiibai tutorials'
Less Than
{<key>:{$lt:<value>}}
db.mycol.find({"likes":{$lt:50}}).pretty()
where likes < 50
Less Than Equals
{<key>:{$lte:<value>}}
db.mycol.find({"likes":{$lte:50}}).pretty()
where likes <= 50
Greater Than
{<key>:{$gt:<value>}}
db.mycol.find({"likes":{$gt:50}}).pretty()
where likes > 50
Greater Than Equals
{<key>:{$gte:<value>}}
db.mycol.find({"likes":{$gte:50}}).pretty()
where likes >= 50
Not Equals
{<key>:{$ne:<value>}}
db.mycol.find({"likes":{$ne:50}}).pretty()
where likes != 50
mongodb¶
mongodb yaml配置文件详解¶
mongodb常用命令¶
登录数据库:¶
mongo 192.168.2.199:27017/admin (库名)
mongo 192.168.2.199:27017/admin -u system -p BJjg10661898
查看帮助¶
db.help() 显示数据库操作命令
db.foo.help() 显示集合(表)操作命令,foo指的是当前数据库下,一个叫foo的集合
查看数据库¶
show dbs
使用数据库¶
use admin
查看集合(表)¶
show collections (show tables)
添加系统管理员账号,用于管理用户¶
db.createUser({user:"system",pwd:"*****",roles:[{role:"root",db:"admin"}]})
验证
db.auth("system", "BJjg10661898");
添加数据库管理员,用来管当前数据库¶
db.createUser({user:"lottery",pwd:"0okmnhy6",roles:["dbOwner"]})
查找用户¶
db.system.users.find();
删除当前使用数据库:¶
use test
db.dropDatabase();
创建集合(表)¶
use alidayusms
db.test.insert({'name':'test'});
删除集合(表)¶
db.test.drop()
导出库¶
导入库¶
导出集合(表)¶
mongoexport -d alidayusms -c systemConfig -o /tmp/systemConfig.dat
导入集合(表)¶
mongoimport -h 192.168.2.199 --port 27017 -u lottery -p 0okmnhy6 -d alidayusms -c systemConfig --upsert --drop /opt/systemConfig.dat
-h 指定主机,可用主机名或者IP
--port 指定端口
-u 指定用户
-p 指定密码
-d 指定库
-c 指定集合(表)
--upsert 插入或者更新现有数据
创建只读用户¶
db.createUser({user: "zhangguoqing",pwd: "zhangguoqing",roles: [{ role: "read", db: "genlotogw" }]})
Successfully added user: {
"user" : "zhanggguoqing",
"roles" : [
{
"role" : "read",
"db" : "genlotogw"
}
]
}
db.createUser({user: "zhanggguoqing",pwd: "zhangguoqing",roles: ["read"]})
监控¶
mongostat -h 192.168.2.199 -u system -p BJjg10661898 --authenticationDatabase=admin
慢查询分析:¶
https://docs.mongodb.com/manual/reference/method/db.setProfilingLevel/
https://docs.mongodb.com/v3.0/tutorial/manage-the-database-profiler/
lottery:PRIMARY> db.getProfilingStatus()
{ "was" : 1, "slowms" : 100 }
lottery:PRIMARY> db.getProfilingLevel()
0
db.setProfilingLevel()
db.system.profile.find().limit(10).sort( { ts : -1 } ).pretty()
找出对我们有用的数据¶
show dbs
use ticketcenter
> db.getProfilingStatus() #默认的100
> { "was" : 0, "slowms" : 100 }
> db.setProfilingLevel(1,200) #设置200
> { "was" : 0, "slowms" : 100, "ok" : 1 }
> db.getProfilingStatus()
> { "was" : 1, "slowms" : 200 }
db.system.profile.find().limit(10).sort( { ts : -1 } ).pretty()
db.setProfilingLevel()
db.getProfilingStatus()
查看索引¶
use tiecketcenter
db.ticket.getIndexes()
后台建立索引¶
mongodb 日志切割¶
每一次重启,日志也会重新生成
四种方法
- Default Log Rotation Behavior
- Log Rotation with –logRotate reopen
- Syslog Log Rotation
- Forcing a Log Rotation with SIGUSR1
第一种 Default Log Rotation Behavior¶
root@ubuntu66:~# mongo
MongoDB shell version v3.4.2
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.2
> use admin
switched to db admin
> db.auth("yang","yang")
1
> db.runCommand({logRotate : 1})
{ "ok" : 1 }

mongodb-1
第二种 Log Rotation with –logRotate reopen¶
第四种 Forcing a Log Rotation with SIGUSR1¶
切割脚本
root@ubuntu66:~# cat logrotate_mongo.sh
#!/bin/bash
pid=$(/bin/pidof /usr/bin/mongod)
for n in $pid
do
if [ "$n" ];then
kill -SIGUSR1 $n
fi
done
exit
定时任务
59 23 * * * /bin/bash /data/logRotate/logrotate_mongo.sh
Mongodb用户,角色,权限管理¶
概念¶
Mongodb的用户是由 用户名+所属库名组成
例如:
登录mongo testdb1 ,创建用户testuser
登录mongo testdb2 ,创建用户testuser
那上面创建的用户分别是:testuser@testdb1,testuser@testdb2
在哪个库下面创建用户,这个用户就是哪个库的
查看所有用户show roles
Mongodb的授权采用了角色授权的方法,每个角色包括一组权限。
Mongodb已经定义好了的角色叫内建角色,我们也可以自定义角色。
Mongodb内建角色包括下面几类:
xx | 角色 |
---|---|
Database User Roles | readreadWrite |
Database Administration Roles | dbAdmindbOwneruserAdmin |
Cluster Administration Roles | clusterAdminclusterManagercluster MonitorhostManager |
Backup and Restoration Roles | backuprestore |
All-Database Roles(3.4) | readAnyDatabasereadWriteAnyDataba seuserAdminAnyDatabasedbAdminAnyD atabase |
Superuser Roles | root |
Internal Role | __system |
配置认证环境和认证登录¶
- Mongodb默认不开启认证
启动mongodb之后,创建一个管理用户
use admin
db.createUser({user:'yang',pwd:'yang',roles:[{ "role" : "root", "db" : "admin" }]});
关闭mongodb
db.shutdownServer()
启用mongodb授权认证
- 以–auth 启动mongod
- 旧版本在mongodb配置文件中加入auth=true,mongodb
3.x使用yaml语法,配置不一样,请参考
mongodb之yaml配置文件
第一次启用–auth时会出现:
2015-05-13T11:20:22.296+0800 I ACCESS [conn1] note: no users configured in admin.system.users, allowing localhost access
2015-05-13T11:20:22.297+0800 I ACCESS [conn1] Unauthorized not authorized on admin to execute command { getLog: “startupWarnings” }
2015-05-13T12:07:08.680+0800 I INDEX [conn1] build index on: admin.system.users properties: { v: 1, unique: true, key: { user: 1, db: 1 }, name: “user_1_db_1″, ns: “admin.system.users” }
即之前未定义过用户,所以mongod将允许本地直接访问
启动Mongodb
sudo service mongod start
认证登录
连接的时候认证
mongo ip:27017/admin -u yang -p
连接之后认证
> use admin
switched to db admin
> db.auth('yang','yang')
用户授权详解¶
语法:db.createUser({user:“UserName”,pwd:“Password”,roles:[{role:“RoleName”,db:“Target_DBName”}]})
创建用户有3项需要提供:用户名,密码,角色列表
创建用户示例
> use admin
switched to db admin
> db.createUser(
... {
... user: "yang",
... pwd: "yang"
... ,
... roles: [{role: "root",db: "admin"}]
... }
... )
Successfully added user: {
"user" : "yang",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}
>
use test
db.changeUserPassword('testuser','testPWD');
use test
db.grantRolesToUser( "testuser", [ { role: "read",db:"admin"} ] )
use test
db.revokeRolesFromUser("testuser",[ { role: "read",db:"admin"} ] )
首先进入目标库:use test db.dropUser(“testuser”)
注意事项¶
1、MongodbVOE版本太低,可能导致远程连接mongodb认证失败,建议升级版本或者更换其它GUI工具 2、远程连接Mongodb一定要把mongodb服务器的防火墙打开,否则连接不上
Mongodb错误解决方法¶
soft rlimits too low¶
WARNING: soft rlimits too low. rlimits set to 15664 processes, 65535 files. Number of processes should be at least 32767.5 : 0.5 times number of files.
数据库挂掉了¶
日志排查发现的问题
涉及到下面两个的语句, 执行时间较长, SORT_KEY_GENERATOR
执行时间超长
COLLSCAN
SORT_KEY_GENERATOR
解决: 索引
https://stackoverflow.com/questions/38380544/mongodb-query-faster-lte-than-gte
There are some issues with what you are trying to do:
$or queries use indexes differently
For $or queries to be able to use an index, all terms of the $or query must have an index. Otherwise, the query will be a collection scan. This is described in https://docs.mongodb.com/manual/reference/operator/query/or/#or-clauses-and-indexes
Too many indexes in the collection
Having too many indexes in a collection affects performance in more than one way, e.g., insert performance will suffer since you are turning one insert operation into many (i.e. one insert for the collection, and one additional insert for each indexes in your collection). Too many similar-looking indexes is also detrimental to the query planner, since it needs to choose one index out of many similar ones with minimal information on which index will be more performant.
Check the explain() output in mongo shell
The explain() output in the mongo shell is the best tool to discover
which index will be used by a query. Generally, you want to avoid any
COLLSCAN
stage (which means a collection scan) and
SORT_KEY_GENERATOR
stage (which means that MongoDB is using an
in-memory sort that is limited to 32MB, see
https://docs.mongodb.com/manual/tutorial/sort-results-with-indexes/).
Please see Explain Results for more details.
mysql¶
Mysql技巧¶
MySQL中查询所有数据库占用磁盘空间大小和单个库中所有表的大小的sql语句¶
select TABLE_SCHEMA, concat(truncate(sum(data_length)/1024/1024,2),' MB') as data_size,
concat(truncate(sum(index_length)/1024/1024,2),'MB') as index_size
from information_schema.tables
group by TABLE_SCHEMA
order by data_length desc;
select TABLE_NAME, concat(truncate(data_length/1024/1024,2),' MB') as data_size,
concat(truncate(index_length/1024/1024,2),' MB') as index_size
from information_schema.tables where TABLE_SCHEMA = 'TestDB'
group by TABLE_NAME
order by data_length desc;
注意替换以上的TestDB为具体的数据库名
MySQL错误记录¶
MySQL常见错误代码及代码说明¶
1005:创建表失败
1006:创建数据库失败
1007:数据库已存在,创建数据库失败<=================可以忽略
1008:数据库不存在,删除数据库失败<=================可以忽略
1009:不能删除数据库文件导致删除数据库失败
1010:不能删除数据目录导致删除数据库失败
1011:删除数据库文件失败
1012:不能读取系统表中的记录
1020:记录已被其他用户修改
1021:硬盘剩余空间不足,请加大硬盘可用空间
1022:关键字重复,更改记录失败
1023:关闭时发生错误
1024:读文件错误
1025:更改名字时发生错误
1026:写文件错误
1032:记录不存在<=============================可以忽略
1036:数据表是只读的,不能对它进行修改
1037:系统内存不足,请重启数据库或重启服务器
1038:用于排序的内存不足,请增大排序缓冲区
1040:已到达数据库的最大连接数,请加大数据库可用连接数
1041:系统内存不足
1042:无效的主机名
1043:无效连接
1044:当前用户没有访问数据库的权限
1045:不能连接数据库,用户名或密码错误
1048:字段不能为空
1049:数据库不存在
1050:数据表已存在
1051:数据表不存在
1054:字段不存在
1062:字段值重复,入库失败<==========================可以忽略
1065:无效的SQL语句,SQL语句为空
1081:不能建立Socket连接
1114:数据表已满,不能容纳任何记录
1116:打开的数据表太多
1129:数据库出现异常,请重启数据库
1130:连接数据库失败,没有连接数据库的权限
1133:数据库用户不存在
1141:当前用户无权访问数据库
1142:当前用户无权访问数据表
1143:当前用户无权访问数据表中的字段
1146:数据表不存在
1147:未定义用户对数据表的访问权限
1149:SQL语句语法错误
1158:网络错误,出现读错误,请检查网络连接状况
1159:网络错误,读超时,请检查网络连接状况
1160:网络错误,出现写错误,请检查网络连接状况
1161:网络错误,写超时,请检查网络连接状况
1169:字段值重复,更新记录失败
1177:打开数据表失败
1180:提交事务失败
1181:回滚事务失败
1203:当前用户和数据库建立的连接已到达数据库的最大连接数,请增大可用的数据库连接数或重启数据库
1205:加锁超时
1211:当前用户没有创建用户的权限
1216:外键约束检查失败,更新子表记录失败
1217:外键约束检查失败,删除或修改主表记录失败
1226:当前用户使用的资源已超过所允许的资源,请重启数据库或重启服务器
1227:权限不足,您无权进行此操作
1235:MySQL版本过低,不具有本功能
MySQL初始化故障排错集锦¶
需修改主机名的解析,使其和uname -n一样
错误示例2下面也是初始化数据库时可能会遇到的错误,原因是/tmp目录的权限问题:
解决办法
查看/tmp目录权限
修改权限 chmod -R 1777 /tmp/
可以手动告诉从库slave_sql线程让他忽略掉这个错误继续执行
mysql>set global sql_slave_skip_counter=1;
mysql>start slave;
或者运行 SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
跳过一行错误
主库可以配置某些语句不记录到binlog
InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED¶
执行语句的时候报如下错误
ERROR 1665 (HY000): Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.
mysql> quit
Bye
原因是transaction_isolation
使用的READ-COMMITTED
innodb的事务隔离级别是READ COMMITTED
或者READ UNCOMMITTED
模式时,BINLOG_FORMAT
不可以使用STATEMENT
模式
binlog_format
使用mixed
set global binlog_format=mixed
重新建立的会话session中binlog format会变为mixed模式。
或者修改innodb事务隔离级别
使用spring连接mysql的时候, 提示依赖使用SSL连接¶
具体报错
WARN: Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
可以关闭SSl认证, 或者提供证书
application.properties
文件修改,
添加参数?verifyServerCertificate=false&useSSL=false&requireSSL=false
spring.datasource.url=jdbc:mysql://localhost:3306/db_example?verifyServerCertificate=false&useSSL=false&requireSSL=false
mysql
MySQL¶
www.90root.com www.abcdocker.com
MySQL 5.7 yum安装¶
[root@ruin ~]# cat /etc/redhat-release
CentOS release 6.6 (Final)
[root@ruin ~]# uname -a
Linux ruin 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@ruin ~]# ifconfig|sed -n '2p'|awk -F'[ :]+' '{print $4}'
10.0.11.103
[root@ruin ~]# rpm -ivh http://repo.mysql.com//mysql57-community-release-el6-9.noarch.rpm
[root@ruin ~]# ll /etc/yum.repos.d/
总用量 16
-rw-r--r-- 1 root root 1991 10月 23 2014 CentOS-Base.repo
-rw-r--r-- 1 root root 1083 5月 15 2015 epel.repo
-rw-r--r-- 1 root root 1414 9月 12 18:32 mysql-community.repo
-rw-r--r-- 1 root root 1440 9月 12 18:32 mysql-community-source.repo
[root@ruin ~]# yum repolist enabled | grep "mysql.*-community.*"
mysql-connectors-community MySQL Connectors Community 30
mysql-tools-community MySQL Tools Community 42
mysql57-community MySQL 5.7 Community Server 162
[root@ruin ~]# yum install mysql-community-server -y
[root@ruin ~]# /etc/init.d/mysqld start
[root@ruin ~]# netstat -lntp|grep mysql
tcp 0 0 :::3306 :::* LISTEN 4493/mysqld
[root@ruin ~]# grep 'temporary password' /var/log/mysqld.log
2017-01-06T07:07:07.718302Z 1 [Note] A temporary password is generated for root@localhost: hr6Oe8_o-EA(
[root@ruin ~]# mysql -uroot -phr6Oe8_o-EA\(
mysql>
MySQL设置root密码¶
[root@ruin ~]# mysql -uroot -phr6Oe8_o-EA\(
mysql> use mysql
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
#需要设置密码,详见下面参考连接
mysql> set password=password('12345');
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
#增加了密码强度验证,详见下面参考连接
mysql> set password=password('Lr1993*0614_');
mysql> flush privileges;
#当没有密码时设置新密码为123456
[root@ruin ~]# mysqladmin -u root password 123456
#当存在密码时
[root@ruin ~]# mysqladmin -uroot -pLr2017*0614_ password Lr2017*0000__
[root@ruin ~]# mysql -uroot -pLr2017*0614_
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
[root@ruin ~]# mysql -uroot -pLr2017*0000__
mysql>
[root@ruin ~]# mysql -uroot -pLr2017*0000__
mysql> use mysql;
mysql> update user set password=password('Lr2017*0614_') where user='root';
ERROR 1054 (42S22): Unknown column 'password' in 'field list'
#没有该字段,详见下面参考连接
mysql> update mysql.user set authentication_string=password('Lr2017*0614_') where user='root';
mysql> flush privileges;
[root@ruin ~]# vi /etc/my.cnf
[root@ruin ~]# grep -A1 '\[mysqld\]' /etc/my.cnf
[mysqld]
skip-grant-tables
[root@ruin ~]# /etc/init.d/mysqld restart
停止 mysqld: [确定]
正在启动 mysqld: [确定]
[root@ruin ~]# mysql
mysql> update mysql.user set authentication_string=password('Lr2017*0610_') where user='root';
mysql> flush privileges;
[root@ruin ~]# vi /etc/my.cnf #删除skip-grant-tables
[root@ruin ~]# /etc/init.d/mysqld restart
[root@ruin ~]# mysql -uroot -pLr2017*0610_
mysql>
[root@ruin ~]# netstat -lntp|grep mysql
tcp 0 0 :::3306 :::* LISTEN 5699/mysqld
[root@ruin ~]# killall -TERM mysqld
[root@ruin ~]# netstat -lntp|grep mysql
[root@ruin ~]# mysqld_safe --skip-grant-tables &
[1] 5751
[root@ruin ~]# mysql
mysql> update mysql.user set authentication_string=password('Lr2017*0614_') where user='root';
mysql> flush privileges;
[root@ruin ~]# killall -TERM mysqld
[root@ruin ~]# /etc/init.d/mysqld start
[root@ruin ~]# mysql -uroot -pLr2017*0614_
mysql>
MySQL授权访问¶
#5.7版本默认localhost连接
[root@ruin ~]# mysql -h10.0.11.103 -uroot -pLr2017*0614_
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1130 (HY000): Host '10.0.11.103' is not allowed to connect to this MySQL server
[root@ruin ~]# mysql -uroot -pLr2017*0614_
mysql> select host,user from mysql.user;
+-----------+-----------+
| host | user |
+-----------+-----------+
| localhost | mysql.sys |
| localhost | root |
+-----------+-----------+
2 rows in set (0.00 sec)
grant 命令用于授权,详情查看help
mysql> help grant;
#简单来说就是grant 权限 on 数据库.表 to 用户@主机 identified by '密码' with grant option
mysql> grant all privileges on *.* to root@'10.0.11.%' identified by 'Lr2017*0614_' with grant option;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show grants for root@'10.0.11.%';
+---------------------------------------------------------------------+
| Grants for root@10.0.11.% |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.0.11.%' WITH GRANT OPTION |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> show grants for root@'10.0.11.%';
+---------------------------------------------------------------------+
| Grants for root@10.0.11.% |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.0.11.%' WITH GRANT OPTION |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select host,user from mysql.user;
+-----------+-----------+
| host | user |
+-----------+-----------+
| 10.0.11.% | root |
| localhost | mysql.sys |
| localhost | root |
+-----------+-----------+
3 rows in set (0.00 sec)
mysql> quit
Bye
[root@ruin ~]# mysql -h10.0.11.103 -uroot -pLr2017*0614_
mysql>
mysql> create user test@10.0.11.103 identified by 'Test*123_';
mysql> grant select,update,delete,insert on *.* to test@10.0.11.103;
mysql> flush privileges;
mysql> show grants for test@'10.0.11.103';
+---------------------------------------------------------------------+
| Grants for test@10.0.11.103 |
+---------------------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'test'@'10.0.11.103' |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select host,user from mysql.user;
+-------------+-----------+
| host | user |
+-------------+-----------+
| 10.0.11.% | root |
| 10.0.11.103 | test |
| localhost | mysql.sys |
| localhost | root |
+-------------+-----------+
4 rows in set (0.00 sec)
创建库,表,用户¶
#查看创库帮助文档
mysql> help create database;
#创建utf8字符集和collate校对字符集为utf8_general_ci的库
mysql> create database liurui default charset utf8 collate utf8_general_ci;
#查看数据库支持的字符集和collate校对字符集
mysql> show character set;
#查看库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| liurui |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
#查看创库语句
mysql> show create database liurui;
+----------+-----------------------------------------------------------------+
| Database | Create Database |
+----------+-----------------------------------------------------------------+
| liurui | CREATE DATABASE `liurui` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+----------+-----------------------------------------------------------------+
1 row in set (0.00 sec)
mysql操作¶
安全删除binlog日志¶
有三种解决方法:
- 关闭mysql主从,关闭binlog
- 开启mysql主从,设置expire_logs_days
- 手动清除binlog文件
重启mysql,开启mysql主从,设置expire_logs_days¶
# vim /etc/my.cnf //修改expire_logs_days,x是自动删除的天数,一般将x设置为短点,如10
expire_logs_days = x //二进制日志自动删除的天数。默认值为0,表示“没有自动删除”
此方法需要重启mysql,附录有关于expire_logs_days的英文说明
当然也可以不重启mysql,开启mysql主从,直接在mysql里设置expire_logs_days,临时
mysql> show binary logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000678 | 558332 |
+------------------+-----------+
1 row in set (0.00 sec)
mysql> show variables like '%log%';
mysql> set global expire_logs_days = 10;
mysql> show variables like '%expire_logs_days%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| expire_logs_days | 10 |
+------------------+-------+
1 row in set (0.02 sec)
手动清除binlog文件¶
mysql> PURGE MASTER LOGS BEFORE DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY); //删除10天前的MySQL binlog日志,附录2有关于PURGE MASTER LOGS手动删除用法及示例
mysql> show master logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000678 | 558332 |
+------------------+-----------+
1 row in set (0.02 sec)
oracle¶
oracle¶
Linux下oracle数据库启动和关闭操作¶
输入 connect /as sysdba
输入 startup
不带参数,启动数据库实例并打开数据库,以便用户使用数据库,在多数情况下,使用这种方式!
nomount,只启动数据库实例,但不打开数据库,在你希望创建一个新的数据库时使用,或者在你需要这样的时候使用!
mount,在进行数据库更名的时候采用。这个时候数据库就打开并可以使用了!
Normal 需要等待所有的用户断开连接
Immediate 等待用户完成当前的语句
Transactional 等待用户完成当前的事务
Abort 不做任何等待,直接关闭数据库
normal 需要在所有连接用户断开后才执行关闭数据库任务,所以有的时候看起来好象命令没有运行一样!在执行这个命令后不允许新的连接
immediate 在用户执行完正在执行的语句后就断开用户连接,并不允许新用户连接。
transactional 在拥护执行完当前事物后断开连接,并不允许新的用户连接数据库。
abort 执行强行断开连接并直接关闭数据库。
退出sqlplus模式
输入 lsnrctl start
redis¶
Redis¶
什么是Redis?
- Redis是一个开源的使用ANSI C语言编写的Key-Value 内存数据库
- 读写性能强,支持多种数据类型
- 把数据存储在内存中的高速缓存
- 作者Salvatore Sanfilippo
Redis简介¶
项目 | Redis | Memcached |
---|---|---|
过期策略 | 支持 | 支持 |
数据类型 | 五种数据类型 | 单一数据类型 |
持久化 | 支持 | 不支持 |
主从复制 | 支持 | 不支持 |
虚拟内存 | 支持 | 不支持 |
性能 | 性能 | 强 |
数据缓存
提高访问性能,使用的方式与memcache相同
会话缓存(Session Cache)
保存web会话信息
排行榜/计数器
Nginx+lua+Redis计数器进行IP自动封禁。
消息队列
构建实时消息系统,聊天,群聊
安装配置¶
[root@web01 ~]# mkdir -p /data/server
[root@web01 ~]# cd /data/server
[root@web01 server]# wget http://download.redis.io/releases/redis-3.2.6.tar.gz
[root@web01 server]# tar xf redis-3.2.6.tar.gz
[root@web01 server]# cd redis-3.2.6
[root@web01 redis-3.2.6]# make
[root@web01 redis-3.2.6]# ln -s /data/server/redis-3.2.6/ /data/server/redis
安装注意:
直接make就行,在哪make的就安装在哪,不需要make install
启动Redis
[root@web01 redis-3.2.6]# src/redis-server
56331:C 30 Dec 09:57:06.373 # Warning: no config file specified, using the default config. In order to specify a config file use src/redis-server /path/to/redis.conf
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 56331
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
56331:M 30 Dec 09:57:06.378 # Server started, Redis version 3.2.6
56331:M 30 Dec 09:57:06.378 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
56331:M 30 Dec 09:57:06.379 * The server is now ready to accept connections on port 6379
ctrl +c
默认Redis在前台启动,修改配置文件,让它后台启动
修改daemonize
[root@web01 redis-3.2.6]# vim redis.conf
daemonize yes # 128行
后台运行,需要指定配置文件
[root@web01 redis-3.2.6]# src/redis-server ./redis.conf
[root@web01 redis-3.2.6]# ss -lntup|grep 6379
tcp LISTEN 0 511 127.0.0.1:6379 *:* users:(("redis-server",56389,4))
登录客户端,默认为6379
[root@web01 redis-3.2.6]# src/redis-cli
127.0.0.1:6379>
127.0.0.1:6379> set foo bar ## foo 为 key,bar为值
OK
127.0.0.1:6379> get foo
"bar"
127.0.0.1:6379> set name yjj
OK
127.0.0.1:6379> keys *
1) "name"
2) "foo"
127.0.0.1:6379> get name
"yjj"
127.0.0.1:6379> SHUTDOWN
not connected> exit
脚本内容见下面
[root@web01 data]# cd /data/server/
[root@web01 server]# cp redis.sh /etc/init.d/redis
[root@web01 server]# chmod +x /etc/init.d/redis
[root@web01 server]# service redis start
Starting Redis server...
[root@web01 server]# ss -lntup|grep redis
tcp LISTEN 0 511 127.0.0.1:6379 *:* users:(("redis-server",56528,4))
脚本如果无法控制,因为pid文件名字问题,注意检查
[root@web01 server]# grep pidfile /data/server/redis/redis.conf
pidfile /var/run/redis.pid
[root@web01 server]# grep PIDFILE= /etc/init.d/redis
PIDFILE=/var/run/redis.pid
脚本内容:
[root@web01 redis]# cat /etc/init.d/redis
#!/bin/sh
#
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
# chkconfig: - 85 15
REDISPORT=6379
EXEC=/data/server/redis/src/redis-server
CLIEXEC=/data/server/redis/src/redis-cli
PIDFILE=/var/run/redis.pid
CONF="/data/server/redis/redis.conf"
case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed"
else
echo "Starting Redis server..."
$EXEC $CONF
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE does not exist, process is not running"
else
PID=$(cat $PIDFILE)
echo "Stopping ..."
$CLIEXEC -p $REDISPORT shutdown
while [ -x /proc/${PID} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
echo "Redis stopped"
fi
;;
status)
if [ -f $PIDFILE ]
then
echo "redis server is running....."
else
echo "redis is stopped"
fi
;;
*)
echo "Please use start or stop or status"
;;
esac
有需要请自行修改
#!/bin/bash
mkdir -p /data/rpm
cd /data/rpm
[ -f /data/rpm/redis-3.2.0.tar.gz ] || wget http://download.redis.io/releases/redis-3.2.0.tar.gz
tar zxvf redis-3.2.0.tar.gz
mv redis-3.2.0 /data/server/redis
cd /data/server/redis
make
cp /data/scripts/redis/files/redis /etc/init.d
rm -rf /data/server/redis/redis.conf
cp /data/scripts/redis/files/redis.conf /data/server/redis/redis.conf
chmod +x /etc/init.d/redis
ln -s /data/server/redis/src/redis-cli /usr/bin/redis-cli
chkconfig redis on
service redis start
service redis status
主目录下:redis.conf
daemonize no ---> yes # 后台运行
port 6379 # 端口Alessia Merz
appendonly no ---> yes # 日志开关
logfile stdout ---> ./logs/redis.log # 日志文件
dbfilename dump.rdb # 持久化数据文件
- Redis 3.2 新特性
- 解决访问安全
- Bind与protected-mode
- 禁止protected-mode
- 增加bind
- 增加requirepass
- auth {password}
[root@web01 redis]# grep -vE "^$|#" redis.conf
bind 127.0.0.1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile /var/run/redis.pid
loglevel notice
logfile ""
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
[root@web01 redis]# head redis.conf
bind 127.0.0.1
protected-mode yes
requirepass root ## 密码,设置密码之后,关闭redis可以使用shutdown命令
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile /var/run/redis.pid
重启redis
[root@web01 redis]# src/redis-cli
127.0.0.1:6379> keys *
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> config get *
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "root"
127.0.0.1:6379> config get loglevel
1) "loglevel"
2) "notice"
127.0.0.1:6379> config set loglevel debug
OK
127.0.0.1:6379> config get loglevel
1) "loglevel"
2) "debug"

redis1-20161230
- RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
- AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。
- Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下,当 Redis 重启时,它会优先使用AOF 文件来还原数据集,因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。
- 你甚至可以关闭持久化功能,让数据只在服务器运行时存在。
日志文件 appendonly yes/no
压缩
同步
appendfsync everysec
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
核心实践¶
数据类型

redis2-20161230
- KEYS * 查看KEY支持通配符
- DEL删除给定的一个或多个key
- EXISTS 检查是否存在
- EXPIRE 设定生存时间
- TTL以秒为单位返回过期时间
- DUMP RESTORE序例化与反序列化
- PEXIRE PTTL PERSIST 以毫秒为单位
- RENAME 变更KEY名
- SORT 键值排序
- TYPE返回键所存储值的类型
SET name "guohz"
Get name
一个键最大能存储512MB
Append将 value 追加到 key 原来的值的末尾
Mget mset同时设置一个或多个键值对
STRLEN 返回字符串长度
INCR DECR 将值增或减1
INCRBY DECRBY 减去指定量
DECRBY count 20
- Redis hash 是一个键值对集合。
- Redis hash是一个string类型的field和value的映射表
- hash特别适合用于存储对象。
- 每个 hash 可以存储 2^32-1 键值对
HSET HGET 设置返回单个值
HMSET HMGET 设置返回多个值
Hmset user name guo sex male age 22
HGETALL 返回KEY的所有键值
HEXSITS HLEN
HKEYS HVALS 获取所有字段或值
HDEL 删除key 中的一个或多个指定域
- Redis列表是简单的字符串列表。
- 按照插入顺序排序每个
- LIST可以存储 2^32 -1 键值对
LPUSH 将一个或多个值插入到列表头部
RPUSH将一个或多个值插入到列表尾部
LPOP/RPOP 移除表头/尾的元素
LLEN 返回列表长度
LRANGE 返回指定的元素
LREM greet 2 morning 删除前两个morning
LREM greet -1 morning 删除后一个morning
LREM greet 0 hello 删除所有hello
Lindex 返回列表 key 中下标为 index 的元素.
LSET key index value
将列表 key 下标为 index 的元素的值设置为 value
LINSERT 插入数据位于某元素之前或之后。
LINSERT key BEFORE|AFTER pivot value
操作
127.0.0.1:6379> lpush list1 yang jin jie niu bi
(integer) 5
127.0.0.1:6379> lrange list1 0 2
1) "bi"
2) "niu"
3) "jie"
127.0.0.1:6379> lrange list1 0 10
1) "bi"
2) "niu"
3) "jie"
4) "jin"
5) "yang"
127.0.0.1:6379> lpush list1 z
(integer) 6
127.0.0.1:6379> lrange list1 0 10
1) "z"
2) "bi"
3) "niu"
4) "jie"
5) "jin"
6) "yang"
127.0.0.1:6379> rpush list1 yjj
(integer) 7
127.0.0.1:6379> lrange list1 0 10
1) "z"
2) "bi"
3) "niu"
4) "jie"
5) "jin"
6) "yang"
7) "yjj"
127.0.0.1:6379> lpop list1
"z"
127.0.0.1:6379> lrange list1 0 10
1) "bi"
2) "niu"
3) "jie"
4) "jin"
5) "yang"
6) "yjj"
127.0.0.1:6379> rpop list1
"yjj"
127.0.0.1:6379> lrange list1 0 10
1) "bi"
2) "niu"
3) "jie"
4) "jin"
5) "yang"
127.0.0.1:6379> lpush list1 morning afternoon morning
(integer) 8
127.0.0.1:6379> lrange list1 0 10
1) "morning"
2) "afternoon"
3) "morning"
4) "bi"
5) "niu"
6) "jie"
7) "jin"
8) "yang"
127.0.0.1:6379> lrem list1 2 morning
(integer) 2
127.0.0.1:6379> lrange list1 0 10
1) "afternoon"
2) "bi"
3) "niu"
4) "jie"
5) "jin"
6) "yang"
127.0.0.1:6379> lindex list1 2
"niu"
127.0.0.1:6379> lset list1 0 ok
OK
127.0.0.1:6379> lindex list1 0
"ok"
127.0.0.1:6379> linsert list1 after jie 123
(integer) 7
127.0.0.1:6379> lrange list1 0 10
1) "ok"
2) "bi"
3) "niu"
4) "jie"
5) "123"
6) "jin"
7) "yang"
- Redis的Set是string类型的无序集合。
- 集合成员是唯一的,这就意味着集合中不能出现重复的数据。
- Redis 中集合是通过哈希表实现的。
SADD key member [member ...]
将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
SCARD key 返回集合KEY的基数
SDIFF key1 key2
返回一个集合的全部成员,该集合是所有给定集合之间的差集,注意前后顺序。比较后Sdiffstore进行存储
SMEMBERS key 查看成员的值
SUNION 返回一个集合的全部成员,该集合是所有给定集合的并集。SUNIONSTORE
SINTER key [key ...]
返回一个集合的全部成员,该集合是所有给定集合的交集。SINTERSTORE
SISMEMBER 判断是否属于该集合
SMOVE source destination member
将 member 元素从 source 集合移动到 destination 集合。
SPOP SRANDMEMBER 移出或读取一个随机元素。
SREM 移除集合中一个或多个元素
127.0.0.1:6379> sadd set1 guohongze ztt zhao
(integer) 3
127.0.0.1:6379> scard set1
(integer) 3
127.0.0.1:6379> sadd set2 guohongze yangjinjie ztt lidaozhang
(integer) 4
127.0.0.1:6379> sdiff set1 set2 ## 用第一个去跟第二个比较,注意下面区别
1) "zhao"
127.0.0.1:6379> sdiff set2 set1
1) "yangjinjie"
2) "lidaozhang"
- Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
- 每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
- 有序集合的成员是唯一的,但分数(score)却可以重复。
ZADD key score member
ZCARD 返回有序集 key 的基数
ZCOUNT key min max
ZCOUNT salary 2000 5000 计算2000到5000之间的数
ZSCORE key member 返回值
ZINCRBY key increment member
为score 值加上增量 increment,负数为减法
ZINCRBY salary 2000 tom
ZRANGE key start stop 返回指定区间成员
ZRANGE salary 0 -1 WITHSCORES # 显示所有
ZRANGEBYSCORE
有序集成员按 score 值递增(从小到大)次序排列。
ZRANGEBYSCORE salary -inf +inf WITHSCORES
ZRANK key member 显示排名
ZRANGE salary 0 -1 WITHSCORES
ZRANGE salary tom
ZREM key member 移除一个或多个成员。
ZREMRANGEBYRANK ZREMRANGEBYSCORE 移除
ZREVRANGE key start stop [WITHSCORES]
递减返回值
127.0.0.1:6379> zadd salary 10000 guohongze
(integer) 1
127.0.0.1:6379> zscore salary guohongze
"10000"
127.0.0.1:6379> zadd salary 15000 zhaobanzhang
(integer) 1
127.0.0.1:6379> zadd salary 13000 laoban
(integer) 1
127.0.0.1:6379> zadd salary 9000 xiaoming
(integer) 1
127.0.0.1:6379> zcount salary 10000 20000
(integer) 3
127.0.0.1:6379> zincrby salary 1000 guohongze
"11000"
127.0.0.1:6379> zscore salary guohongze
"11000"
127.0.0.1:6379> zincrby salary -1000 xiaoming
"8000"
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "xiaoming"
2) "8000"
3) "guohongze"
4) "11000"
5) "laoban"
6) "13000"
7) "zhaobanzhang"
8) "15000"
127.0.0.1:6379> zrange salary 0 1 withscores
1) "xiaoming"
2) "8000"
3) "guohongze"
4) "11000"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "xiaoming"
2) "8000"
3) "guohongze"
4) "11000"
5) "laoban"
6) "13000"
7) "zhaobanzhang"
8) "15000"
127.0.0.1:6379> zrangebyscore salary 10000 20000 withscores
1) "guohongze"
2) "11000"
3) "laoban"
4) "13000"
5) "zhaobanzhang"
6) "15000"
127.0.0.1:6379> zrangebyscore salary 10000 20000
1) "guohongze"
2) "laoban"
3) "zhaobanzhang"
127.0.0.1:6379> zrem salary xiaoming
(integer) 1
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "zhaobanzhang"
2) "15000"
3) "laoban"
4) "13000"
5) "guohongze"
6) "11000"
Redis 高级应用¶
消息模式
发布消息通常有两种模式:队列模式(queuing)和发布-订阅模式(publish-subscribe)。队列模式中,consumers可以同时从服务端读取消息,每个消息只被其中一个consumer读到。
发布-订阅模式中消息被广播到所有的consumer中,topic中的消息将被分发到组中的一个成员中。同一组中的consumer可以在不同的程序中,也可以在不同的机器上。
Redis 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。

redis3-20161230
SUBSCRIBE mq1 #客户端 PUBLISH mq1 “Redis is a great caching technique”
PSUBSCRIBE订阅一个或多个符合给定模式的频道。
psubscribe news.* tech.*
PUBLISH channel message
将信息 message 发送到指定的频道 channel 。返回值代表消费者数量
pubsub channels 显示订阅频道
PUBSUB NUMSUB news.it 打印各频道订阅者数量
PUNSUBSCRIBE 退订多个频道
SUBSCRIBE 订阅给定的一个或多个频道的信息。
UNSUBSCRIBE 退订频道
实例
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
新开窗口,继续做如上操作
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
显示订阅频道
127.0.0.1:6379> pubsub channels
1) "channel1"
127.0.0.1:6379> pubsub numsub channel1
1) "channel1"
2) (integer) 2

订阅发布实例-20161230
- Redis 事务可以一次执行多个命令。
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 原子性:事务中的命令要么全部被执行,要么全部都不执行。
- 执行过程
- 开始事务。
- 命令入队。
- 执行事务。
事务命令
DISCARD
取消事务,放弃执行事务块内的所有命令。
EXEC
执行所有事务块内的命令。
MULTI
标记一个事务块的开始。
UNWATCH
取消 WATCH 命令对所有 key 的监视。
WATCH key [key ...]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
事务执行
范例:
zadd salary 2000 guohongze
zadd salary 3000 test
ZRANGE salary 0 -1 WITHSCORES
MULTI
- ZINCRBY salary 1000 guohongze
- zincrby salary -1000 test
- EXEC
[root@web01 ~]# redis-cli
127.0.0.1:6379> auth root
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> zincrby salary -1000 laoban
QUEUED
127.0.0.1:6379> ZINCRBY salary 1000 guohongze
QUEUED
127.0.0.1:6379> EXEC
1) "12000"
2) "12000"
执行exec 前
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "guohongze"
2) "11000"
3) "laoban"
4) "13000"
5) "zhaobanzhang"
6) "15000"
执行exec后
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "guohongze"
2) "12000"
3) "laoban"
4) "12000"
5) "zhaobanzhang"
6) "15000"
- Info
- Clinet list
- Client kill ip:port
- config get *
- CONFIG RESETSTAT 重置统计
- CONFIG GET/SET 动态修改
- Dbsize 查看key的数量
- FLUSHALL 清空所有数据 select 1
- FLUSHDB 清空当前库
- MONITOR 监控实时指令
- SHUTDOWN 关闭服务器
- save 将当前数据保存
- SLAVEOF host port 主从配置
- SLAVEOF NO ONE
- SYNC 主从同步
- ROLE返回主从角色
127.0.0.1:6379> client list
id=6 addr=127.0.0.1:38172 fd=5 name= age=13214 idle=1132 flags=N db=0 sub=1 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=subscribe
id=8 addr=127.0.0.1:40574 fd=7 name= age=1166 idle=899 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=pubsub
id=9 addr=127.0.0.1:40658 fd=6 name= age=752 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=11 addr=127.0.0.1:40739 fd=8 name= age=353 idle=353 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=command
127.0.0.1:6379> dbsize
(integer) 5
127.0.0.1:6379> monitor
OK
新开窗口 执行
127.0.0.1:6379> set foo bar
OK
原窗口查看
1483082063.661610 [0 127.0.0.1:40574] "set" "foo" "bar"
- Slow log 是 Redis 用来记录查询执行时间的日志系统。
- slow log 保存在内存里面,读写速度非常快
- 可以通过改写 redis.conf 文件或者用 CONFIG GET 和 CONFIG SET 命令对它们动态地进行修改
- slowlog-log-slower-than 10000 超过多少微秒
- CONFIG SET slowlog-log-slower-than 100
- CONFIG SET slowlog-max-len 1000 保存多少条慢日志
- CONFIG GET slow*
- SLOWLOG GET
- SLOWLOG RESET
127.0.0.1:6379> config get slow*
1) "slowlog-log-slower-than"
2) "10000"
3) "slowlog-max-len"
4) "128"
127.0.0.1:6379> config set slowlog-max-len 256
OK
127.0.0.1:6379> config get slow*
1) "slowlog-log-slower-than"
2) "10000"
3) "slowlog-max-len"
4) "256"
- CONFIG GET dir 获取当前目录
- Save 备份(无持久化策略时),生成时在redis当前目录中。
- 恢复时只需将dump.rdb放入redis当前目录
127.0.0.1:6379> config get dir
1) "dir"
2) "/"
127.0.0.1:6379> config set dir /data/server/redis
OK
127.0.0.1:6379> config get dir
1) "dir"
2) "/data/server/redis-3.2.6"
127.0.0.1:6379> save
OK
redis 复制¶
- 从 Redis 2.8 开始,使用异步复制。
- 一个主服务器可以有多个从服务器。
- 从服务器也可以有自己的从服务器。
- 复制功能不会阻塞主服务器。
- 可以通过复制功能来让主服务器免于执行持久化操作,由从服务器去执行持久化操作即可。
slaveof 192.168.1.1 6379
slave-read-only 只读模式
masterauth <password> 主服务器设置密码后需要填写密码
min-slaves-to-write <number of slaves>
从服务器不少于,才允许写入
min-slaves-max-lag <number of seconds>
从服务器延迟不大于
CONFIG set slave-read-only yes
Config set masterauth root
INFO replication
SLAVEOF NO ONE 升级至MASTER
[root@web01 server]# pwd
/data/server
[root@web01 server]# mkdir 8000 8001
cp redis/redis.conf 8000
cp redis/redis.conf 8001
cp redis/src/redis-server 8000
cp redis/src/redis-server 8001
修改配置文件

redis-master-slave-20161230
[root@web01 server]# ./8000/redis-server ./8000/redis.conf
[root@web01 server]# ./8001/redis-server ./8001/redis.conf
[root@web01 8002]# ss -lntup|grep redis
tcp LISTEN 0 511 127.0.0.1:8000 *:* users:(("redis-server",57766,4))
tcp LISTEN 0 511 127.0.0.1:8001 *:* users:(("redis-server",57867,4))
tcp LISTEN 0 511 127.0.0.1:6379 *:* users:(("redis-server",57142,4))
[root@web01 server]# redis-cli -p 8000
127.0.0.1:8000> role
1) "master"
2) (integer) 197
3) 1) 1) "127.0.0.1"
2) "8001"
3) "197"
再加一个slave
[root@web01 server]# cp -r 8001 8002
[root@web01 server]# cd 8002
[root@web01 8002]# ll
total 7652
-rw-r--r-- 1 root root 76 Dec 30 16:06 dump.rdb
-rw-r--r-- 1 root root 1194 Dec 30 16:06 redis.conf
-rwxr-xr-x 1 root root 7826344 Dec 30 16:06 redis-server
[root@web01 8002]# rm -f dump.rdb
[root@web01 8002]# vim redis.conf
port 8002
pidfile /var/run/redis_8002.pid
---------
[root@web01 8002]# ss -lntup |grep 8002
tcp LISTEN 0 511 127.0.0.1:8002 *:* users:(("redis-server",57893,4))
8000 查看角色
127.0.0.1:8000> role
1) "master"
2) (integer) 421
3) 1) 1) "127.0.0.1"
2) "8001"
3) "421"
2) 1) "127.0.0.1"
2) "8002"
3) "421"
客户端登录执行slaveof,重启后失效
[root@web01 server]# cp -r 8000 8003
[root@web01 server]# cd 8003
[root@web01 8003]# ll
total 7652
-rw-r--r-- 1 root root 76 Dec 30 16:09 dump.rdb
-rw-r--r-- 1 root root 1171 Dec 30 16:09 redis.conf
-rwxr-xr-x 1 root root 7826344 Dec 30 16:09 redis-server
[root@web01 8003]# rm -f dump.rdb
[root@web01 8003]# vim redis.conf
port 8003
pidfile /var/run/redis_8003.pid
----配置文件没有添加slaveof
[root@web01 8003]# ./redis-server ./redis.conf
8000 role角色没有变化
127.0.0.1:8000> role
127.0.0.1:8003> slaveof 127.0.0.1 8000
OK
重新查看8000 role角色
127.0.0.1:8000> role
1) "master"
2) (integer) 869
3) 1) 1) "127.0.0.1"
2) "8001"
3) "869"
2) 1) "127.0.0.1"
2) "8002"
3) "869"
3) 1) "127.0.0.1"
2) "8003"
3) "869"
开启主从复制之后,从库自动开启read-only
127.0.0.1:8003> config get slave-read-only
1) "slave-read-only"
2) "yes"
info replication
127.0.0.1:8003> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:8000
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:1135
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
主从手动切换
127.0.0.1:8000> shutdown
not connected> exit
[root@web01 server]# redis-cli -p 8001
127.0.0.1:8001> slaveof no one
OK
127.0.0.1:8001> role
1) "master"
2) (integer) 0
3) (empty list or set)
127.0.0.1:8001> exit
[root@web01 server]# redis-cli -p 8002
127.0.0.1:8002> slaveof 127.0.0.1 8001
OK
127.0.0.1:8002> role
1) "slave"
2) "127.0.0.1"
3) (integer) 8001
4) "connected"
5) (integer) 1
127.0.0.1:8003> slaveof 127.0.0.1 8001
OK
[root@web01 server]# redis-cli -p 8001
127.0.0.1:8001> role
1) "master"
2) (integer) 71
3) 1) 1) "127.0.0.1"
2) "8002"
3) "71"
2) 1) "127.0.0.1"
2) "8003"
3) "71"
Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。
功能
配置记录,详细操作见下文
mkdir 8000 8001 8002
cp src/redis-sentinel sentinel.conf 8000 8001 8002
cd 7000
vim sentinel
sentinel monitor mymaster 127.0.0.1 6380 2
./redis-sentinel ./sentinel.conf
配置文件:
指定监控master
sentinel monitor mymaster 127.0.0.1 6379 2 ## sentinel也可以做高可用,后面的2表示两台sentinel同时检测到master挂掉
安全信息
sentinel auth-pass mymaster luyx30
超过15000毫秒后认为主机宕机
sentinel down-after-milliseconds mymaster 15000
和当主从切换多久后认为主从切换失败
sentinel failover-timeout mymaster 900000
这两个配置后面的数量主从机需要一样
sentinel leader-epoch mymaster 1
sentinel config-epoch mymaster 1
具体查看sentinel高可用
恢复现场
[root@web01 8000]# ./redis-server ./redis.conf
[root@web01 8000]# redis-cli -p 8001
127.0.0.1:8001> slaveof 127.0.0.1 8000
OK
127.0.0.1:8001>
[root@web01 8000]# redis-cli -p 8002
127.0.0.1:8002> slaveof 127.0.0.1 8000
OK
127.0.0.1:8002>
[root@web01 8000]# redis-cli -p 8003
127.0.0.1:8003> slaveof 127.0.0.1 8000
OK
127.0.0.1:8003>
[root@web01 8000]# redis-cli -p 8000
127.0.0.1:8000> role
1) "master"
2) (integer) 29
3) 1) 1) "127.0.0.1"
2) "8001"
3) "29"
2) 1) "127.0.0.1"
2) "8002"
3) "29"
3) 1) "127.0.0.1"
2) "8003"
3) "29"
[root@web01 server]# pwd
/data/server
[root@web01 server]# mkdir sentinel
[root@web01 server]# cp redis/sentinel.conf sentinel/
[root@web01 server]# cp redis/src/redis-sentinel sentinel/
[root@web01 sentinel]# ll
total 7652
-rwxr-xr-x 1 root root 7826344 Dec 30 16:32 redis-sentinel
-rw-r--r-- 1 root root 7606 Dec 30 16:32 sentinel.conf
[root@web01 sentinel]# egrep -v "^$|#" sentinel.conf > sentinel.conf1
[root@web01 sentinel]# mv sentinel.conf sentinel.conf.bak
[root@web01 sentinel]# mv sentinel.conf1 sentinel.conf
[root@web01 sentinel]# ls
redis-sentinel sentinel.conf sentinel.conf.bak
[root@web01 sentinel]# cat sentinel.conf
port 26379
dir /tmp
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
[root@web01 sentinel]# sed -i.ori 's#127.0.0.1 6379 2#127.0.0.1 8000 1#g' sentinel.conf
[root@web01 sentinel]# cat sentinel.conf
port 26379
dir /tmp
sentinel monitor mymaster 127.0.0.1 8000 1
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
启动sentinel
[root@web01 sentinel]# ./redis-sentinel sentinel.conf
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 58031
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
58031:X 30 Dec 16:38:20.142 # Sentinel ID is ee6562c23b9b3e7309015019318659860351105a
58031:X 30 Dec 16:38:20.143 # +monitor master mymaster 127.0.0.1 8000 quorum 1
58031:X 30 Dec 16:38:20.144 * +slave slave 127.0.0.1:8001 127.0.0.1 8001 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:38:20.146 * +slave slave 127.0.0.1:8002 127.0.0.1 8002 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:38:20.149 * +slave slave 127.0.0.1:8003 127.0.0.1 8003 @ mymaster 127.0.0.1 8000
此时它会自动在配置文件里面写入部分内容
新开窗口,关闭8000 redis
127.0.0.1:8000> SHUTDOWN [NOSAVE|SAVE]
之前窗口会显示日志信息:
58031:X 30 Dec 16:43:55.941 # +sdown master mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:55.941 # +odown master mymaster 127.0.0.1 8000 #quorum 1/1
58031:X 30 Dec 16:43:55.941 # +new-epoch 1
58031:X 30 Dec 16:43:55.941 # +try-failover master mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:55.943 # +vote-for-leader ee6562c23b9b3e7309015019318659860351105a 1
58031:X 30 Dec 16:43:55.943 # +elected-leader master mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:55.943 # +failover-state-select-slave master mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:56.000 # +selected-slave slave 127.0.0.1:8002 127.0.0.1 8002 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:56.000 * +failover-state-send-slaveof-noone slave 127.0.0.1:8002 127.0.0.1 8002 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:56.052 * +failover-state-wait-promotion slave 127.0.0.1:8002 127.0.0.1 8002 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:56.370 # +promoted-slave slave 127.0.0.1:8002 127.0.0.1 8002 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:56.370 # +failover-state-reconf-slaves master mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:56.468 * +slave-reconf-sent slave 127.0.0.1:8001 127.0.0.1 8001 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:57.383 * +slave-reconf-inprog slave 127.0.0.1:8001 127.0.0.1 8001 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:57.383 * +slave-reconf-done slave 127.0.0.1:8001 127.0.0.1 8001 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:57.446 * +slave-reconf-sent slave 127.0.0.1:8003 127.0.0.1 8003 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:58.392 * +slave-reconf-inprog slave 127.0.0.1:8003 127.0.0.1 8003 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:59.480 * +slave-reconf-done slave 127.0.0.1:8003 127.0.0.1 8003 @ mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:59.579 # +failover-end master mymaster 127.0.0.1 8000
58031:X 30 Dec 16:43:59.579 # +switch-master mymaster 127.0.0.1 8000 127.0.0.1 8002
58031:X 30 Dec 16:43:59.579 * +slave slave 127.0.0.1:8001 127.0.0.1 8001 @ mymaster 127.0.0.1 8002
58031:X 30 Dec 16:43:59.579 * +slave slave 127.0.0.1:8003 127.0.0.1 8003 @ mymaster 127.0.0.1 8002
58031:X 30 Dec 16:43:59.579 * +slave slave 127.0.0.1:8000 127.0.0.1 8000 @ mymaster 127.0.0.1 8002
58031:X 30 Dec 16:44:29.643 # +sdown slave 127.0.0.1:8000 127.0.0.1 8000 @ mymaster 127.0.0.1 8002
根据日志 可以看到主切换到了8002
新开窗口,登录8002,查看role
[root@web01 8000]# redis-cli -p 8002
127.0.0.1:8002> role
1) "master"
2) (integer) 5190
3) 1) 1) "127.0.0.1"
2) "8001"
3) "5190"
2) 1) "127.0.0.1"
2) "8003"
3) "5190"
重新启动8000
[root@web01 8000]# ./redis-server ./redis.conf
查看之前日志窗口,会显示日志信息:
58031:X 30 Dec 16:45:59.979 # -sdown slave 127.0.0.1:8000 127.0.0.1 8000 @ mymaster 127.0.0.1 8002
58031:X 30 Dec 16:46:09.974 * +convert-to-slave slave 127.0.0.1:8000 127.0.0.1 8000 @ mymaster 127.0.0.1 8002
登录查看8002
[root@web01 8000]# redis-cli -p 8002
127.0.0.1:8002> role
1) "master"
2) (integer) 12682
3) 1) 1) "127.0.0.1"
2) "8001"
3) "12682"
2) 1) "127.0.0.1"
2) "8003"
3) "12682"
3) 1) "127.0.0.1"
2) "8000"
3) "12682"
- PING :返回 PONG 。
- SENTINEL masters :列出所有被监视的主服务器
- SENTINEL slaves
- SENTINEL get-master-addr-by-name : 返回给定名字的主服务器的 IP 地址和端口号。
- SENTINEL reset : 重置所有名字和给定模式 pattern 相匹配的主服务器。
- SENTINEL failover : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移。
[root@web01 8000]# redis-cli -p 26379
127.0.0.1:26379>
127.0.0.1:26379> sentinel masters
1) 1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "8002"
7) "runid"
8) "8b76427cae519ae65a1e2474ce6944e064137db4"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "414"
19) "last-ping-reply"
20) "414"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "8712"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "321004"
29) "config-epoch"
30) "1"
31) "num-slaves"
32) "3"
33) "num-other-sentinels"
34) "0"
35) "quorum"
36) "1"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
Redis Cluster¶
- Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。
- Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。
- Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
- 将数据自动切分(split)到多个节点的能力。
- 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
- Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
- 节点 A 负责处理 0 号至 5500 号哈希槽。
- 节点 B 负责处理 5501 号至 11000 号哈希槽。
- 节点 C 负责处理 11001 号至 16384 号哈希槽。
- 为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。
- 在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000 号的哈希槽。
- 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。
- 不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作。
Redis Cluster

Redis Cluster-20161230
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的master节点检测失效时才生效.
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- 把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key
需要安装ruby支持
yum install ruby rubygems –y
gem install redis
[root@web01 8000]# yum install -y ruby rubygems
[root@web01 8000]# gem install redis
Fetching: redis-3.3.2.gem (100%)
Successfully installed redis-3.3.2
Parsing documentation for redis-3.3.2
Installing ri documentation for redis-3.3.2
Done installing documentation for redis after 1 seconds
1 gem installed
配置文件redis.conf需要添加如下配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
并删除 slaveof
添加多个实例,并配置
[root@web01 server]# cp -r 8003 8004
[root@web01 server]# cp -r 8003 8005
[root@web01 server]# cd 8000
[root@web01 8000]# ls
dump.rdb redis.conf redis-server
[root@web01 8000]# rm dump.rdb -f
并按上述内容添加,其他实例同样操作
启动
[root@web01 server]# for n in {0..5};do ./800$n/redis-server ./800$n/redis.conf;done
[root@web01 redis]# ss -lntup|grep redis
tcp LISTEN 0 511 127.0.0.1:8000 *:* users:(("redis-server",58282,4))
tcp LISTEN 0 511 127.0.0.1:8001 *:* users:(("redis-server",58286,4))
tcp LISTEN 0 511 127.0.0.1:8002 *:* users:(("redis-server",58290,4))
tcp LISTEN 0 511 127.0.0.1:8003 *:* users:(("redis-server",58292,4))
tcp LISTEN 0 511 127.0.0.1:8004 *:* users:(("redis-server",58412,4))
tcp LISTEN 0 511 127.0.0.1:8005 *:* users:(("redis-server",58389,4))
tcp LISTEN 0 511 127.0.0.1:18000 *:* users:(("redis-server",58282,7))
tcp LISTEN 0 511 127.0.0.1:18001 *:* users:(("redis-server",58286,7))
tcp LISTEN 0 511 127.0.0.1:18002 *:* users:(("redis-server",58290,7))
tcp LISTEN 0 511 127.0.0.1:18003 *:* users:(("redis-server",58292,7))
tcp LISTEN 0 511 127.0.0.1:18004 *:* users:(("redis-server",58412,7))
tcp LISTEN 0 511 127.0.0.1:18005 *:* users:(("redis-server",58389,7))
创建集群
- {redis_src_home}/src/redis-trib.rb create –replicas 1 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005
- 给定 redis-trib.rb 程序的命令是 create , 这表示我们希望创建一个新的集群。
- 选项 –replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
- 之后跟着的其他参数则是实例的地址列表, 我们希望程序使用这些地址所指示的实例来创建新集群。
[root@web01 server]# cd redis
[root@web01 redis]# src/redis-trib.rb create --replicas 1 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:8000
127.0.0.1:8001
127.0.0.1:8002
Adding replica 127.0.0.1:8003 to 127.0.0.1:8000
Adding replica 127.0.0.1:8004 to 127.0.0.1:8001
Adding replica 127.0.0.1:8005 to 127.0.0.1:8002
M: efc84ca03f218f1e6e5b192d80d98c6ac1249b82 127.0.0.1:8000
slots:0-5460 (5461 slots) master
M: a119e29d2c0b10f8ad518b2f22a32ec6521b678c 127.0.0.1:8001
slots:5461-10922 (5462 slots) master
M: 8e15a9b6263360570c14cec4eac371274a797d8c 127.0.0.1:8002
slots:10923-16383 (5461 slots) master
S: f27567b23b4a2b359384fcfdac23d4506ce1e184 127.0.0.1:8003
replicates efc84ca03f218f1e6e5b192d80d98c6ac1249b82
S: ca0d6d326ee412c42cdf3a97837b949982f8d40c 127.0.0.1:8004
replicates a119e29d2c0b10f8ad518b2f22a32ec6521b678c
S: 84546ff6bf2e05b78a05db594143e23f5fbaa1e0 127.0.0.1:8005
replicates 8e15a9b6263360570c14cec4eac371274a797d8c
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join..
>>> Performing Cluster Check (using node 127.0.0.1:8000)
M: efc84ca03f218f1e6e5b192d80d98c6ac1249b82 127.0.0.1:8000
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 8e15a9b6263360570c14cec4eac371274a797d8c 127.0.0.1:8002
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: ca0d6d326ee412c42cdf3a97837b949982f8d40c 127.0.0.1:8004
slots: (0 slots) slave
replicates a119e29d2c0b10f8ad518b2f22a32ec6521b678c
M: a119e29d2c0b10f8ad518b2f22a32ec6521b678c 127.0.0.1:8001
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: f27567b23b4a2b359384fcfdac23d4506ce1e184 127.0.0.1:8003
slots: (0 slots) slave
replicates efc84ca03f218f1e6e5b192d80d98c6ac1249b82
S: 84546ff6bf2e05b78a05db594143e23f5fbaa1e0 127.0.0.1:8005
slots: (0 slots) slave
replicates 8e15a9b6263360570c14cec4eac371274a797d8c
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[root@web01 redis]# redis-cli -p 8000 cluster nodes
8e15a9b6263360570c14cec4eac371274a797d8c 127.0.0.1:8002 master - 0 1483089436276 3 connected 10923-16383
efc84ca03f218f1e6e5b192d80d98c6ac1249b82 127.0.0.1:8000 myself,master - 0 0 1 connected 0-5460
ca0d6d326ee412c42cdf3a97837b949982f8d40c 127.0.0.1:8004 slave a119e29d2c0b10f8ad518b2f22a32ec6521b678c 0 1483089435268 5 connected
a119e29d2c0b10f8ad518b2f22a32ec6521b678c 127.0.0.1:8001 master - 0 1483089435772 2 connected 5461-10922
f27567b23b4a2b359384fcfdac23d4506ce1e184 127.0.0.1:8003 slave efc84ca03f218f1e6e5b192d80d98c6ac1249b82 0 1483089434263 4 connected
84546ff6bf2e05b78a05db594143e23f5fbaa1e0 127.0.0.1:8005 slave 8e15a9b6263360570c14cec4eac371274a797d8c 0 1483089435268 6 connected
集群客户端操作
redis-cli -c -p 8000
set foo bar
get foo
重新分片 ## 操作危险,需要谨慎
./redis-trib.rb reshard 127.0.0.1:7000
集群状态
redis-cli -p 8000 cluster nodes | grep master
故障转移
redis-cli -p 8002 debug segfault
查看状态
redis-cli -p 8000 cluster nodes | grep master
增加新的节点
./redis-trib.rb add-node 127.0.0.1:8006 127.0.0.1:8000
变成某实例的从
redis 127.0.0.1:8006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
删除一个节点
redis-trib del-node ip:port '<node-id>'
删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点
- 集群最近一次向节点发送 PING 命令之后, 过去了多长时间还没接到回复。
- 节点最近一次返回 PONG 回复的时间。
- 节点的配置纪元(configuration epoch):详细信息请参考 Redis 集群规范 。
- 本节点的网络连接情况:例如 connected 。
- 节点目前包含的槽:例如 127.0.0.1:7001 目前包含号码为 5960 至 10921 的哈希槽。
Redis API¶
tar zxvf 2.2.7.tar.gz
cd phpredis-2.2.7
/data/server/php/bin/phpize
./configure --with-php-config=/data/server/php/bin/php-config
make && make install
echo 'extension="redis.so"' >> /data/server/php/etc/php.ini
service php-fpm restart
service nginx restart
连接代码
<?php
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
echo "Connection to server sucessfully";
//查看服务是否运行
echo "Server is running: " . $redis->ping();
?>
字符串操作
<?php
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
echo "Connection to server sucessfully";
//设置 redis 字符串数据
$redis->set("tutorial-name", "Redis tutorial");
// 获取存储的数据并输出
echo "Stored string in redis:: " . $redis->get("tutorial-name");
?>
pip install redis
>>> import redis
>>> r = redis.StrictRedis(host='localhost', port=6379, db=0)
>>> r.set('foo', 'bar')
True
>>> r.get('foo')
'bar'
db
SQL¶
SQL语法¶
- 数据操作语言 (DML)
- 数据定义语言 (DDL)
DML¶
- SELECT - 从数据库表中获取数据
- UPDATE - 更新数据库表中的数据
- DELETE - 从数据库表中删除数据
- INSERT INTO - 向数据库表中插入数据
DDL¶
常用的DDL语句
- CREATE DATABASE - 创建新数据库
- ALTER DATABASE - 修改数据库
- CREATE TABLE - 创建新表
- ALTER TABLE - 变更(改变)数据库表
- DROP TABLE - 删除表
- CREATE INDEX - 创建索引(搜索键)
- DROP INDEX - 删除索引
创建数据库¶
CREATE DATABASE sql_test
创建表¶
指定主键
CREATE TABLE Persons
(
Id_P int NOT NULL PRIMARY KEY,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
show create table Persons;
mysql> show create table Persons\G
*************************** 1. row ***************************
Table: Persons
Create Table: CREATE TABLE `Persons` (
`Id_P` int(11) NOT NULL AUTO_INCREMENT,
`LastName` varchar(255) DEFAULT NULL,
`FirstName` varchar(255) DEFAULT NULL,
`Address` varchar(255) DEFAULT NULL,
`City` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id_P`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
CREATE TABLE Persons
(
Id_P int NOT NULL PRIMARY KEY AUTO_INCREMENT,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
查看表结构¶
mysql> desc Persons;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| Id_P | int(11) | NO | PRI | NULL | auto_increment |
| LastName | varchar(255) | YES | | NULL | |
| FirstName | varchar(255) | YES | | NULL | |
| Address | varchar(255) | YES | | NULL | |
| City | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)
select * from information_schema.columns where table_schema = 'sql_test' and table_name = 'Persons' ;
INSERT INTO¶
语法
INSERT INTO 表名称 VALUES (值1, 值2,....)
或者指定需要插入数据的列
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
插入数据
INSERT INTO Persons (LastName,FirstName,Address,City) VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')
INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')
查看结果
mysql> select * from Persons;
+------+----------+-----------+----------------+---------+
| Id_P | LastName | FirstName | Address | City |
+------+----------+-----------+----------------+---------+
| 1 | Gates | Bill | Xuanwumen 10 | Beijing |
| 2 | Wilson | NULL | Champs-Elysees | NULL |
+------+----------+-----------+----------------+---------+
2 rows in set (0.00 sec)
SELECT¶
SELECT 列名称 FROM 表名称
SELECT * FROM 表名称
mysql> SELECT LastName,FirstName FROM Persons;
+----------+-----------+
| LastName | FirstName |
+----------+-----------+
| Gates | Bill |
| Wilson | NULL |
+----------+-----------+
2 rows in set (0.00 sec)
java¶
Java基础¶
快速开始¶
HelloWorld¶
内容如下
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
在编辑器中编辑并执行. 实际上相当于执行了如下命令
# 使用javac命令将Java源文件编译为Class字节码文件
➜ javac HelloWorld.java
# 可以看到当前目录下多了一个 HelloWorld.class 文件
➜ ls
HelloWorld.class HelloWorld.java
# 使用java命令装载和运行Class字节码, 会在终端输出"Hello World!"
➜ java HelloWorld
Hello World!
基本语法¶
Java中的主要代码都会位于一个类中, 通过class
关键字定义,
上面栗子的主要代码都在HelloWorld
类中
public class HelloWorld {
...
}
- Java对大小写敏感
- 程序的文件名必须和类名的完全相同,
Java代码的文件都以类名加
.java
后缀进行命名 public static void main(String[] args)
是一个方法, 是Java程序的入口System.out.println()
是一个方法, 会在输出内容后增加一个换行符, 而System.out.print()
不会增加换行符
标识符¶
类名, 变量名, 方法名, 方法参数名等都被成为标识符.
比如HelloWorld
这个类名在代码中就是一种标识符.
标识符由我们自己命名
Java标识符, 需要注意下面几点
- 所有的标识符都应该以字母(
A-Za-z
), 美元符($
), 或者下划线(_
)开始 - 标识符由字母(
A-Za-z
),美元符($
)、下划线(_
)和数字
组成 - Java关键字不能用作标识符
例如:
name
、$user
、_title
和__1_content
都是合法的标识符,
而12haha
和-name
都是非法标识符。
为了程序的可读性, 一般情况下
- 类名以大写字母开头, 比如
HelloWorld
以大写字母H
开头 - 方法名一般以小写字母开头, 比如
main
方法以小写字母m
开头 - 如果名称中包含几个单词, 从第二个单词开始每个单词首字母大写,
这种命名方式我们称之为
驼峰命名法
。
修饰符¶
像其他语言一样, Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符
- 访问控制修饰符 :
default
,public
,protected
,private
- 非访问控制修饰符 :
final
,abstract
,strictfp
Java变量¶
- 局部变量
- 类变量(静态变量)
- 成员变量(非静态变量)
代码注释¶
Java注释有下面三种
- 单行注释:在注释内容前加两个斜线
//
- 多行注释:在要注释的内容前面添加
/*
, 在注释的内容后添加*/
- 文档注释:在要注释的内容前面添加
/**
, 在注释的内容后添加*/
, 这是一种特殊的多行注释, 注释中的内容可以用以生成程序的文档, 具体用法我们以后讲解。
面向对象¶
Java是一门面向对象语言, 一个Java程序就是一系列对象(Object)的集合, 对象通过方法调用来彼此协作完成特定的功能。
- 类: 类是一个模板, 描述一类对象的行为和状态.
- 对象: 对象是类的一个实例
类¶
- 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中, 方法结束后, 变量就会自动销毁。
- 成员变量:成员变量是定义在类中, 方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
- 类变量:类变量也声明在类中, 方法体之外,
但必须声明为
static
类型。
示例
// public是一个修饰符, 表示外部可以访问这个类,
public class Car {
int color; // 成员变量
int speed; // 成员变量
// 成员方法
void startup() {
System.out.println("启动!");
}
// 成员方法
void run(int speed) {
System.out.println("我的速度是" + speed);
}
}
public class Post {
String title; // 成员变量
String content; // 成员变量
// 成员方法
void print() {
System.out.println(title);
System.out.println(content);
}
}
构造方法¶
每个类都有构造方法。如果没有显式地为类定义构造方法, Java编译器将会为该类提供一个默认构造方法。 在创建一个对象的时候, 至少要调用一个构造方法。构造方法的名称必须与类同名, 一个类可以有多个构造方法。 下面是一个构造方法示例:
public class Puppy{
public Puppy(){
}
public Puppy(String name){
// 这个构造器仅有一个参数:name
}
}
创建对象¶
对象是根据类创建的。在Java中,
使用关键字new
来创建一个新的对象。创建对象需要以下三步:
- 声明:声明一个对象, 包括对象名称和对象类型。
- 实例化:使用关键字new来创建一个对象。
- 初始化:使用new创建对象时, 会调用构造方法初始化对象。
public class Puppy{
public Puppy(String name){
//这个构造器仅有一个参数:name
System.out.println("小狗的名字是 : " + name );
}
public static void main(String []args){
// 下面的语句将创建一个Puppy对象
Puppy myPuppy = new Puppy( "tommy" );
}
}
访问实例变量和方法¶
通过已创建的对象来访问成员变量和成员方法
/* 实例化对象 */
ObjectReference = new Constructor();
/* 访问类中的变量 */
ObjectReference.variableName;
/* 访问类中的方法 */
ObjectReference.MethodName();
实例
public class Puppy{
int puppyAge;
public Puppy(String name){
// 这个构造器仅有一个参数:name
System.out.println("小狗的名字是 : " + name );
}
public void setAge( int age ){
puppyAge = age;
}
public int getAge( ){
System.out.println("小狗的年龄为 : " + puppyAge );
return puppyAge;
}
public static void main(String []args){
/* 创建对象 */
Puppy myPuppy = new Puppy( "tommy" );
/* 通过方法来设定age */
myPuppy.setAge( 2 );
/* 调用另一个方法获取age */
myPuppy.getAge( );
/*你也可以像下面这样访问成员变量 */
System.out.println("变量值 : " + myPuppy.puppyAge );
}
}
源文件声明规则¶
当在一个源文件中定义多个类,
并且还有import
语句和package
语句时, 要注意下列规则
- 一个源文件中只能有一个
public
类 - 一个源文件可以有多个非
public
类 - 源文件的名称应该和
public
类的类名保持一致 - 如果一个类定义在某个包中, 那么
package
语句应该放在源文件首行 - 如果源文件包含
import
语句, 那么应该放在package
语句和类定义之间. 如果没有package
语句, 那么import
语句应该在源文件中最前面 import
语句和package
语句对源文件中定义的所有类都有效. 在同一源文件中, 不能给不通的类不通的包声明.
Java还有一些特殊的类, 如: 内部类, 匿名类
Java包¶
包主要用来对类和接口进行分类.
import语句¶
在Java中, 如果给出一个完整的限定名, 包括包名, 类名, 那么Java编译器就可以很容易地定位到源代码或者类. import语句就是用来提供一个合理的路径, 使得编辑器可以找到某个类.
例如,
下面的命令行将会命令编辑器载入java_installation/java/io
路径下的所有类
import java.io.*
实例¶
创建两个类Employee
, EmployeeTest
Employee
类由四个成员变量: name
, age
, designation
,
salary
. 该类显式声明了一个构造方法, 该方法只有一个参数
import java.io.*;
public class Employee {
String name;
int age;
String designation;
double salary;
// Employee 类的构造器
public Employee(String name){
this.name = name;
}
// 设置age的值
public void empAge(int empAge){
age = empAge;
}
// 设置designation的值
public void empDesignation(String empDesig){
designation = empDesig;
}
// 设置salary的值
public void empSalary(double empSalary){
salary = empSalary;
}
// 打印信息
public void printEmployee(){
System.out.println("名字:" + name);
System.out.println("年龄:" + age);
System.out.println("职位" + designation);
System.out.println("薪水" + salary);
}
}
EmployeeTest.java
程序都是从main
方法开始执行. 所以必须包含main
方法,
并且创建一个实例对象.
EmployeeTest
类, 实例化两个Employee
类的实例,
并调用方法设置变量的值.
import java.io.*;
public class EmployeeTest {
public static void main(String args[]){
// 使用构造器创建两个对象
Employee empOne = new Employee("RUN00B1");
Employee empTwo = new Employee("RUN00B2");
// 调用这两个对象的成员方法
empOne.empAge(26);
empOne.empDesignation("高级程序员");
empOne.empSalary(1000);
empOne.printEmployee();
empTwo.empAge(21);
empTwo.empDesignation("菜鸟");
empTwo.empSalary(500);
empTwo.printEmployee();
}
}
run EmployeeTest.java
结果
名字:RUN00B1
年龄:26
职位高级程序员
薪水1000.0
名字:RUN00B2
年龄:21
职位菜鸟
薪水500.0
基本数据类型¶
Java的两大数据类型¶
- 内置数据类型
- 引用数据类型
内置数据类型¶
Java提供了八种基本类型. 六种数字类型(四个整数型, 两个浮点型), 一种字符类型, 一种布尔型.
- byte 数据类型是8位、有符号的, 以二进制补码表示的整数;
- 最小值是
-128(-2^7)
; - 最大值是
127(2^7-1)
; - 默认值是 0;
- byte 类型用在大型数组中节约空间, 主要代替整数, 因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:byte a = 100, byte b = -50。
- short 数据类型是 16 位、有符号的以二进制补码表示的整数
- 最小值是
-32768(-2^15)
; - 最大值是
32767(2^15 - 1)
; - Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是 0;
- 例子:
short s = 1000
,short r = -20000
。
- int 数据类型是32位、有符号的以二进制补码表示的整数;
- 最小值是
-2,147,483,648(-2^31)
; - 最大值是
2,147,483,647(2^31 - 1)
; - 一般地整型变量默认为
int
类型; - 默认值是 0 ;
- 例子:
int a = 100000, int b = -200000
- long 数据类型是 64 位、有符号的以二进制补码表示的整数;
- 最小值是
-9,223,372,036,854,775,808(-2^63)
; - 最大值是
9,223,372,036,854,775,807(2^63 -1)
; - 这种类型主要使用在需要比较大整数的系统上;
- 默认值是
0L
; - 例子:
long a = 100000L, Long b = -200000L
。 - “L”理论上不分大小写, 但是若写成“l”容易与数字“1”混淆, 不容易分辩。所以最好大写。
- float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
- float 在储存大型浮点数组的时候可节省内存空间;
- 默认值是
0.0f
; - 浮点数不能用来表示精确的值, 如货币;
- 例子:
float f1 = 234.5f
。
- double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
- 浮点数的默认类型为double类型;
- double类型同样不能表示精确的值, 如货币;
- 默认值是
0.0d
; - 例子:
double d1 = 123.4
。
boolean数据类型表示一位的信息; 只有两个取值:true 和 false;
这种类型只作为一种标志来记录 true/false
情况; 默认值是 false;
例子:boolean one = true
。
char类型是一个单一的 16 位 Unicode 字符; 最小值是
\u0000
(即为0); 最大值是 \uffff
(即为65,535); char
数据类型可以储存任何字符; 例子:char letter = 'A';
对于数值类型的基本类型的取值范围, 我们无需强制去记忆, 因为它们的值都已经以变量的形式定义在对应的包装类中了.
运行下面的代码即可
public class PrimitiveTypeTest {
public static void main(String[] args) {
// byte
System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE);
System.out.println("包装类:java.lang.Byte");
System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE);
System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE);
System.out.println();
// short
System.out.println("基本类型:short 二进制位数:" + Short.SIZE);
System.out.println("包装类:java.lang.Short");
System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE);
System.out.println();
// int
System.out.println("基本类型:int 二进制位数:" + Integer.SIZE);
System.out.println("包装类:java.lang.Integer");
System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);
System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);
System.out.println();
// long
System.out.println("基本类型:long 二进制位数:" + Long.SIZE);
System.out.println("包装类:java.lang.Long");
System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE);
System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE);
System.out.println();
// float
System.out.println("基本类型:float 二进制位数:" + Float.SIZE);
System.out.println("包装类:java.lang.Float");
System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE);
System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE);
System.out.println();
// double
System.out.println("基本类型:double 二进制位数:" + Double.SIZE);
System.out.println("包装类:java.lang.Double");
System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE);
System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE);
System.out.println();
// char
System.out.println("基本类型:char 二进制位数:" + Character.SIZE);
System.out.println("包装类:java.lang.Character");
// 以数值形式而不是字符形式将Character.MIN_VALUE输出到控制台
System.out.println("最小值:Character.MIN_VALUE="
+ (int) Character.MIN_VALUE);
// 以数值形式而不是字符形式将Character.MAX_VALUE输出到控制台
System.out.println("最大值:Character.MAX_VALUE="
+ (int) Character.MAX_VALUE);
}
}
Float和Double的最小值和最大值都是以科学记数法的形式输出的,
结尾的“E+数字”表示E之前的数字要乘以10的多少次方。比如3.14E3
就是3.14 × 10^3 =3140
,
3.14E-3
就是 3.14 x 10^-3 =0.00314
。
实际上, JAVA中还存在另外一种基本类型void, 它也有对应的包装类
java.lang.Void
, 不过我们无法直接对它们进行操作。
引用类型¶
- 在Java中,
引用类型的变量非常类似于
C/C++
的指针。引用类型指向一个对象, 指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型, 比如Employee
、Puppy
等。变量一旦声明后, 类型就不能被改变了。 - 对象、数组都是引用数据类型。
- 所有引用类型的默认值都是null。
- 一个引用变量可以用来引用与任何与之兼容的类型。
- 例子:
Site site = new Site("Runoob")
。
Java常量¶
常量在程序运行时, 不会被修改的量。
在 Java 中使用 final 关键字来修饰常量, 声明方式和变量类似
final double PI = 3.1415927;
虽然常量名也可以用小写, 但为了便于识别, 通常使用大写字母表示常量。
字面量可以赋给任何内置类型的变量。例如:
byte a = 68;
char a = 'A'
byte、int、long、和short都可以用十进制、16进制以及8进制的方式来表示。
当使用常量的时候, 前缀0表示8进制, 而前缀0x
代表16进制。例如
int decimal = 100;
int octal = 0144;
int hexa = 0x64;
和其他语言一样, Java的字符串常量也是包含在两个引号之间的字符序列。下面是字符串型字面量的例子
"Hello World"
"two\nlines"
"\"This is in quotes\""
字符串常量和字符常量都可以包含任何Unicode字符。例如:
char a = '\u0001';
String a = "\u0001";
Java支持一些特殊的转义字符序列
符号 | 字符含义 |
---|---|
:raw-latex:`\n` | 换行 (0x0a) |
:raw-latex:`\r` | 回车 (0x0d) |
:raw-latex:`\f` | 换页符(0x0c) |
:raw-latex:`\b` | 退格 (0x08) |
\0 | 空字符 (0x20) |
:raw-latex:`\s` | 字符串 |
:raw-latex:`\t` | 制表符 |
“ | 双引号 |
‘ | 单引号 |
\ | 反斜杠 |
:raw-latex:`\ddd` | 八进制字符 (ddd) |
:raw-latex:`\uxxxx` | 16进制Unicode字符 (xxxx) |
自动转换类型¶
整型, 实型(常量), 字符型数据可以混合运算. 运算中, 不同类型的数据线转换为同一类型, 然后进行运算
转换从低级到高级
低 ------------------------------------> 高
byte,short,char—> int —> long—> float —> double
数据类型转换必须满足如下规则
- 不能对boolean类型进行类型转换。
- 不能把对象类型转换成不相关类的对象。
- 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
- 转换过程中可能导致溢出或损失精度
java int i =128; byte b = (byte)i; // 因为byte类型时8位, 最大值为127, 所以当强制转换为int类型值128时候就会导致溢出。
- 浮点数到整数的转换是通过舍弃小数得到, 而不是四舍五入
java (int)23.7 == 23; (int)-45.89f == -45
必须满足转换前的数据类型的位数要低于转换后的数据类型, 例如: short数据类型的位数为16位, 就可以自动转换位数为32的int类型, 同样float数据类型的位数为32, 可以自动转换为64位的double类型。
public class ZiDongLeiZhuan{
public static void main(String[] args){
char c1='a';//定义一个char类型
int i1 = c1;//char自动类型转换为int
System.out.println("char自动类型转换为int后的值等于"+i1);
char c2 = 'A';//定义一个char类型
int i2 = c2+1;//char 类型和 int 类型计算
System.out.println("char类型和int计算后的值等于"+i2);
}
}
c1的值为字符’a’,查ascii码表可知对应的int类型值为97, ’A’对应值为65, 所以i2=65+1=66。
- 条件是转换的数据类型必须是兼容的。
- 格式:(type)value type是要强制类型转换后的数据类型
public class QiangZhiZhuanHuan{
public static void main(String[] args){
int i1 = 123;
byte b = (byte)i1;//强制类型转换为byte
System.out.println("int强制类型转换为byte后的值等于"+b);
}
}
- 整数的默认类型是 int。
- 浮点型不存在这种情况, 因为在定义 float 类型时必须在数字后面跟上 F 或者 f。
这一节讲解了 Java 的基本数据类型。下一节将探讨不同的变量类型以及它们的用法。
变量类型¶
Java中, 所有变量使用前必须声明, 声明格式如下
type identifier [ = value ][, identifier [ = value ] ...];
type为Java数据类型. identifier是变量名. 可以使用逗号隔开声明多个同类型变量.
示例
int a, b, c; // 声明三个int型整数:a、 b、c
int d = 3, e = 4, f = 5; // 声明三个整数并赋予初值
byte z = 22; // 声明并初始化 z
String s = "runoob"; // 声明并初始化字符串 s
double pi = 3.14159; // 声明了双精度浮点型变量 pi
char x = 'x'; // 声明变量 x 的值是字符 'x'。
Java支持的变量类型有
- 类变量: 独立于方法之外的变量, 用
static
修饰 - 实例变量: 独立于方法之外的变量, 不过没有
static
修饰 - 局部变量: 类的方法中的变量
实例
public class Variable{
static int allClicks=0; // 类变量
String str="hello world"; // 实例变量
public void method(){
int i =0; // 局部变量
}
}
Java局部变量¶
- 局部变量声明在方法、构造方法或者语句块中;
- 局部变量在方法、构造方法、或者语句块被执行的时候创建, 当它们执行完成后, 变量将会被销毁;
- 访问修饰符不能用于局部变量;
- 局部变量只在声明它的方法、构造方法或者语句块中可见;
- 局部变量是在栈上分配的。
- 局部变量没有默认值, 所以局部变量被声明后, 必须经过初始化, 才可以使用。
public class Test_par {
public void pupAge(){
// 如果age不进行初始化, 下一句, 就会报错
int age = 0;
age = age + 7;
System.out.println("小狗的年龄: " + age);
}
public static void main(String args[]){
Test_par test = new Test_par();
test.pupAge();
}
}
实例变量¶
- 实例变量声明在一个类中, 但在方法、构造方法和语句块之外;
- 当一个对象被实例化之后, 每个实例变量的值就跟着确定;
- 实例变量在对象创建的时候创建, 在对象被销毁的时候销毁;
- 实例变量的值应该至少被一个方法、构造方法或者语句块引用, 使得外部能够通过这些方式获取实例变量信息;
- 实例变量可以声明在使用前或者使用后;
- 访问修饰符可以修饰实例变量;
- 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
- 实例变量具有默认值。数值型变量的默认值是0, 布尔型变量的默认值是false, 引用类型变量的默认值是null。变量的值可以在声明时指定, 也可以在构造方法中指定;
- 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,
就应该使用完全限定名:
ObejectReference.VariableName
。
实例
public class Employee_par {
public String name;
private double salary;
public Employee_par(String empName){
name = empName;
}
public void setSalary(double empSal){
salary = empSal;
}
public void printEmp(){
System.out.println("名字: " + name);
System.out.println("薪水: " + salary);
}
public static void main(String args[]){
Employee_par empOne = new Employee_par("haha");
empOne.setSalary(1000);
empOne.printEmp();
}
}
运行结果
名字: haha
薪水: 1000.0
类变量(静态变量)¶
- 类变量也称为静态变量, 在类中以
static
关键字声明, 但必须在方法构造方法和语句块之外。 - 无论一个类创建了多少个对象, 类只拥有类变量的一份拷贝。
- 静态变量除了被声明为常量外很少使用。常量是指声明为
public/private
,final
和static
类型的变量。常量初始化后不可改变。 - 静态变量储存在静态存储区。经常被声明为常量,
很少单独使用
static
声明变量。 - 静态变量在程序开始时创建, 在程序结束时销毁。
- 与实例变量具有相似的可见性。但为了对类的使用者可见,
大多数静态变量声明为
public
类型。 - 默认值和实例变量相似。数值型变量默认值是0, 布尔型默认值是
false
, 引用类型默认值是null
。变量的值可以在声明的时候指定, 也可以在构造方法中指定。此外, 静态变量还可以在静态语句块中初始化。 - 静态变量可以通过:
ClassName.VariableName
的方式访问。 - 类变量被声明为
public static final
类型时, 类变量名称一般建议使用大写字母。如果静态变量不是public
和final
类型, 其命名方式与实例变量以及局部变量的命名方式一致。
public class Employee {
//salary是静态的私有变量
private static double salary;
// DEPARTMENT是一个常量
public static final String DEPARTMENT = "开发人员";
public static void main(String args[]){
salary = 10000;
System.out.println(DEPARTMENT+"平均工资:"+salary);
}
}
修饰符¶
Java提供了很多修饰符, 主要分为下面两类
- 访问修饰符
- 非访问修饰符
修饰符用来定义类, 方法或者变量, 通常放在语句的最前端.
public class className {
// ...
}
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
// 方法体
}
访问控制修饰符¶
Java中, 可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Javav支持 4 种不同的访问权限。
- default (即缺省, 什么也不写): 在同一包内可见, 不使用任何修饰符。使用对象:类、接口、变量、方法。
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- public : 对所有类可见。使用对象:类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
修饰符 | 当前类 | 同一包内 | 子孙类 | 其他包 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
default | Y | Y | N | N |
private | Y | N | N | N |
不使用任何关键字
使用默认访问修饰符声明的变量和方法, 对同一个包内的类是可见的。接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。
实例, 变量和方法的声明可以不使用任何修饰符。
String version = "1.5.1";
boolean processOrder() {
return true;
}
私有访问修饰符是最严格的访问级别, 所以被声明为 private 的方法、变量和构造方法只能被所属类访问, 并且类和接口不能声明为 private。
声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。
Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
// Logger 类
public class Logger {
// format 变量为私有变量, 其他类不能直接得到和设置该变量的值
private String format;
// 为了使其他类能够操作该变量, 定义了两个 public 方法:getFormat() (返回 format的值)
public String getFormat() {
return this.format;
}
// setFormat(String)(设置 format 的值)
public void setFormat(String format) {
this.format = format;
}
}
被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的 public 类分布在不同的包中, 则需要导入相应 public 类所在的包。由于类的继承性, 类所有的公有方法和变量都能被其子类继承。
// Java 程序的 main() 方法必须设置成公有的, 否则, Java 解释器将不能运行该类。
public static void main(String[] arguments) {
// ...
}
被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问, 也能够被不同包中的子类访问。
protected 访问修饰符不能修饰类和接口, 方法和成员变量能够声明为 protected, 但是接口的成员变量和成员方法不能声明为 protected。
子类能访问 protected 修饰符声明的方法和变量, 这样就能保护不相关的类使用这些方法和变量。
下面的父类使用了 protected
访问修饰符, 子类重写了父类的
openSpeaker()
方法。
class AudioPlayer {
protected boolean openSpeaker(Speaker sp) {
// 实现细节
}
}
class StreamingAudioPlayer extends AudioPlayer {
protected boolean openSpeaker(Speaker sp) {
// 实现细节
}
}
如果把 openSpeaker()
方法声明为 private
, 那么除了
AudioPlayer
之外的类将不能访问该方法。
如果把 openSpeaker()
声明为 public
,
那么所有的类都能够访问该方法。
如果我们只想让该方法对其所在类的子类可见, 则将该方法声明为
protected
。
请注意以下方法继承的规则
- 父类中声明为
public
的方法在子类中也必须为public。
- 父类中声明为
protected
的方法在子类中要么声明为protected
, 要么声明为public
, 不能声明为private
- 父类中声明为
private
的方法, 不能够被继承。
非访问修饰符¶
为了实现一些其他的功能, Java
也提供了许多非访问修饰符。
static
修饰符, 用来修饰类方法和类变量。
final
修饰符, 用来修饰类、方法和变量, final
修饰的类不能够被继承, 修饰的方法不能被继承类重新定义, 修饰的变量为常量,
是不可修改的。
abstract
修饰符, 用来创建抽象类和抽象方法。
synchronized
和 volatile
修饰符, 主要用于线程的编程。
- 静态变量:
static
关键字用来声明独立于对象的静态变量, 无论一个类实例化多少对象, 它的静态变量只有一份拷贝. 静态变量也称为类变量. 局部变量不能被声明为static
变量 - 静态方法:
static
关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据, 然后计算这些数据。
对类变量和方法的访问可以直接使用 classname.variablename
和
classname.methodname()
的方式访问。 如下例所示,
static修饰符用来创建类方法和类变量。
public class InstanceCounter {
private static int numInstances = 0;
protected static int getCount() {
return numInstances;
}
private static void addInstance() {
numInstances++;
}
InstanceCounter() {
InstanceCounter.addInstance();
}
public static void main(String[] arguments) {
System.out.println("Starting with " +
InstanceCounter.getCount() + " instances");
for (int i = 0; i < 500; ++i){
new InstanceCounter();
}
System.out.println("Created " +
InstanceCounter.getCount() + " instances");
}
}
运行结果
Started with 0 instances
Created 500 instances
final
变量能被显式地初始化并且只能初始化一次。被声明为 final
的对象的引用不能指向不同的对象。但是 final
对象里的数据可以被改变。也就是说 final
对象的引用不能改变,
但是里面的值可以改变。
final
修饰符通常和 static
修饰符一起使用来创建类常量。
实例
public class Test{
final int value = 10;
// 下面是声明常量的实例
public static final int BOXWIDTH = 6;
static final String TITLE = "Manager";
public void changeValue(){
value = 12; //将输出一个错误
}
}
类中的 final
方法可以被子类继承, 但是不能被子类修改。
声明 final
方法的主要目的是防止该方法的内容被修改。
如下所示, 使用 final
修饰符声明方法。
public class Test{
public final void changeName(){
// 方法体
}
}
抽象类
抽象类不能用来实例化对象, 声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法, 那么该类一定要声明为抽象类, 否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。
实例
abstract class Caravan{
private double price;
private String model;
private String year;
public abstract void goFast(); //抽象方法
public abstract void changeColor();
}
抽象方法是一种没有任何实现的方法, 该方法的的具体实现由子类提供。
抽象方法不能被声明成 final 和 static。
任何继承抽象类的子类必须实现父类的所有抽象方法, 除非该子类也是抽象类。
如果一个类包含若干个抽象方法, 那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾, 例如:public abstract sample();
实例
public abstract class SuperClass{
abstract void m(); //抽象方法
}
class SubClass extends SuperClass{
//实现抽象方法
void m(){
.........
}
}
synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
public synchronized void showDetails(){
.......
}
序列化的对象包含被 transient 修饰的实例变量时, java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中, 用来预处理类和变量的数据类型。
public transient int limit = 55; // 不会持久化
public int b; // 持久化
volatile
修饰的成员变量在每次被线程访问时,
都强制从共享内存中重新读取该成员变量的值。而且, 当成员变量发生变化时,
会强制线程将变化值回写到共享内存。这样在任何时刻,
两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile
对象引用可能是 null
public class MyRunnable implements Runnable
{
private volatile boolean active;
public void run()
{
active = true;
while (active) // 第一行
{
// 代码
}
}
public void stop()
{
active = false; // 第二行
}
}
通常情况下, 在一个线程调用 run()
方法(在 Runnable 开启的线程),
在另一个线程调用 stop()
方法。 如果 第一行 中缓冲区的 active
值被使用, 那么在 第二行 的 active
值为 false
时循环不会停止。
但是以上代码中我们使用了 volatile
修饰 active
,
所以该循环会停止。
运算符¶
- 算术运算符
- 关系运算符
- 位运算符
- 逻辑运算符
- 赋值运算符
- 其他运算符
算术运算符¶
变量A的值为10, 变量B的值为20
操作符 | 描述 | 例子 |
---|---|---|
+ |
加法 相加运算符两侧的值 | A + B 等于 30 |
- |
减法 左操作数减去右操作数 | A – B 等于 -10 |
* |
乘法 相乘操作符两侧的值 | A * B等于200 |
/ |
除法 左操作数除以右操作数 | B / A等于2 |
% |
取模 左操作数除以右操作数的余数 | B%A等于0 |
++ |
自增 操作数的值增加1 | B++ 或 ++B 等于 21 |
-- |
自减 操作数的值减少1 | B-- 或 --B 等于 19 |
- 自增(++)自减(–)运算符是一种特殊的算术运算符, 在算术运算符中需要两个操作数来进行运算, 而自增自减运算符是一个操作数。
- 前缀自增自减法(++a,–a): 先进行自增或者自减运算, 再进行表达式运算。
- 后缀自增自减法(a++,a–): 先进行表达式运算, 再进行自增或者自减运算 实例:
关系运算符¶
变量A的值为10, 变量B的值为20
位运算符¶
循环结构¶
Java主要有三种循环结构
while
循环do…while
循环for
循环
while循环¶
// 只要布尔表达式为 true, 循环体会一直执行下去。
while( 布尔表达式 ) {
//循环内容
}
实例
public class Text_while_1 {
public static void main(String args[]){
int x = 10;
while (x < 20){
System.out.println("value of x : " + x);
x++;
}
}
}
do…while 循环¶
对于 while
语句而言, 如果不满足条件,
则不能进入循环。但有时候我们需要即使不满足条件, 也至少执行一次。
do…while
循环和 while
循环相似, 不同的是, do…while
循环至少会执行一次。
do {
//代码语句
}while(布尔表达式);
注意:布尔表达式在循环体的后面,
所以语句块在检测布尔表达式之前已经执行了。 如果布尔表达式的值为
true
, 则语句块一直执行, 直到布尔表达式的值为 false
.
实例
public class Test {
public static void main(String args[]){
int x = 10;
do{
System.out.print("value of x : " + x );
x++;
System.out.print("\n");
}while( x < 20 );
}
}
for循环¶
虽然所有循环结构都可以用 while 或者 do...while
表示, 但 Java
提供了另一种语句, for
循环, 使一些循环结构变得更加简单。
for
循环执行的次数是在执行前就确定的。
for(初始化; 布尔表达式; 更新) {
//代码语句
}
关于 for 循环有以下几点说明
- 最先执行初始化步骤。可以声明一种类型, 但可初始化一个或多个循环控制变量, 也可以是空语句。
- 然后, 检测布尔表达式的值。如果为
true
, 循环体被执行。如果为false
, 循环终止, 开始执行循环体后面的语句。 - 执行一次循环后, 更新循环控制变量。
- 再次检测布尔表达式。循环执行上面的过程。
实例
public class Test_for_1 {
public static void main(String args[]){
for (int x = 10; x<20;x++){
System.out.println("value of x : " + x);
}
}
}
增强for循环¶
Java5
引入了一种主要用于数组的增强型 for 循环。
Java 增强 for 循环语法格式如下:
for(声明语句 : 表达式)
{
//代码句子
}
- 声明语句:声明新的局部变量, 该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块, 其值与此时数组元素的值相等。
- 表达式:表达式是要访问的数组名, 或者是返回值为数组的方法。
实例
public class Test_for_2 {
public static void main(String args[]){
int [] numbers = {10, 20, 30, 40, 50};
for (int x : numbers){
System.out.print(x);
System.out.print(",");
}
System.out.print("\n");
String [] names = {"James", "Larry", "Tom", "Lacy"};
for (String name : names){
System.out.print(name);
System.out.print(",");
}
}
}
continue¶
continue
适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。
在 for
循环中, continue
语句使程序立即跳转到更新语句。
在 while
或者 do…while
循环中,
程序立即跳转到布尔表达式的判断语句。
分支结构¶
if
语句switch
语句
可以嵌套
if…else语句¶
if 语句后面可以跟 else 语句, 当 if 语句的布尔表达式值为 false 时, else 语句块会被执行。
if(布尔表达式){
//如果布尔表达式的值为true
}else{
//如果布尔表达式的值为false
}
if…else if…else¶
使用 if, else if, else 语句的时候, 需要注意下面几点
- if 语句至多有 1 个
else
语句,else
语句在所有的else if
语句之后。 - if 语句可以有若干个
else if
语句, 它们必须在else
语句之前。 - 一旦其中一个
else if
语句检测为true
, 其他的else if
以及else
语句都将跳过执行。
if(布尔表达式 1){
//如果布尔表达式 1的值为true执行代码
}else if(布尔表达式 2){
//如果布尔表达式 2的值为true执行代码
}else if(布尔表达式 3){
//如果布尔表达式 3的值为true执行代码
}else {
//如果以上布尔表达式都不为true执行代码
}
switch 语句¶
switch 语句判断一个变量与一系列值中某个值是否相等, 每个值称为一个分支。
switch(expression){
case value :
//语句
break; //可选
case value :
//语句
break; //可选
//你可以有任意数量的case语句
default : //可选
//语句
}
switch 语句有如下规则
- switch 语句中的变量类型可以是: byte、short、int 或者 char。从 Java SE 7 开始, switch 支持字符串类型了, 同时 case 标签必须为字符串常量或字面量。
- switch 语句可以拥有多个 case 语句。每个 case 后面跟一个要比较的值和冒号。
- case 语句中的值的数据类型必须与变量的数据类型相同, 而且只能是常量或者字面常量。
- 当变量的值与 case 语句的值相等时, 那么 case 语句之后的语句开始执行, 直到 break 语句出现才会跳出 switch 语句。
- 当遇到 break 语句时, switch 语句终止。程序跳转到 switch 语句后面的语句执行。case 语句不必须要包含 break 语句。如果没有 break 语句出现, 程序会继续执行下一条 case 语句, 直到出现 break 语句。
- switch 语句可以包含一个 default 分支, 该分支必须是 switch 语句的最后一个分支。default 在没有 case 语句的值和变量值相等的时候执行。default 分支不需要 break 语句。
实例
public class Test {
public static void main(String args[]){
//char grade = args[0].charAt(0);
char grade = 'C';
switch(grade)
{
case 'A' :
System.out.println("优秀");
break;
case 'B' :
case 'C' :
System.out.println("良好");
break;
case 'D' :
System.out.println("及格");
case 'F' :
System.out.println("你需要再努力努力");
break;
default :
System.out.println("未知等级");
}
System.out.println("你的等级是 " + grade);
}
}
数组¶
声明数组¶
首先必须声明数组变量,才能在程序中使用数组。
dataType[] arrayRefVar; // 首选的方法
或
dataType arrayRefVar[]; // 效果相同,C/C++ 风格
日期时间¶
http://www.runoob.com/java/java-date-time.html
java.util
包提供了 Date 类来封装当前的日期和时间。 Date
类提供两个构造函数来实例化 Date 对象。
第一个构造函数使用当前日期和时间来初始化对象。
Date( )
第二个构造函数接收一个参数, 该参数是从1970年1月1日起的毫秒数。
Date(long millisec)
创建Date对象之后, 可以使用下面的方法
获取当前日期时间¶
import java.util.Date;
public class Date_toString {
public static void main(String args[]){
Date date = new Date();
System.out.println(date.toString());
}
}
日期比较¶
Java使用以下三种方法来比较两个日期
- 使用 getTime() 方法获取两个日期(自1970年1月1日经历的毫秒数值), 然后比较这两个值。
- 使用方法 before(), after() 和 equals()。例如, 一个月的12号比18号早, 则 new Date(99, 2, 12).before(new Date (99, 2, 18)) 返回true。
- 使用 compareTo() 方法, 它是由 Comparable 接口定义的, Date 类实现了这个接口。
DateTimeFormatter¶
使用 SimpleDateFormat 格式化日期¶
SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。
import java.util.*;
import java.text.*;
public class SimpleDateFormat_xx {
public static void main(String args[]) {
Date dNow = new Date( );
/* 这一行代码确立了转换的格式, 其中 yyyy 是完整的公元年, MM 是月份, dd 是日期, HH:mm:ss 是时、分、秒。
注意:有的格式大写, 有的格式小写, 例如 MM 是月份, mm 是分;HH 是 24 小时制, 而 hh 是 12 小时制。*/
SimpleDateFormat ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");
System.out.println("Current Date: " + ft.format(dNow));
}
}
使用printf格式化日期¶
printf 方法可以很轻松地格式化时间和日期。使用两个字母格式, 它以 %t 开头并且以下面表格中的一个字母结尾。
import java.util.Date;
public class DateDemo {
public static void main(String args[]) {
// 初始化 Date 对象
Date date = new Date();
//c的使用
System.out.printf("全部日期和时间信息:%tc%n",date);
//f的使用
System.out.printf("年-月-日格式:%tF%n",date);
//d的使用
System.out.printf("月/日/年格式:%tD%n",date);
//r的使用
System.out.printf("HH:MM:SS PM格式(12时制):%tr%n",date);
//t的使用
System.out.printf("HH:MM:SS格式(24时制):%tT%n",date);
//R的使用
System.out.printf("HH:MM格式(24时制):%tR",date);
}
}
方法¶
方法的定义¶
一般情况下, 定义一个方法包含以下语法
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
方法包含一个方法头和一个方法体。下面是一个方法的所有部分
- 修饰符:修饰符, 这是可选的, 告诉编译器如何调用该方法。定义了该方法的访问类型。
- 返回值类型 :方法可能会返回值。returnValueType 是方法返回值的数据类型。有些方法执行所需的操作, 但没有返回值。在这种情况下, returnValueType 是关键字void。
- 方法名:是方法的实际名称。方法名和参数表共同构成方法签名。
- 参数类型:参数像是一个占位符。当方法被调用时, 传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的, 方法可以不包含任何参数。
- 方法体:方法体包含具体的语句, 定义该方法的功能。
方法调用¶
Java 支持两种调用方法的方式, 根据方法是否返回值来选择。
当程序调用一个方法时, 程序的控制权交给了被调用的方法。当被调用方法的返回语句执行或者到达方法体闭括号时候交还控制权给程序。
当方法返回一个值的时候, 方法调用通常被当做一个值。例如:
int larger = max(30, 40);
如果方法返回值是void, 方法调用一定是一条语句。例如, 方法println返回void。下面的调用是个语句:
System.out.println("Hello World");
Stream,File,IO¶
File¶
掌握
- IO流操作中大部分都是对文件的操作,所以Java就提供了File类供我们来操作文件
- 构造方法
File file = new File("e:\\demo\\a.txt");
File file = new File("e:\\demo","a.txt");
File file = new File("e:\\demo");
File file2 = new File(file,"a.txt");
- File类的功能(自己补齐)
- 创建功能
- 删除功能
- 重命名功能
- 判断功能
- 获取功能
- 高级获取功能
- 过滤器功能
- 案例:
- 输出指定目录下指定后缀名的文件名称
- 先获取所有的,在遍历的时候判断,再输出
- 先判断,再获取,最后直接遍历输出即可
- 批量修改文件名称
- 输出指定目录下指定后缀名的文件名称
实例
fileStreamTest.java
import java.io.*;
public class fileStreamTest{
public static void main(String args[]){
try{
byte bWrite [] = {11,21,3,40,5};
OutputStream os = new FileOutputStream("test.txt");
for(int x=0; x < bWrite.length ; x++){
os.write( bWrite[x] ); // writes the bytes
}
os.close();
InputStream is = new FileInputStream("test.txt");
int size = is.available();
for(int i=0; i< size; i++){
System.out.print((char)is.read() + " ");
}
is.close();
}catch(IOException e){
System.out.print("Exception");
}
}
}
上面使用二进制形式写入文件, 同时输出到控制台, 如下代码解决乱码问题
fileStreamTest2.java
//文件名 :fileStreamTest2.java
import java.io.*;
public class fileStreamTest2{
public static void main(String[] args) throws IOException {
File f = new File("a.txt");
FileOutputStream fop = new FileOutputStream(f);
// 构建FileOutputStream对象,文件不存在会自动新建
OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
// 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk
writer.append("中文输入");
// 写入到缓冲区
writer.append("\r\n");
//换行
writer.append("English");
// 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入
writer.close();
//关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉
fop.close();
// 关闭输出流,释放系统资源
FileInputStream fip = new FileInputStream(f);
// 构建FileInputStream对象
InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
// 构建InputStreamReader对象,编码与写入相同
StringBuffer sb = new StringBuffer();
while (reader.ready()) {
sb.append((char) reader.read());
// 转成char加到StringBuffer对象中
}
System.out.println(sb.toString());
reader.close();
// 关闭读取流
fip.close();
// 关闭输入流,释放系统资源
}
}
Scanner¶
java.util.Scanner
是 Java5 的新特征, 我们可以通过 Scanner
类来获取用户的输入。
异常¶
异常体系¶
- 程序出现的不正常的情况.
- 异常的体系
- Throwable
- |–Error 严重问题, 我们不处理.
- |–Exception
- |–RuntimeException 运行期异常, 我们需要修正代码
- |–非RuntimeException 编译期异常, 必须处理的, 否则程序编译不通过
- Throwable
内置异常¶
异常处理¶
- JVM的默认处理
- 把异常的名称,原因,位置等信息输出在控制台, 但是呢程序不能继续执行了.
- 自己处理
- a:try…catch…finally
- 自己编写处理代码,后面的程序可以继续执行
- b:throws
- 把自己处理不了的, 在方法上声明, 告诉调用者, 这里有问题
- a:try…catch…finally
捕获异常¶
使用 try
和 catch
关键字可以捕获异常. try/catch
代码块放在异常可能发生的地方. try/catch代码块中的代码称为保护代码, 使用
try/catch 的语法如下:
try
{
// 程序代码
}catch(ExceptionName e1)
{
//catch 块
}
catch
语句包含要捕获异常类型的声明. 当保护代码块中发生一个异常时,
try 后面的 catch 块就会被检查.
如果发生的异常包含在 catch 块中, 异常会被传递到该 catch 块, 这和传递一个参数到方法是一样.
实例
下面的例子中声明有两个元素的一个数组, 当代码试图访问数组的第三个元素的时候就会抛出一个异常.
// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{
public static void main(String args[]){
try{
int a[] = new int[2];
System.out.println("Access element three :" + a[3]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Exception thrown :" + e);
}
System.out.println("Out of the block");
}
}
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获.
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}
上面的代码段包含了 3 个 catch块.
可以在 try 语句后面添加任意数量的 catch 块.
如果保护代码中发生异常, 异常被抛给第一个 catch 块.
如果抛出异常的数据类型与 ExceptionType1 匹配, 它在这里就会被捕获.
如果不匹配, 它会被传递给第二个 catch 块.
如此, 直到异常被捕获或者通过所有的 catch 块.
实例
try
{
file = new FileInputStream(fileName);
x = (byte) file.read();
}catch(IOException i)
{
i.printStackTrace();
return -1;
}catch(FileNotFoundException f) //Not valid!
{
f.printStackTrace();
return -1;
}
try...catch...finally
try...catch...
try...catch...catch...
try...catch...catch...fianlly
try...finally
throws/throw¶
throws
如果一个方法有捕获一个检查性异常, 那么该方法必须使用 throws 关键字来声明. throws 关键字放在方法签名的尾部.
- 在方法声明上,后面跟的是异常的类名,可以是多个
- throws是声明方法有异常, 是一种可能性, 这个异常并不一定会产生
throw
- 在方法体中, 后面跟异常对象名, 并且只能是一个
throw
触发异常, 无论它是新实例化的还是刚捕获到的.
下面方法的声明抛出一个 RemoteException
异常:
import java.io.*;
public class className
{
public void deposit(double amount) throws RemoteException
{
// Method implementation
throw new RemoteException();
}
//Remainder of class definition
}
一个方法可以声明抛出多个异常, 多个异常之间用逗号隔开.
例如, 下面的方法声明抛出 RemoteException
和
InsufficientFundsException
import java.io.*;
public class className
{
public void withdraw(double amount) throws RemoteException,
InsufficientFundsException
{
// Method implementation
}
//Remainder of class definition
}
finally¶
finally 关键字用来创建在 try 代码块后面执行的代码块.
无论是否发生异常, finally 代码块中的代码总会被执行, 以运行清理类型等收尾善后性质的语句. 特殊情况: 在执行到finally之前JVM退出了
finally 代码块出现在 catch 代码块最后
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}
注意下面事项
- catch 不能独立于 try 存在.
- 在 try/catch 后面添加 finally 块并非强制性要求的.
- try 代码后不能既没 catch 块也没 finally 块.
- try, catch, finally 块之间不能添加任何代码.
自定义异常¶
在 Java 中你可以自定义异常. 编写自己的异常类时需要记住下面的几点.
- 所有异常都必须是 Throwable 的子类.
- 如果希望写一个检查性异常类, 则需要继承 Exception 类.
- 如果你想写一个运行时异常类, 那么需要继承 RuntimeException 类.
只需要提供无参构造和一个带参构造即可
可以像下面这样定义自己的异常类:
class MyException extends Exception{
}
只继承Exception 类来创建的异常类是检查性异常类.
下面的 InsufficientFundsException 类是用户定义的异常类, 它继承自 Exception.
一个异常类和其它任何类一样, 包含有变量和方法.
编译器异常和运行期异常¶
- 编译期异常 必须要处理的, 否则编译不通过
- 运行期异常 可以不处理, 也可以处理
异常注意事项¶
- 异常的注意实现
- 父的方法有异常抛出,子的重写方法在抛出异常的时候必须要小于等于父的异常
- 父的方法没有异常抛出,子的重写方法不能有异常抛出
- 父的方法抛出多个异常,子的重写方法必须比父少或者小
Java基础¶
介绍¶
Java环境安装与配置¶
在Ubuntu默认的软件仓库中不包含Oracle官方提供的JDK, 我们可以添加第三方仓库来安装, 执行:
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
使用apt-get安装Oracle的JDK
# 安装需要安装的版本即可
sudo apt-get install oracle-java8-installer
安装完成会将系统默认的Java版本更新为对应的安装版本。
- 下载, 安装
- 配置环境变量
cmd
执行命令java -version
,javac -version
JDK, JRE, JVM¶
- JDK : Java Development ToolKit
- JRE:Java Runtime Environment
- JVM:Java Virtual Machine
JDK在包含JRE之外, 提供了开发Java应用的各种工具, 比如编译器和调试器。
JRE包括JVM和JAVA核心类库和支持文件, 是Java的运行平台, 所有的Java程序都要在JRE下才能运行。
JVM是JRE的一部分, Java虚拟机的主要工作是将Java字节码(通过Java程序编译得到)映射到本地的 CPU 的指令集或 OS 的系统调用。JVM回根据不同的操作系统使用不同的JVM映射规则, 从而使得Java平台与操作系统无关, 实现了跨平台的特性性。
在实际开发过程中, 我们首先编写Java代码, 然后通过JDK中的编译程序(javac)将Java文件编译成Java字节码, JRE加载和验证Java字节码, JVM解释字节码, 映射到CPU指令集或O的系统调用, 完成最终的程序功能。
开发工具¶
- IntelliJ IDEA
- Eclipse
笔记参考¶
面向对象¶
继承¶
继承的特性¶
- 子类拥有父类非private的属性, 方法。
- 子类可以拥有自己的属性和方法, 即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java的继承是单继承, 但是可以多重继承, 单继承就是一个子类只能继承一个父类, 多重继承就是, 例如A类继承B类, B类继承C类, 所以按照关系就是C类是B类的父类, B类是A类的父类, 这是java继承区别于C++继承的一个特性。
- 提高了类之间的耦合性(继承的缺点, 耦合度高就会造成代码之间的联系)
继承关键字¶
继承可以使用 extends
和 implements
这两个关键字来实现继承,
而且所有的类都是继承于 java.lang.Object, 当一个类没有继承的两个关键字,
则默认继承object(这个类在 java.lang 包中, 所以不需要 import)祖先类。
在 Java 中, 类的继承是单一继承, 也就是说, 一个子类只能拥有一个父类, 所以 extends 只能继承一个类。
public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}
使用 implements 关键字可以变相的使java具有多继承的特性, 使用范围为类继承接口的情况, 可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
- super关键字:我们可以通过super关键字来实现对父类成员的访问, 用来引用当前对象的父类。
- this关键字:指向自己的引用。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
final 关键字声明类可以把类定义为不能继承的, 即最终类;或者用于修饰方法, 该方法不能被子类重写:
声明类
final class 类名 {//类体}
声明方法
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注:实例变量也可以被定义为 final, 被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final, 但是实例变量并不是 final
构造器¶
子类不能继承父类的构造器(构造方法或者构造函数),但是父类的构造器带有参数的,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。
如果父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
class SuperClass {
private int n;
SuperClass(){
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n)");
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){
super(300);
System.out.println("SubClass");
}
public SubClass(int n){
System.out.println("SubClass(int n):"+n);
this.n = n;
}
}
public class TestSuperSub{
public static void main (String args[]){
SubClass sc = new SubClass();
SubClass sc2 = new SubClass(200);
}
}
输出结果
SuperClass(int n)
SubClass
SuperClass()
SubClass(int n):200
重写与重载¶
重写(Override)¶
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}
在上面的例子中可以看到,尽管b属于Animal类型,但是它运行的是Dog类的move方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象的方法。
TestDog.java
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
// 程序会报错, 因为b的引用类型Animal没有bark方法, 可以将b声明为Dog
b.bark();
}
}
以上实例编译运行结果如下
Error:(23, 10) java: cannot find symbol
symbol: method bark()
location: variable b of type Animal
该程序将抛出一个编译错误,因为b的引用类型Animal没有bark
方法。
方法的重写规则¶
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。
- 声明为final的方法不能被重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
Super关键字的使用¶
当需要在子类中调用父类的被重写方法时,要使用super关键字。
TestDog.java
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法
}
}
重载(Overload)¶
- 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
- 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
- 最常用的地方就是构造器的重载。
- 重载规则
- 被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
实例Overloading.java
public class Overloading {
public int test(){
System.out.println("test1");
return 1;
}
public void test(int a){
System.out.println("test2");
}
//以下两个参数类型顺序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}
public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}
public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}
重写与重载之间的区别¶
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
总结¶
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
多态¶
多态是同一个行为具有多个不同表现形式或形态的能力.
多态就是同一个接口, 使用不同的实例而执行不同操作
多态性是对象多种表现形式的体现
多态的优点¶
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
多态存在的三个必要条件¶
- 继承
- 重写
- 父类引用指向子类对象
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
Test.java
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
抽象类¶
在面向对象的概念中, 所有的对象都是通过类来描绘的, 但是反过来, 并不是所有的类都是用来描绘对象的, 如果一个类中没有包含足够的信息来描绘一个具体的东西, 这样的类就是抽象类.
抽象类除了不能实例化对象之外, 类的其它功能依然存在, 成员变量, 成员方法和构造方法的访问方式和普通类一样.
由于抽象类不能实例化对象, 所以抽象类必须被继承, 才能被使用. 也是因为这个原因, 通常在设计阶段决定要不要涉及抽象类.
父类包含了子类集合的常见方法, 但是由于父类本身是抽象的, 所以不能使用这些方法.
在Java中抽象类表示的是一种继承关系, 一个类智能继承一个抽象类, 而一个类却可以实现多个接口
此处应该有栗子¶
抽象方法¶
如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
public abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay();
//其余代码
}
声明抽象方法会造成以下两个结果
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
如果Salary类继承了Employee类,那么它必须实现computePay()方法
/* 文件名 : Salary.java */
public class Salary extends Employee
{
private double salary; // Annual salary
public double computePay()
{
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
//其余代码
}
抽象类总结规定¶
- 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用static修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
封装¶
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
实现Java封装的步骤¶
- 修改属性的可见性来限制对属性的访问(一般限制为private),例如:
public class Person {
private String name;
private int age;
}
这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
- 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。
接口¶
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface
来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口与类相似点¶
- 一个接口可以有多个方法
- 接口文件保存在
.java
结尾的文件中, 文件名使用接口名 - 接口的字节码文件保存在
.class
结尾的文件中 - 接口相应的字节码文件必须在与包名称相匹配的目录结构中
接口与类的区别¶
- 接口不能用于实例化对象
- 接口没有构造方法
- 接口中所有的方法必须是抽象方法
- 接口不能包含成员变量, 除了
static
和final
变量 - 接口不是被类继承, 而是要被类实现
- 接口支持多继承
接口特性¶
- 接口中每一个方法也是隐式抽象的,
接口中的方法会被隐式的指定为
public abstract
(只能是``public abstract``, 其他修饰符都会报错) - 接口中可以含有变量,
但是接口中的变量会被隐式的指定为
public static final
变量(并且只能是public
, 用private
修饰会报编译错误) - 接口中的方法是不能再接口中实现的, 只能由实现接口的类来实现接口中的方法
抽象类和接口的区别¶
- 抽象类中的方法可以有方法体, 就是能实现方法的具体功能, 但是接口中的方法不行
- 抽象类中的成员变量可以是各种类型的,
而接口中的成员变量只能是
public static final
类型的 - 接口总不能含有静态代码以及静态方法(用
static
修饰的方法), 而抽象类是可以有静态代码块和静态方法 - 一个类只能继承一个抽象类, 而一个类却可以实现多个接口
接口的声明¶
语法格式
[可见度] interface 接口名称 [extends 其他的类名] {
// 声明变量
// 抽象方法
}
interface
关键字用来声明一个接口
实例
/* 文件名 : NameOfInterface.java */
import java.lang.*;
//引入包
public interface NameOfInterface
{
//任何类型 final, static 字段
//抽象方法
}
接口有以下特性
- 接口是隐式抽象的, 当声明一个接口的时候,
不必使用
abstract
关键字 - 接口中灭一个方法也是隐式抽象的,
声明时同样不需要
abstract
关键字 - 接口中的方法都是公有的
实例
/* 文件名 : Animal.java */
interface Animal {
public void eat();
public void travel();
}
接口的实现¶
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
实现一个接口的语法,可以使用这个公式:
Animal.java
...implements 接口名称[, 其他接口, 其他接口..., ...] ...
实例
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
编译运行结果如下
Mammal eats
Mammal travels
重写接口中声明的方法时, 需要注意一下规则
- 类在实现接口的方法时, 不能抛出强制性异常, 只能在接口中, 或者继承接口的抽象类中抛出该强制性异常
- 类在重写方法时要保持一致的方法名, 并且应该保持相同或者相兼容的返回值类型
- 如果实现接口的类是抽象类, 那么就没必要实现该接口的方法
在实现接口的时候, 也要注意一些规则
- 一个类可以实现多个接口
- 一个类只能继承一个类, 但是能实现多个接口
- 一个接口能继承另一个接口, 这和类之间的继承比较相似
接口的继承¶
一个接口能继承另一个接口, 和类之间的继承方式比较相似.
接口的继承使用extends
关键字, 子接口继承父接口的方法
下面的Sports
接口被Hockey
和Football
接口继承:
// 文件名: Sports.java
public interface Sports
{
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
// 文件名: Football.java
public interface Football extends Sports
{
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
// 文件名: Hockey.java
public interface Hockey extends Sports
{
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。
相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。
接口的多继承¶
在Java中,类的多继承是不合法,但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法
标记接口¶
最常用的继承接口是没有包含任何方法的接口。
标识接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标识接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:
package java.util;
public interface EventListener
{}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的
- 建立一个公共的父接口
- 正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
- 向一个类添加数据类型
- 这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
包(package)¶
为了更好地组织类, Java提供了包机制, 用于区别类名的命名空间
包的作用¶
- 把功能相似或相关的类或接口组织在同一个包中, 方便类的查找和使用
- 如同文件夹一样, 包也采用树形目录的存储方式. 同一个包中的类名字是不同的, 不同的包中的类的名字是可以相同的, 当同时调用两个不同包中相同类名的类时, 应该加上包名加以区别. 因此, 包可以避免名字冲突.
- 包也限定了访问权限, 拥有包访问权限的类才能访问某个包中的类
Java 使用包(package)这种机制是为了防止命名冲突, 访问控制, 提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
语法格式
package pkg1[.pkg2[.pkg3…]];
例如,一个Something.java 文件它的内容
package net.java.util
public class Something{
...
}
那么它的路径应该是 net/java/util/Something.java
这样保存的。
package(包) 的作用是把不同的 java 程序分类保存, 更方便的被其他 java
程序调用。
一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释), 为这些类型提供访问保护和命名空间管理的功能。
以下是一些 Java 中的包:
java.lang
: 打包基础的类java.io
: 包含输入输出功能的函数
开发者可以自己把一组类和接口等打包, 并定义自己的包。而且在实际开发中这样做是值得提倡的, 当你自己完成类的实现之后, 将相关的类分组, 可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。
由于包创建了新的命名空间(namespace), 所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制, 更容易实现访问控制, 并且让定位相关类更加简单。
创建包¶
创建包的时候,你需要为这个包取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。
包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。
如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。
例子
创建了一个叫做animals
的包。通常使用小写的字母来命名避免与类、接口名字的冲突。
在 animals
包中加入一个接口(interface):
Animal.java
/* 文件名: Animal.java */
package animals;
interface Animal {
public void eat();
public void travel();
}
接下来,在同一个包中加入该接口的实现:
MammalInt.java
package animals;
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
然后,编译这两个文件,并把他们放在一个叫做animals的子目录中。 用下面的命令来运行:
$ mkdir animals
$ cp Animal.class MammalInt.class animals
$ java animals/MammalInt
Mammal eats
Mammal travel
import¶
为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 “import” 语句可完成此功能。
在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:
import package1[.package2…].(classname|*);
如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。
例子
下面的 payroll 包已经包含了 Employee 类,接下来向 payroll 包中添加一个 Boss 类。Boss 类引用 Employee 类的时候可以不用使用 payroll 前缀,Boss类的实例如下。
// Boss.java 文件代码:
package payroll;
public class Boss
{
public void payEmployee(Employee e)
{
e.mailCheck();
}
}
如果 Boss 类不在 payroll 包中又会怎样?Boss 类必须使用下面几种方法之一来引用其他包中的类。
使用类全名描述,例如:
payroll.Employee
用 import 关键字引入,使用通配符 “*”
import payroll.*;
使用 import 关键字引入 Employee 类:
import payroll.Employee;
注意
类文件中可以包含任意数量的 import 声明。import 声明必须在包声明之后,类声明之前。
package的目录结构¶
类放在包中会有两种主要的结果:
- 包名成为类名的一部分,正如我们前面讨论的一样。
- 包名必须与相应的字节码所在的目录结构相吻合。
下面是管理你自己 java 中文件的一种简单方式:
将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:
// 文件名 : Car.java
package vehicle;
public class Car {
// 类实现
}
接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。
....\vehicle\Car.java
现在,正确的类名和路径将会是如下样子:
- 类名 -> vehicle.Car
- 路径名 -> vehicle:raw-latex:Car.java (在 windows 系统中)
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是 runoob.com,所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。
例如:有一个 com.runoob.test 的包,这个包包含一个叫做 Runoob.java 的源文件,那么相应的,应该有如下面的一连串子目录:
....\com\runoob\test\Runoob.java
编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀。 例如:
// 文件名: Runoob.java
package com.runoob.test;
public class Runoob {
}
class Google {
}
现在,我们用-d选项来编译这个文件,如下:
javac -d . Runoob.java
这样会像下面这样放置编译了的文件:
.\com\runoob\test\Runoob.class
.\com\runoob\test\Google.class
你可以像下面这样来导入所有** :raw-latex:`\com`:raw-latex:`\runoob`:raw-latex:`\test` **中定义的类、接口等:
import com.runoob.test.*;
编译之后的 .class 文件应该和 .java 源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。
<path-one>\sources\com\runoob\test\Runoob.java
<path-two>\classes\com\runoob\test\Google.class
这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到你程序中使用的所有类型。
类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。
<path-two>\classes
是 class path
,package
名字是
com.runoob.test
,而编译器和 JVM
会在
<path-two>\classes\com\runoob\test
中找 .class 文件。
一个 class path
可能会包含好几个路径,多路径应该用分隔符分开。默认情况下,编译器和
JVM
查找当前目录。JAR
文件按包含 Java
平台相关的类,所以他们的目录默认放在了 class path
中。
设置 CLASSPATH 系统变量¶
用下面的命令显示当前的CLASSPATH变量:
- Windows 平台(DOS 命令行下):
C:\> set CLASSPATH
- UNIX 平台(Bourne shell 下):
# echo $CLASSPATH
删除当前CLASSPATH变量内容:
- Windows 平台(DOS 命令行下):
C:\> set CLASSPATH=
- UNIX 平台(Bourne shell
下):
# unset CLASSPATH; export CLASSPATH
设置CLASSPATH变量:
- Windows 平台(DOS 命令行下):
C:\> set CLASSPATH=C:\users\jack\java\classes
- UNIX 平台(Bourne shell
下):
# CLASSPATH=/home/jack/java/classes; export CLASSPATH
高级教程¶
数据结构¶
Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类:
- 枚举(Enumeration)
- 位集合(BitSet)
- 向量(Vector)
- 栈(Stack)
- 字典(Dictionary)
- 哈希表(Hashtable)
- 属性(Properties)
以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection)。
集合框架¶
早在Java 2中之前,Java就提供了特设类。比如:Dictionary, Vector, Stack, 和Properties这些类用来存储和操作对象组。
虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由于这个原因,使用Vector类的方式和使用Properties类的方式有着很大不同。
集合框架被设计成要满足以下几个目标。
- 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
- 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
- 对一个集合的扩展和适应必须是简单的。
为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet等,除此之外你也可以通过这些接口实现自己的集合。
集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
- 接口:是代表集合的抽象数据类型。接口允许集合独立操纵其代表的细节。在面向对象的语言,接口通常形成一个层次。
- 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构。
- 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
除了集合,该框架也定义了几个Map接口和类。Map里存储的是键值对
。尽管Map不是collections,但是它们完全整合在集合中。
使用迭代器¶
通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。
一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或ListIterator接口。
迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了Iterator,以允许双向遍历列表和修改元素。
使用 Java Iterator
这里通过实例列出Iterator
和listIterator
接口提供的所有方法。
实例
import java.util.*;
public class Test{
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("Hello");
list.add("World");
list.add("HAHAHAHA");
//第一种遍历方法使用foreach遍历List
for (String str : list) { //也可以改写for(int i=0;i<list.size();i++)这种形式
System.out.println(str);
}
//第二种遍历,把链表变为数组相关的内容进行遍历
String[] strArray=new String[list.size()];
list.toArray(strArray);
for(int i=0;i<strArray.length;i++) //这里也可以改写为 foreach(String str:strArray)这种形式
{
System.out.println(strArray[i]);
}
//第三种遍历 使用迭代器进行相关遍历
Iterator<String> ite=list.iterator();
while(ite.hasNext())//判断下一个元素之后有值
{
System.out.println(ite.next());
}
}
}
三种方法都是用来遍历ArrayList集合,第三种方法是采用迭代器的方法,该方法可以不用担心在遍历的过程中会超出集合的长度。
实例
import java.util.*;
public class Test{
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种:普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
//第二种
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第四种
System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
for (String v : map.values()) {
System.out.println("value= " + v);
}
}
}
如何使用比较器¶
TreeSet和TreeMap的按照排序顺序来存储元素. 然而,这是通过比较器来精确定义按照什么样的排序顺序。 这个接口可以让我们以不同的方式来排序一个集合。
使用 Java Comparator
总结¶
Java集合框架为程序员提供了预先包装的数据结构和算法来操纵他们。
集合是一个对象,可容纳其他对象的引用。集合接口声明对每一种类型的集合可以执行的操作。
集合框架的类和接口均在java.util包中。
任何对象加入集合类后,自动转变为Object类型,所以在取出的时候,需要进行强制类型转换。
泛型¶
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制, 该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数。
泛型方法¶
你可以写一个泛型方法, 该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型, 编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),
该类型参数声明部分在方法返回类型之前(在下面例子中的
<E>
)。 - 每一个类型参数声明部分包含一个或多个类型参数, 参数间用逗号隔开。一个泛型参数, 也被称为一个类型变量, 是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型, 并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型, 不能是原始类型(像int,double,char的等)。
实例
下面的例子演示了如何使用泛型方法打印不同字符串的元素:
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
编译以上代码, 运行结果如下所示:
整型数组元素为:
1 2 3 4 5
双精度型数组元素为:
1.1 2.2 3.3 4.4
字符型数组元素为:
H E L L O
有界的类型参数:
可能有时候, 你会想限制那些被允许传递到一个类型参数的类型种类范围。例如, 一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
要声明一个有界的类型参数, 首先列出类型参数的名称, 后跟extends关键字, 最后紧跟它的上界。
实例
下面的例子演示了“extends”如何使用在一般意义上的意思“extends”(类)或者“implements”(接口)。该例子中的泛型方法返回三个可比较对象的最大值。
public class MaximumTest
{
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z)
{
T max = x; // 假设x是初始最大值
if ( y.compareTo( max ) > 0 ){
max = y; //y 更大
}
if ( z.compareTo( max ) > 0 ){
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main( String args[] )
{
System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ) );
System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );
System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n","pear",
"apple", "orange", maximum( "pear", "apple", "orange" ) );
}
}
编译以上代码, 运行结果如下所示
3, 4 和 5 中最大的数为 5
6.6, 8.8 和 7.7 中最大的数为 8.8
pear, apple 和 orange 中最大的数为 pear
泛型类¶
泛型类的声明和非泛型类的声明类似, 除了在类名后面添加了类型参数声明部分。
和泛型方法一样, 泛型类的类型参数声明部分也包含一个或多个类型参数, 参数间用逗号隔开。一个泛型参数, 也被称为一个类型变量, 是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数, 这些类被称为参数化的类或参数化的类型。
实例
如下实例演示了我们如何定义一个泛型类:
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("菜鸟教程"));
System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
编译以上代码, 运行结果如下所示
整型值为 :10
字符串为 :菜鸟教程
类型通配符¶
这些全都属于java泛型的通配符, 这几个其实没什么区别, 只不过是一个约定好的代码, 也就是说, 使用大写字母A,B,C,D……X,Y,Z定义的, 就都是泛型, 把T换成A也一样, 这里T只是名字上的意义而已
- ? 表示不确定的java类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
- 类型通配符一般是使用?代替具体的类型参数。例如
List<?>
在逻辑上是List<String>
,List<Integer>
等所有List的父类。
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getData(name);
getData(age);
getData(number);
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
}
输出结果为
data :icon
data :18
data :314
解析: 因为getDate()方法的参数是List类型的, 所以name, age, number都可以作为这个方法的实参, 这就是通配符的作用
- 类型通配符上限通过形如List来定义, 如此定义就是通配符泛型值接受Number及其下层子类类型。
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
//getUperNumber(name);//1
getUperNumber(age);//2
getUperNumber(number);//3
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
}
输出结果:
data :18
data :314
解析: 在(//1
)处会出现错误,
因为getUperNumber()
方法中的参数已经限定了参数泛型上限为Number
,
所以泛型为String是不在这个范围之内, 所以会报错
- 类型通配符下限通过形如
List<? super Number>
来定义, 表示类型只能接受Number
及其三层父类类型, 如Object类型的实例。
序列化¶
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
类 ObjectInputStream
和 ObjectOutputStream
是高层次的数据流,它们包含序列化和反序列化对象的方法。
ObjectOutputStream
类包含很多写方法来写各种数据类型,但是一个特别的方法例外:
public final void writeObject(Object x) throws IOException
上面的方法序列化一个对象,并将它发送到输出流。相似的
ObjectInputStream
类包含如下反序列化一个对象的方法:
public final Object readObject() throws IOException, ClassNotFoundException
该方法从流中取出下一个对象, 并将对象反序列化. 它的返回值为Object, 因此, 你需要将它转换成合适的数据类型
为了演示序列化在Java中是怎样工作的,我将使用之前教程中提到的Employee类,假设我们定义了如下的Employee类,该类实现了Serializable 接口。
Employee.java
文件代码
public class Employee implements java.io.Serializable
{
public String name;
public String address;
public transient int SSN;
public int number;
public void mailCheck()
{
System.out.println("Mailing a check to " + name
+ " " + address);
}
}
请注意,一个类的对象要想序列化成功,必须满足两个条件:
该类必须实现 java.io.Serializable 对象。
该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。
序列化对象¶
ObjectOutputStream
类用来序列化一个对象,如下的 SerializeDemo
例子实例化了一个 Employee 对象,并将该对象序列化到一个文件中。
该程序执行后,就创建了一个名为 employee.ser 文件。该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。
注意: 当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。
SerializeDemo.java
文件代码:
import java.io.*;
public class SerializeDemo
{
public static void main(String [] args)
{
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try
{
FileOutputStream fileOut =
new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /tmp/employee.ser");
}catch(IOException i)
{
i.printStackTrace();
}
}
}
反序列化对象¶
下面的 DeserializeDemo 程序实例了反序列化,/tmp/employee.ser 存储了 Employee 对象。
DeserializeDemo.java
文件代码
import java.io.*;
public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
try
{
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
以上程序编译运行结果如下所示
Deserialized Employee...
Name: Reyan Ali
Address:Phokka Kuan, Ambehta Peer
SSN: 0
Number:101
这里要注意以下要点
readObject()
方法中的 try/catch
代码块尝试捕获
ClassNotFoundException
异常。对于 JVM
可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个
ClassNotFoundException 异常。
注意,readObject()
方法的返回值被转化成 Employee
引用。
当对象被序列化时,属性 SSN 的值为 111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后 Employee 对象的 SSN 属性为 0。
网络编程¶
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
java.net 包中提供了两种常见的网络协议的支持:
- TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
- UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。
- Socket 编程:这是使用最广泛的网络概念,它已被解释地非常详细。
- URL 处理:这部分会在另外的篇幅里讲,点击这里更详细地了解在 Java 语言中的 URL 处理。
Socket 编程¶
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
- 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
- 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
- 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
- Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
- 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。
ServerSocket 类的方法¶
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有四个构造方法:
序号 | 方法描述 | |
---|---|
1 | public ServerSocket(int port) throws IOException创建绑定到特定端口的服务器套接字。 |
2 | public ServerSocket(int port, int backlog) throws IOException利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
3 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。 |
4 | public ServerSocket() throws IOException创建非绑定服务器套接字。 |
创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket 类的常用方法:
序号 | 方法描述 | |
---|---|
1 | public int getLocalPort()返回此套接字在其上侦听的端口。 |
2 | public Socket accept() throws IOException侦听并接受到此套接字的连接。 |
3 | public void setSoTimeout(int timeout)通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
4 | public void bind(SocketAddress host, int backlog)将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
Socket 类的方法¶
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
Socket 类有五个构造方法.
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
InetAddress 类的方法¶
这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:
序号 | 方法描述 | |
---|---|
1 | static InetAddress getByAddress(byte[] addr)在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
2 | static InetAddress getByAddress(String host, byte[] addr)根据提供的主机名和 IP 地址创建 InetAddress。 |
3 | static InetAddress getByName(String host)在给定主机名的情况下确定主机的 IP 地址。 |
4 | **String getHostAddress() **返回 IP 地址字符串(以文本表现形式)。 |
5 | **String getHostName() ** 获取此 IP 地址的主机名。 |
6 | static InetAddress getLocalHost()返回本地主机。 |
7 | String toString()将此 IP 地址转换为 String。 |
Socket 客户端实例¶
如下的 GreetingClient 是一个客户端程序,该程序通过 socket 连接到服务器并发送一个请求,然后等待一个响应。
// 文件名 GreetingClient.java
import java.net.*;
import java.io.*;
public class GreetingClient
{
public static void main(String [] args)
{
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try
{
System.out.println("连接到主机:" + serverName + " ,端口号:" + port);
Socket client = new Socket(serverName, port);
System.out.println("远程主机地址:" + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("Hello from " + client.getLocalSocketAddress());
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("服务器响应: " + in.readUTF());
client.close();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
Socket 服务端实例¶
如下的GreetingServer 程序是一个服务器端应用程序,使用 Socket 来监听一个指定的端口。
// 文件名 GreetingServer.java
import java.net.*;
import java.io.*;
public class GreetingServer extends Thread
{
private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException
{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
}
public void run()
{
while(true)
{
try
{
System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("远程主机地址:" + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!");
server.close();
}catch(SocketTimeoutException s)
{
System.out.println("Socket timed out!");
break;
}catch(IOException e)
{
e.printStackTrace();
break;
}
}
}
public static void main(String [] args)
{
int port = Integer.parseInt(args[0]);
try
{
Thread t = new GreetingServer(port);
t.run();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
编译以上两个 java 文件代码,并执行以下命令来启动服务,使用端口号为 6066:
$ javac GreetingServer.java
$ java GreetingServer 6066
等待远程连接,端口号为:6066...
新开一个命令窗口,执行以上命令来开启客户端:
$ javac GreetingClient.java
$ java GreetingClient localhost 6066
连接到主机:localhost ,端口号:6066
远程主机地址:localhost/127.0.0.1:6066
服务器响应: 谢谢连接我:/127.0.0.1:6066
Goodbye!
发送邮件¶
使用Java应用程序发送 E-mail 十分简单,但是首先你应该在你的机器上安装 JavaMail API 和Java Activation Framework (JAF) 。
- 您可以从 Java 网站下载最新版本的 JavaMail,打开网页右侧有个 Downloads 链接,点击它下载。
- 您可以从 Java 网站下载最新版本的 JAF(版本 1.1.1)。
你也可以使用本站提供的下载链接:
下载并解压缩这些文件,在新创建的顶层目录中,您会发现这两个应用程序的一些 jar 文件。您需要把 mail.jar 和 activation.jar文件添加到您的 CLASSPATH 中。
如果你使用第三方邮件服务器如QQ的SMTP服务器,可查看文章底部用户认证完整的实例。
发送简单的E-mail¶
下面是一个发送简单E-mail的例子。假设你的localhost已经连接到网络。
// 文件名 SendEmail.java
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class SendEmail
{
public static void main(String [] args)
{
// 收件人电子邮箱
String to = "abcd@gmail.com";
// 发件人电子邮箱
String from = "web@gmail.com";
// 指定发送邮件的主机为 localhost
String host = "localhost";
// 获取系统属性
Properties properties = System.getProperties();
// 设置邮件服务器
properties.setProperty("mail.smtp.host", host);
// 获取默认session对象
Session session = Session.getDefaultInstance(properties);
try{
// 创建默认的 MimeMessage 对象
MimeMessage message = new MimeMessage(session);
// Set From: 头部头字段
message.setFrom(new InternetAddress(from));
// Set To: 头部头字段
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Set Subject: 头部头字段
message.setSubject("This is the Subject Line!");
// 设置消息体
message.setText("This is actual message");
// 发送消息
Transport.send(message);
System.out.println("Sent message successfully....");
}catch (MessagingException mex) {
mex.printStackTrace();
}
}
}
编译并运行这个程序来发送一封简单的E-mail:
$ java SendEmail
Sent message successfully....
如果你想发送一封e-mail给多个收件人,那么使用下面的方法来指定多个收件人ID:
void addRecipients(Message.RecipientType type, Address[] addresses)throws MessagingException
下面是对于参数的描述
- type:要被设置为 TO, CC 或者 BCC,这里 CC 代表抄送、BCC 代表秘密抄送。举例:Message.RecipientType.TO
- addresses: 这是 email ID 的数组。在指定电子邮件 ID 时,你将需要使用 InternetAddress() 方法。
发送一封 HTML E-mail¶
下面是一个发送 HTML E-mail 的例子。假设你的 localhost 已经连接到网络。
和上一个例子很相似,除了我们要使用 setContent() 方法来通过第二个参数为 “text/html”,来设置内容来指定要发送HTML 内容。
// 文件名 SendHTMLEmail.java
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class SendHTMLEmail
{
public static void main(String [] args)
{
// 收件人电子邮箱
String to = "abcd@gmail.com";
// 发件人电子邮箱
String from = "web@gmail.com";
// 指定发送邮件的主机为 localhost
String host = "localhost";
// 获取系统属性
Properties properties = System.getProperties();
// 设置邮件服务器
properties.setProperty("mail.smtp.host", host);
// 获取默认的 Session 对象。
Session session = Session.getDefaultInstance(properties);
try{
// 创建默认的 MimeMessage 对象。
MimeMessage message = new MimeMessage(session);
// Set From: 头部头字段
message.setFrom(new InternetAddress(from));
// Set To: 头部头字段
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Set Subject: 头字段
message.setSubject("This is the Subject Line!");
// 发送 HTML 消息, 可以插入html标签
message.setContent("<h1>This is actual message</h1>",
"text/html" );
// 发送消息
Transport.send(message);
System.out.println("Sent message successfully....");
}catch (MessagingException mex) {
mex.printStackTrace();
}
}
}
编译并运行此程序来发送HTML e-mail:
$ java SendHTMLEmail
Sent message successfully....
发送带有附件的 E-mail¶
// 文件名 SendFileEmail.java
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
public class SendFileEmail
{
public static void main(String [] args)
{
// 收件人电子邮箱
String to = "abcd@gmail.com";
// 发件人电子邮箱
String from = "web@gmail.com";
// 指定发送邮件的主机为 localhost
String host = "localhost";
// 获取系统属性
Properties properties = System.getProperties();
// 设置邮件服务器
properties.setProperty("mail.smtp.host", host);
// 获取默认的 Session 对象。
Session session = Session.getDefaultInstance(properties);
try{
// 创建默认的 MimeMessage 对象。
MimeMessage message = new MimeMessage(session);
// Set From: 头部头字段
message.setFrom(new InternetAddress(from));
// Set To: 头部头字段
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Set Subject: 头字段
message.setSubject("This is the Subject Line!");
// 创建消息部分
BodyPart messageBodyPart = new MimeBodyPart();
// 消息
messageBodyPart.setText("This is message body");
// 创建多重消息
Multipart multipart = new MimeMultipart();
// 设置文本消息部分
multipart.addBodyPart(messageBodyPart);
// 附件部分
messageBodyPart = new MimeBodyPart();
String filename = "file.txt";
DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
multipart.addBodyPart(messageBodyPart);
// 发送完整消息
message.setContent(multipart );
// 发送消息
Transport.send(message);
System.out.println("Sent message successfully....");
}catch (MessagingException mex) {
mex.printStackTrace();
}
}
}
编译并运行
用户认证部分¶
如果需要提供用户名和密码给e-mail服务器来达到用户认证的目的,你可以通过如下设置来完成:
props.put("mail.smtp.auth", "true");
props.setProperty("mail.user", "myuser");
props.setProperty("mail.password", "mypwd");
实例
// 需要用户名密码邮件发送实例
//文件名 SendEmail2.java
//本实例以QQ邮箱为例,你需要在qq后台设置
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class SendEmail2
{
public static void main(String [] args)
{
// 收件人电子邮箱
String to = "xxx@qq.com";
// 发件人电子邮箱
String from = "xxx@qq.com";
// 指定发送邮件的主机为 smtp.qq.com
String host = "smtp.qq.com"; //QQ 邮件服务器
// 获取系统属性
Properties properties = System.getProperties();
// 设置邮件服务器
properties.setProperty("mail.smtp.host", host);
properties.put("mail.smtp.auth", "true");
// 获取默认session对象
Session session = Session.getDefaultInstance(properties,new Authenticator(){
public PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication("xxx@qq.com", "qq邮箱密码"); //发件人邮件用户名、密码
}
});
try{
// 创建默认的 MimeMessage 对象
MimeMessage message = new MimeMessage(session);
// Set From: 头部头字段
message.setFrom(new InternetAddress(from));
// Set To: 头部头字段
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Set Subject: 头部头字段
message.setSubject("This is the Subject Line!");
// 设置消息体
message.setText("This is actual message");
// 发送消息
Transport.send(message);
System.out.println("Sent message successfully....from runoob.com");
}catch (MessagingException mex) {
mex.printStackTrace();
}
}
}
多线程编程¶
Java 给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守候线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
一个线程的生命周期¶
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

001
- 新建状态:
- 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
- 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
- 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
- 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 死亡状态:
- 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的优先级¶
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
通过实现 Runnable 接口来创建线程¶
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。
为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:
public void run()
你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
Thread 定义了几个构造方法,下面的这个是我们经常使用的:
Thread(Runnable threadOb,String threadName);
这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。
新线程创建之后,你调用它的 start() 方法它才会运行。
void start();
下面是一个创建线程并开始让它执行的实例:
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
编译以上程序运行结果如下:
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
通过继承Thread来创建线程¶
创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡醒一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
编译以上程序运行结果如下:
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Thread 方法¶
下表列出了Thread类的一些重要方法:
序号 | 方法描述 | |
---|---|
1 | public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name)改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec)等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt()中断线程。 |
8 | public final boolean isAlive()测试线程是否处于活动状态。 |
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。
如下的ThreadClassDemo 程序演示了Thread类的一些方法:
// 文件名 : DisplayMessage.java
// 通过实现 Runnable 接口创建线程
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) {
this.message = message;
}
public void run() {
while(true) {
System.out.println(message);
}
}
}
// 文件名 : GuessANumber.java
// 通过继承 Thread 类创建线程
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) {
this.number = number;
}
public void run() {
int counter = 0;
int guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " guesses " + guess);
counter++;
} while(guess != number);
System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");
}
}
// 文件名 : ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String [] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
System.out.println("Starting hello thread...");
thread1.start();
Runnable bye = new DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starting goodbye thread...");
thread2.start();
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try {
thread3.join();
}catch(InterruptedException e) {
System.out.println("Thread interrupted.");
}
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
}
}
运行结果如下,每一次运行的结果都不一样。
Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......
通过 Callable 和 Future 创建线程
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
创建线程的三种方式的对比¶
- 采用实现 Runnable、Callable 接口的方式创见多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
多线程的使用¶
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
Collections¶
是针对集合进行操作的工具类
Collection和Collections的区别¶
- Collection 是单列集合的顶层接口,有两个子接口List和Set
- Collections 是针对集合进行操作的工具类,可以对集合进行排序和查找等
常见的几个小方法¶
public static <T> void sort(List<T> list)
public static <T> int binarySearch(List<?> list,T key)
public static <T> T max(Collection<?> coll)
public static void reverse(List<?> list)
public static void shuffle(List<?> list)
Collection集合总结¶
- Collection
- |–List 有序,可重复
- |–ArrayList
- 底层数据结构是数组,查询快,增删慢。
- 线程不安全,效率高
- |–Vector
- 底层数据结构是数组,查询快,增删慢。
- 线程安全,效率低
- |–LinkedList
- 底层数据结构是链表,查询慢,增删快。
- 线程不安全,效率高
- |–ArrayList
- |–Set 无序,唯一
- |–HashSet
- 底层数据结构是哈希表。
- 如何保证元素唯一性的呢?
- 依赖两个方法:hashCode()和equals()
- 开发中自动生成这两个方法即可
- |–LinkedHashSet
- 底层数据结构是链表和哈希表
- 由链表保证元素有序
- 由哈希表保证元素唯一
- |–TreeSet
- 底层数据结构是红黑树。
- 如何保证元素排序的呢?
- 自然排序
- 比较器排序
- 如何保证元素唯一性的呢?
- 根据比较的返回值是否是0来决定
- |–HashSet
- |–List 有序,可重复
在集合中常见的数据结构(掌握)¶
- ArrayXxx:底层数据结构是数组,查询快,增删慢
- LinkedXxx:底层数据结构是链表,查询慢,增删快
- HashXxx:底层数据结构是哈希表。依赖两个方法:
hashCode()
和equals()
- TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序
针对Collection集合我们到底使用谁¶
- 是否唯一
- 是:Set
- 排序吗?
- 是:TreeSet
- 否:HashSet
- 如果你知道是Set,但是不知道是哪个Set,就用HashSet。
- 排序吗?
- 否:List
- 要安全吗?
- 是:Vector
- 否:ArrayList或者LinkedList
- 查询多:ArrayList
- 增删多:LinkedList
- 如果你知道是List,但是不知道是哪个List,就用ArrayList。
- 要安全吗?
- 是:Set
- 如果你知道是Collection集合,但是不知道使用谁,就用ArrayList。
- 如果你知道用集合,就用ArrayList。
Map¶
将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
Map和Collection的区别¶
- Map 存储的是键值对形式的元素,键唯一,值可以重复。夫妻对
- Collection 存储的是单独出现的元素,子接口Set元素唯一,子接口List元素可重复。
Map功能概述(待补齐)¶
- 添加功能
- 删除功能
- 判断功能
- 获取功能
- 长度功能
Map集合的遍历¶
- 获取所有键的集合
- 遍历键的集合,得到每一个键
- 根据键到集合中去找值
- 获取所有的键值对对象的集合
- 遍历键值对对象的集合,获取每一个键值对对象
- 根据键值对对象去获取键和值
代码体现:
Map<String,String> hm = new HashMap<String,String>();
hm.put("it002","hello");
hm.put("it003","world");
hm.put("it001","java");
//方式1 键找值
Set<String> set = hm.keySet();
for(String key : set) {
String value = hm.get(key);
System.out.println(key+"---"+value);
}
//方式2 键值对对象找键和值
Set<Map.Entry<String,String>> set2 = hm.entrySet();
for(Map.Entry<String,String> me : set2) {
String key = me.getKey();
String value = me.getValue();
System.out.println(key+"---"+value);
}
HashMap集合(常用)¶
- HashMap<String,String>
- HashMap<Integer,String>
- HashMap<String,Student>
- HashMap<Student,String>
TreeMap集合的练习¶
- TreeMap<String,String>
- TreeMap<Student,String>
Set集合(理解)¶
Set集合的特点¶
无序,唯一
HashSet集合(掌握)¶
- 底层数据结构是哈希表(是一个元素为链表的数组)
- 哈希表底层依赖两个方法:hashCode()和equals()
- 执行顺序:
- 首先比较哈希值是否相同
- 相同:继续执行equals()方法
- 返回true:元素重复了,不添加
- 返回false:直接把元素添加到集合
- 不同:就直接把元素添加到集合
- 相同:继续执行equals()方法
- 首先比较哈希值是否相同
- 执行顺序:
- 如何保证元素唯一性的呢? - 由hashCode()和equals()保证的
- 开发的时候,代码非常的简单,自动生成即可。
- HashSet存储字符串并遍历
- HashSet存储自定义对象并遍历(对象的成员变量值相同即为同一个元素)
TreeSet集合¶
- 底层数据结构是红黑树(是一个自平衡的二叉树)
- 保证元素的排序方式
- 自然排序(元素具备比较性)
- 让元素所属的类实现Comparable接口
- 比较器排序(集合具备比较性)
- 让集合构造方法接收Comparator的实现类对象
- 自然排序(元素具备比较性)
- 把我们讲过的代码看一遍即可
案例¶
- 获取无重复的随机数
- 键盘录入学生按照总分从高到底输出
高级教程¶
Spring¶
IntelliJ IDEA¶
Spring¶
- 使用POJO进行轻量级和最小侵入式开发
- 通过依赖注入和基于接口编程实现松耦合
- 通过AOP和默认习惯进行声明式编程
- 使用AOP和模板(template)减少模式化代码
Swagger2构建RESTful API¶
tmp¶
定制Banner¶
Spring Boot启动的时候有一个默认的启团
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.9.RELEASE)
src/main/resources
下新建 banner.txt
通过如下网站生成字符
http://patorjk.com/software/taag
复制写入 banner.txt
文件, 启动之后, 图案就会变成设定的图案.
访问mysql¶
前提¶
JDK 1.8
或更高Gradle 2.3+
orMaven 3.0+
- IDE
将项目下initial
导入到IntelliJ IDEA
git clone https://github.com/spring-guides/gs-accessing-data-mysql.git
cd into gs-accessing-data-mysql/initial
创建数据库¶
mysql> create database db_example;
-- 创建用户
mysql> create user 'springuser'@'localhost' identified by '111111';
-- 授权, 后期根据需求授权
mysql> grant all on db_example.* to 'springuser'@'%';
创建application.properties文件¶
Spring Boot默认使用H2
数据库, 所以我们需要更改为mysql,
并定义数据库相关设置.
创建文件
src/main/resources/application.properties
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/db_example?verifyServerCertificate=false&useSSL=false&requireSSL=false
spring.datasource.username=springuser
spring.datasource.password=111111
spring.jpa.hibernate.ddl-auto
选项
spring.jpa.hibernate.ddl-auto
可以设置为none
, update
,
create
, create-drop
, 具体说明查阅Hibernate
文档
none
使用 MySQL 数据库时, 默认值为 none
, 不更改数据库结构.
update
Hibernate会根据给予的实体结构更改数据库. create
每次都创建数据库, 关闭的时候不会删除数据库. create-drop
创建并且在
SessionFactory
关闭的时候删除数据库.
这里使用create
, 因为我们还没有创建表结构, 运行一次之后,
需要根据程序把spring.jpa.hibernate.ddl-auto
设置为update
或none
,
当你想修改表结构的时候, 使用update
.
Spring为 H2
或者其他嵌入的数据库使用的是 create-drop
,
其他的为none
, 比如 MySQL
生产中, 推荐把该参数设置为none
, 并且回收用户权限,
给与SELECT, UPDATE, INSERT, DELETE
权限即可.
创建 @Entity 模型¶
src/main/java/hello/User.java
package hello;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
private String email;
public Integer getId(){
return id;
}
public void setId(Integer id){
this.id = id;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getEmail(){
return email;
}
public void setEmail(String email){
this.email = email;
}
}
实例类, Hibernate
将自动转换成表结构.
创建repository¶
src/main/java/hello/UserRepository.java
package hello;
import org.springframework.data.repository.CrudRepository;
import hello.User;
// CRUD refers Create, Read, Update, Delete
public interface UserRepository extends CrudRepository<User, Long> {
}
repository接口, this will be automatically implemented by Spring in a bean with the same name with changing case The bean name will be userRepository
创建controller¶
src/main/java/hello/MainController.java
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import hello.User;
import hello.UserRepository;
@Controller // 表明该类为 Controller
@RequestMapping(path="/demo") // url为 /demo
public class MainController {
@Autowired
private UserRepository userRepository;
@GetMapping(path="/add")
public @ResponseBody String addNewUser(@RequestParam String name
, @RequestParam String email){
User n = new User();
n.setName(name);
n.setEmail(email);
userRepository.save(n);
return "Saved";
}
@GetMapping(path = "/all")
public @ResponseBody Iterable<User> getAllUsers(){
return userRepository.findAll();
}
}
上述示例没有明确指定 GET
,PUT
,POST
等等, 因为
@GetMapping
是一个快捷方式 @RequestMapping(method=GET)
.
@RequestMapping
方法默认映射所有HTTP方法.
使用@RequestMapping(method=GET)
或其他方法缩小映射.
打包¶
Although it is possible to package this service as a traditional WAR file for deployment to an external application server, the simpler approach demonstrated below creates a standalone application. You package everything in a single, executable JAR file, driven by a good old Java main() method. Along the way, you use Spring’s support for embedding the Tomcat servlet container as the HTTP runtime, instead of deploying to an external instance.
src/main/java/hello/Application.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String [] args){
SpringApplication.run(Application.class, args);
}
}
打成JAR
包
你可以在命令行使用 Gradle
或 Maven
.
或者你可以构建成一个包含所有依赖, 类, 资源文件的JAR
包, 然后运行.
这会便于传输, 版本显示, 以及将服务部署成一个应用, 贯穿整个开发周期,
部署在不同的环境等等.
如果你在使用Gradle
, 你可以通过运行./gradlew bootRun
来运行.
或者你可以使用 ./gradlew build
构建JAR
包 .
然后使用如下命令运行jar包:
java -jar build/libs/gs-accessing-data-mysql-0.1.0.jar
如果你使用Maven
, 你可以使用 ./mvnw spring-boot:run
运行应用.
或者使用 ./mvnw clean package
打包, 运行:
java -jar target/gs-accessing-data-mysql-0.1.0.jar
上面的步骤可以创建一个可执行的JAR
包. 你也可以构建传统的WAR
包.
运行过程中国会打印日志, 服务会在几秒钟后启动.
测试应用¶
使用 curl
或者浏览器测试. 现在有2个 REST Web Services
localhost:8080/demo/all This gets all data localhost:8080/demo/add This adds one user to the data
curl 'localhost:8080/demo/add?name=First&email=someemail@someemailprovider.com'
会得到如下回应
Saved
curl 'localhost:8080/demo/all'
会得到如下回应
[{"id":1,"name":"First","email":"someemail@someemailprovider.com"}]
模板引擎¶
静态资源访问¶
Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则:
- /static
- /public
- /resources
- /META-INF/resources
我们可以在src/main/resources/
目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/xxx.jpg。如能显示图片,配置成功。
模板¶
Spring Boot提供了默认配置的模板引擎主要有以下几种:
- Thymeleaf
- FreeMarker
- Velocity
- Groovy
- Mustache
上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates
。路径可修改
示例
<table>
<thead>
<tr>
<th th:text="#{msgs.headers.name}">Name</td>
<th th:text="#{msgs.headers.price}">Price</td>
</tr>
</thead>
<tbody>
<tr th:each="prod : ${allProducts}">
<td th:text="${prod.name}">Oranges</td>
<td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td>
</tr>
</tbody>
</table>
Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,所以Thymeleaf的模板可以通过浏览器直接打开展现,这样非常有利于前后端的分离。
使用在Spring Boot中使用Thymeleaf,只需要引入下面依赖,并在默认的模板路径src/main/resources/templates下编写模板文件即可完成。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
注解¶
lib¶
microservice¶
consul¶
搭建consul¶
仅供参考
1 规划¶
IP | 角色 | 系统 |
---|---|---|
2.6 | bootstrap | CentOS 6.8 |
2.3 | server | windows xp |
2.4 | server | CentOS 6.6 |
2.5 | client | CentOS 6.6 |
2.18 | client | CentOS 6.8 |
2.19 | client | CentOS 6.8 |
2.198 | client | CentOS 6.8 |
2.199 | client | CentOS 6.8 |
2 安装consul¶
所有机器执行以下命令
# mkdir -p /data/program/consul
# cd /data/program/consul
# ll consul*
-rw-r--r--+ 1 root root 6.2M 10月 27 11:38 consul_0.7.0_linux_amd64.zip
# unzip consul_0.7.0_linux_amd64.zip
# echo "export PATH=$PATH:/data/program/consul" >>/etc/profile
# source /etc/profile
# which consul
/data/program/consul/consul
3 2.199安装consul-template¶
# cd /data/program/consul
# ls
consul consul_0.7.0_linux_amd64.zip consul-template_0.16.0_linux_amd64.zip
# unzip consul-template_0.16.0_linux_amd64.zip
# ll
总用量 42804
-rwxr-xr-x 1 root root 24003648 9月 15 00:16 consul
-rw-r--r-- 1 root root 6470848 10月 27 11:38 consul_0.7.0_linux_amd64.zip
-rwxr-xr-x 1 root root 10128479 9月 23 06:24 consul-template
-rw-r--r-- 1 root root 3219587 9月 23 06:27 consul-template_0.16.0_linux_amd64.zip
# cp consul-template /usr/bin
# which consul-template
/usr/bin/consul-template
4 每台机器配置¶
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/bootstrap/config.json
{
"bootstrap": true,
"server": true,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "bs2-6",
"bind_addr": "192.168.2.6",
"client_addr": "0.0.0.0",
"ui": true,
"log_level": "INFO",
"enable_syslog": true,
"retry_join": ["192.168.2.3","192.168.2.4"]
}
# vim /etc/init.d/consuld_bs
# chmod 755 /etc/init.d/consuld_bs
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/server/config.json
{
"bootstrap": false,
"server": true,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "s2-4",
"bind_addr": "192.168.2.4",
"client_addr": "0.0.0.0",
"ui": true,
"log_level": "INFO",
"enable_syslog": true,
"retry_join": ["192.168.2.6","192.168.2.3"]
}
# vim /etc/init.d/consuld_s
# chmod 755 /etc/init.d/consuld_s
2.5
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/client/config.json
{
"server": false,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "c2-5",
"bind_addr": "192.168.2.5",
"log_level": "INFO",
"enable_syslog": true,
"start_join": ["192.168.2.6","192.168.2.3","192.168.2.4"],
"retry_join": ["192.168.2.6","192.168.2.3","192.168.2.4"]
}
# vim /etc/init.d/consuld
# chmod 755 /etc/init.d/consuld
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/client/config.json
{
"server": false,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "c2-18",
"bind_addr": "192.168.2.18",
"log_level": "INFO",
"enable_syslog": true,
"start_join": ["192.168.2.6","192.168.2.3","192.168.2.4"],
"retry_join": ["192.168.2.6","192.168.2.3","192.168.2.4"]
}
# vim /etc/init.d/consuld
# chmod 755 /etc/init.d/consuld
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/client/config.json
{
"server": false,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "c2-19",
"bind_addr": "192.168.2.19",
"client_addr": "0.0.0.0",
"log_level": "INFO",
"enable_syslog": true,
"start_join": ["192.168.2.6","192.168.2.3","192.168.2.4"],
"retry_join": ["192.168.2.6","192.168.2.3","192.168.2.4"]
}
# vim /etc/init.d/consuld
# chmod 755 /etc/init.d/consuld
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/client/config.json
{
"server": false,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "c2-198",
"bind_addr": "192.168.2.198",
"log_level": "INFO",
"enable_syslog": true,
"start_join": ["192.168.2.6","192.168.2.3","192.168.2.4"],
"retry_join": ["192.168.2.6","192.168.2.3","192.168.2.4"]
}
# vim /etc/init.d/consuld
# chmod 755 /etc/init.d/consuld
# mkdir -p /etc/consul.d/{bootstrap,server,client}
# cat /etc/consul.d/client/config.json
{
"server": false,
"datacenter": "jiuge-01",
"data_dir": "/data/program/consul/consul_data",
"node_name": "c2-199",
"bind_addr": "192.168.2.199",
"log_level": "INFO",
"enable_syslog": true,
"start_join": ["192.168.2.6","192.168.2.3","192.168.2.4"],
"retry_join": ["192.168.2.6","192.168.2.3","192.168.2.4"]
}
# vim /etc/init.d/consuld
# chmod 755 /etc/init.d/consuld
参考
5 启动脚本¶
# cat /etc/init.d/consuld
#!/bin/bash
#
# consul Manage the consul agent
#
# chkconfig: 2345 95 5
# description: Consul is a tool for service discovery and configuration
# processname: consul
# config: /etc/consul.conf
# pidfile: /var/run/consul.pid
### BEGIN INIT INFO
# Provides: consul
# Required-Start: $local_fs $network
# Required-Stop:
# Should-Start:
# Should-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Manage the consul agent
# Description: Consul is a tool for service discovery and configuration
### END INIT INFO
# source function library
. /etc/rc.d/init.d/functions
prog="consul"
user="consul"
exec="/data/program/consul/$prog"
pidfile="/var/run/$prog.pid"
lockfile="/var/lock/subsys/$prog"
logfile="/var/log/$prog.log"
confdir="/etc/consul.d/client"
# pull in sysconfig settings
start() {
[ -x $exec ] || exit 5
[ -d $confdir ] || exit 6
umask 077
touch $logfile $pidfile
echo -n $"Starting $prog: "
## holy shell shenanigans, batman!
## daemon can't be backgrounded. we need the pid of the spawned process,
## which is actually done via runuser thanks to --user. you can't do "cmd
## &; action" but you can do "{cmd &}; action".
daemon \
--pidfile=$pidfile \
" { $exec agent -config-dir=$confdir &>> $logfile & } ; echo \$! >| $pidfile "
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch $lockfile
return $RETVAL
}
stop() {
echo -n $"Shutting down $prog: "
## graceful shutdown with SIGINT
killproc -p $pidfile $exec -INT
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f $lockfile
return $RETVAL
}
restart() {
stop
sleep 2
start
}
reload() {
echo -n $"Reloading $prog: "
killproc -p $pidfile $exec -HUP
echo
}
force_reload() {
restart
}
rh_status() {
status -p "$pidfile" -l $prog $exec
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
restart
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
exit 2
esac
exit $?
参考
# cat /etc/init.d/consul-templated
#!/bin/sh
#
# consul-template - this script manages the consul-template
#
# chkconfig: 345 97 03
# processname: consul-template
### BEGIN INIT INFO
# Provides: consul-template
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: Manage the vault server
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
if [ -L $0 ]; then
initscript=`/bin/readlink -f $0`
else
initscript=$0
fi
exec="/bin/consul-template"
prog=`/bin/basename $exec`
conffile="/etc/consul.d/consul_template/consul_template.conf"
lockfile="/var/lock/subsys/${prog}"
logfile="/var/log/${prog}.log"
pidfile="/var/run/${prog}.pid"
RETVAL=0
start() {
[ -x $exec ] || exit 5
[ -f $conffile ] || exit 6
echo -n $"Starting $prog: "
/bin/touch $logfile
$exec -pid-file=$pidfile -config=$conffile &>> $logfile &
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch ${lockfile}
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
killproc -p $pidfile $exec 2>>$logfile
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
return $RETVAL
}
restart() {
stop
sleep 2
start
}
reload() {
echo -n $"Reloading $prog: "
killproc -p $pidfile $exec -HUP
RETVAL=$?
echo
}
force_reload() {
restart
}
configtest() {
if [ "$#" -ne 0 ] ; then
case "$1" in
-q)
FLAG=$1
;;
*)
;;
esac
shift
fi
${exec} -dry -config=${conffile} $FLAG
RETVAL=$?
return $RETVAL
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
# See how we were called.
case "$1" in
start)
rh_status >/dev/null 2>&1 && exit 0
start
;;
stop)
stop
;;
status)
rh_status
RETVAL=$?
;;
restart)
restart
;;
reload)
reload
;;
configtest)
configtest
;;
*)
echo $"Usage: $prog {start|stop|restart|reload|status|configtest}"
RETVAL=2
esac
exit $RETVAL
# tree /etc/consul.d/consul_template/
├── consul_template.conf
├── consul_template.conf.example
└── weixin-server.ctmpl
# cat /etc/consul.d/consul_template/weixin-server.ctmpl
upstream weixinHost {
{{range service "weixin-server"}}
server {{.Address}}:{{.Port}};
{{end}}
}
server {
listen 80;
server_name test118;
location /weixin {
proxy_pass http://weixinHost/weixin/server;
}
}
# cat /etc/consul.d/consul_template/consul_template.conf
reload_signal = "SIGHUP"
dump_signal = "SIGQUIT"
kill_signal = "SIGINT"
retry = "10s"
max_stale = "10m"
log_level = "debug"
pid_file = "/var/run/consul-template.pid"
wait = "5s:10s"
template {
source = "/etc/consul.d/consul_template/weixin-server.ctmpl"
destination = "/etc/nginx/conf.d/weixin.conf"
command = "service nginx reload || true"
perms = 0644
backup = true
left_delimiter = "{{"
right_delimiter = "}}"
wait = "2s:6s"
}
参考
6 consul使用¶
#查看集群节点详细情况
# consul members
#离开集群
# consul leave
#强制离开集群
# consul force-leave c27
#通过api查看节点详细情况
# curl http://192.168.11.46:8500/v1/catalog/nodes\?pretty
#通过api删除节点
# curl -X PUT http://192.168.11.46:8500/v1/agent/force-leave/c27
microservice¶
network¶
DNS解析基本流程¶
1、DNS简介
DNS,全称Domain Name System,它在一个网站运行中祈祷了至关重要的作用,它的主要作用是负责把网站域名解析为对应的IP地址,例如:把www.baidu.com解析为对应的IP地址记录,如1.1.1.1,这个从域名到IP的解析过程,称作A记录,即Address Record
DNS系统出了负责这个最重要的A记录解析外,还有很多的功能:
- 设置CNAME别名记录,这个别名解析功能长被CDN加速服务商应用。
- 设置MX邮件记录,这个MX记录功能,在购买或搭建邮件服务时会被用到。
- 设置PTR记录,反向解析,即把IP地址解析为对应的域名,和A记录的解析相反,邮件服务等业务中会用到。
DNS系统的架构类似于一颗倒挂着的树(和Linux系统目录结构类似),它的顶点也是根(“.”),只不过这个根是用点来表示的,而不是目录的根斜线。

DNS
递归和迭代¶
简单的讲
递归是重复调用模块自身实现循环。
迭代是函数内某段代码实现循环。
OID获取¶
http://www.mamicode.com/info-detail-1391074.html
华为¶
S5700-28C-EI
找到对应版本号
V200R003C00SPC300

S5700-01
将下载的MIB 库解压后打开,找到HUAWEI-CPU-MIB中的详细描述,找到OID的前缀为 1.3.6.1.4.1.2011.6.3.4.1.1
思科¶
要监控交换机的端口流量、状态,CPU使用率,内存状态,温度等,关键是找出与之相对应的OID,本文将与大家探讨怎么样获取思科及华为交换机的OID,方法是一样的,大家可以举一反三。
一、思科OID的获取
https://www.cisco.com/c/en/us/support/web/tools-catalog.html
- 找到MIB Locator,并点进去

Cisco-MIB
选择SNMP Object Navigator这一项,并登陆思科账号
在SNMP Object Navigator里,选择MIB SUPPORT-SW ,将要查找OID 的交换机的IOS 名称填写进image-name框中,点击search,会出来交换机所有的MIB 库
根据所使用的snmp版本选择对应的v1或v2,查找相对应的OID 库,这里我以环境OID 为例。
找到CISCO-ENVMON-MIB,点击后面的V2
按CTRL+F,查找Temperature,copy ciscoEnvMonTemperatureStatusValue,注意要找值一定是在OBJECT-TYPE前面
在SNMP Object Navigator里,选择TRANSLATE/BROWSE,将刚刚复制的值粘贴到object name里面,点击Translate,得出相应的OID值为1.3.6.1.4.1.9.9.13.1.3.1.3
在linux系统中使用snmpwalk命令获取OID的全值,上一步获取的只是OID的一部分。
命令:snmpwalk -v 2c -c snmp-ready-value ip .1.3.6.1.4.1.9.9.13.1.3.1.3,得到的完全OID为1.3.6.1.4.1.9.9.13.1.3.1.3.1008
1.3.6.1.4.1.9.9.13.1.3.1.3前面加一“.”或不加,效果是一样的。 从snmpwalk命令获取的信息可以知道,该交换机的温度为39度,可以到交换机上用命令show env temperature status核对
可以随便找一个交换机的IOS来试试找一下CPU跟内存,看跟我找的是不是一样的。
.1.3.6.1.4.1.9.9.48.1.1.1.6.1 ciscoMemoryPoolFree
.1.3.6.1.4.1.9.9.48.1.1.1.5.1 ciscoMemoryPoolUsed
.1.3.6.1.4.1.9.2.1.57.0 CPU utilization for one minute
.1.3.6.1.4.1.9.2.1.58.0 CPU utilization for five minutes
.1.3.6.1.4.1.9.2.1.56.0 CPU utilization for five seconds
网络相关知识¶
华为S5700配置¶
常用命令¶
进入系统视图¶
system-view
sys
# Enter system view, return user view with Ctrl+Z.
显示端口vlan信息,display可缩写为dis¶
display port vlan
进入端口进行配置¶
int
<S5700-24TP-PWR-SI_1>display port vlan
[S5700-24TP-PWR-SI_1]dis vlan
[S5700-24TP-PWR-SI_1]dis interface brief
PHY: Physical
*down: administratively down
(l): loopback
(s): spoofing
(E): E-Trunk down
(b): BFD down
(e): ETHOAM down
(dl): DLDP down
(d): Dampening Suppressed
InUti/OutUti: input utility/output utility
Interface PHY Protocol InUti OutUti inErrors outErrors
GigabitEthernet0/0/1 up up 0.01% 0.11% 0 0
[S5700-24TP-PWR-SI_1]int
过滤¶
include
示例: display arp | inc 192
关闭端口¶
[S5700-24TP-PWR-SI_1]interface GigabitEthernet 0/0/1
[S5700-24TP-PWR-SI_1-GigabitEthernet0/0/1]shutdown
查看状态(up,down)¶
dis interface brief
dis ip int brief
<S5700-24TP-PWR-SI_1>dis interface brief
PHY: Physical
*down: administratively down
(l): loopback
(s): spoofing
(E): E-Trunk down
(b): BFD down
(e): ETHOAM down
(dl): DLDP down
(d): Dampening Suppressed
InUti/OutUti: input utility/output utility
Interface PHY Protocol InUti OutUti inErrors outErrors
GigabitEthernet0/0/1 *down down 0% 0% 0 0
GigabitEthernet0/0/2 up up 0.01% 0.01% 0 0
GigabitEthernet0/0/3 *down down 0% 0% 0 0
GigabitEthernet0/0/4 *down down 0% 0% 0 0
GigabitEthernet0/0/5 *down down 0% 0% 0 0
GigabitEthernet0/0/6 *down down 0% 0% 0 0
GigabitEthernet0/0/7 *down down 0% 0% 0 0
GigabitEthernet0/0/8 *down down 0% 0% 0 0
GigabitEthernet0/0/9 *down down 0% 0% 0 0
GigabitEthernet0/0/10 *down down 0% 0% 0 0
GigabitEthernet0/0/11 *down down 0% 0% 0 0
GigabitEthernet0/0/12 *down down 0% 0% 0 0
GigabitEthernet0/0/13 *down down 0% 0% 0 0
GigabitEthernet0/0/14 *down down 0% 0% 0 0
GigabitEthernet0/0/15 *down down 0% 0% 0 0
GigabitEthernet0/0/16 *down down 0% 0% 0 0
GigabitEthernet0/0/17 *down down 0% 0% 0 0
GigabitEthernet0/0/18 *down down 0% 0% 0 0
GigabitEthernet0/0/19 *down down 0% 0% 0 0
GigabitEthernet0/0/20 up up 0.01% 0.01% 0 0
GigabitEthernet0/0/21 *down down 0% 0% 0 0
GigabitEthernet0/0/22 *down down 0% 0% 0 0
GigabitEthernet0/0/23 *down down 0% 0% 0 0
GigabitEthernet0/0/24 up up 0.14% 0.01% 464676 0
查看端口类型¶
http://bbs.51cto.com/thread-88186-1.html
Access端口只属于1个VLAN,所以它的缺省VLAN就是它所在的VLAN,不用设置;
Hybrid端口和Trunk端口属于多个VLAN,所以需要设置缺省VLAN ID
。缺省情况下,Hybrid端口和Trunk端口的缺省VLAN为VLAN
1
如果设置了端口的缺省VLAN ID
,当端口接收到不带VLAN
Tag的报文后,则将报文转发到属于缺省VLAN的端口;当端口发送带有VLAN
Tag的报文时,如果该报文的VLAN ID与端口缺省的VLAN
ID相同,则系统将去掉报文的VLAN Tag,然后再发送该报文。
华为交换机缺省VLAN被称为“Pvid Vlan”, 对于思科交换机缺省VLAN被称为“Native Vlan”
<S5700-28C-EI_1>dis port ?
protect-group Protect-group index
vlan Virtual LAN
<S5700-28C-EI_1>display port vlan
...
Acess端口收报文
收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有则直接丢弃(缺省)
Acess端口发报文
将报文的VLAN信息剥离,直接发送出去
trunk端口收报文
收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有判断该trunk端口是否允许该 VLAN的数据进入:如果可以则转发,否则丢弃
trunk端口发报文
比较端口的PVID和将要发送报文的VLAN信息,如果两者相等则剥离VLAN信息,再发送,如果不相等则直接发送
hybrid端口收报文
收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有则判断该hybrid端口是否允许该VLAN的数据进入:如果可以则转发,否则丢弃(此时端口上的untag配置是不用考虑的,untag配置只对发送报文时起作用)
hybrid端口发报文
- 判断该VLAN在本端口的属性(disp interface 即可看到该端口对哪些VLAN是untag, 哪些VLAN是tag)
- 如果是untag则剥离VLAN信息,再发送,如果是tag则直接发送
示例¶
修改时间¶
<S5700-24TP-PWR-SI_2>dis clock
2017-05-31 15:08:07-05:13
Wednesday
Time Zone(Indian Standard Time) : UTC-05:13
<S5700-24TP-PWR-SI_2>clock timezone BJ add 8
<S5700-24TP-PWR-SI_2>clock datetime 15:09:00 2017-05-31
<S5700-24TP-PWR-SI_2>dis clock
2017-05-31 15:09:04+08:00
Wednesday
Time Zone(BJ) : UTC+08:00
设置NTP¶
设置NTP
[Quidway]ntp-service unicast-server X.X.X.X
设置时区
<Quidway>clock timezone BJ add 8
静态绑定IP与Mac地址¶
DHCP服务使用的全局模式
# 进入对应地址池
ip pool 192.168.77.0
#绑定
sys
ip pool 192.168.77.0
static-bind ip-address 192.168.77.223 mac-address 28f3-6623-b8e9
static-bind ip-address 192.168.0.211 mac-address c48e-8f74-14b9
### 如果有如下报错,原因是IP被使用,或者已经分配,此时需要释放全局地址池的该IP(需要在用户视图下执行)
(如果释放之后还提示该错误,是因为操作不够快,写好命令直接粘贴就可以了)
Error: The IP address's status is error.
# ---
下面是释放全局地址池名称为mypool的192.168.20.230这个IP的示例
reset ip pool name mypool 192.168.20.230
示例:
reset ip pool name 192.168.20.0 192.168.20.230
如果报如下错误, 则是因为已经给这个mac地址分配过IP了, 先关闭设备WiFi, 再进行绑定即可
Error: The static-MAC is exist in this IP-pool.
华为交换机常用命令¶
查看当前视图下的配置信息¶
display this
显示当前配置 display current-configuration
显示接口信息 display interface GigabitEthernet 1/1/4
显示cpu信息 display cpu
显示接口acl应用信息 display packet-filter interface GigabitEthernet 1/1/4
显示所有acl设置(3900系列交换机) display acl all
显示所有acl设置(6500系列交换机) display acl config all
显示该ip地址的mac地址,所接交换机的端口位置 display arp 10.78.4.1
进入系统图(配置交换机) system-view (等于config t 命令)
设置路由 ip route-static 0.0.0.0 0.0.0.0 10.78.1.1 preference 60
重置接口信息 reset counters interface Ethernet 1/0/14
保存设置 save
退出 quit
11. acl number 5000 在system-view命令后使用,进入acl配置状态
12. rule 0 deny 0806 ffff 24 0a4e0401 f 40 在上面的命令后使用,,acl 配置例子
13. rule 1 permit 0806 ffff 24 000fe218ded7 f 34 //在上面的命令后使用,acl配置例子
14. interface GigabitEthernet 1/0/9 //在system-view命令后使用,进入接口配置状态
15. [86ZX-S6503-GigabitEthernet1/0/9]qos //在上面的命令后使用,进入接口qos配置
16. [86ZX-S6503-qosb-GigabitEthernet1/0/9]packet-filter inbound user-group 5000 //在上面的命令后使用,在接口上应用进站的acl
17. [Build4-2_S3928TP-GigabitEthernet1/1/4]packet-filter outbound user-group 5001 //在接口上应用出站的acl16. undo acl number 5000 //取消acl number 5000 的设置
其他¶
单交换机VLAN划分¶
命令 命令解释 system 进入系统视图 system-view 进入系统视图 quit 退到系统视图 undo vlan 20 删除vlan 20 sysname 交换机命名 disp vlan 显示vlan vlan 20 创建vlan(也可进入vlan 20) port e1/0/1 to e1/0/5 把端口1-5放入VLAN 20 中 disp vlan 20 显示vlan里的端口20 int e1/0/24 进入端口24 port access vlan 20 把当前端口放入vlan 20 undo port e1/0/10 表示删除当前VLAN端口10 disp curr 显示当前配置
配置交换机支持TELNET¶
system 进入系统视图 sysname 交换机命名 int vlan 1 进入VLAN 1 ip address 192.168.3.100 255.255.255.0 配置IP地址 user-int vty 0 4 进入虚拟终端 authentication-mode password (aut password) 设置口令模式 set authentication password simple 222 (set aut pass sim 222) 设置口令 user privilege level 3(use priv lev 3) 配置用户级别 disp current-configuration (disp cur) 查看当前配置 disp ip int 查看交换机VLAN IP配置 删除配置必须退到用户模式 reset saved-configuration(reset saved) 删除配置 reboot 重启交换机
跨交换机VLAN的通讯¶
在sw1上: vlan 10 建立VLAN 10 int e1/0/5 进入端口5 port access vlan 10 把端口5加入vlan 10 vlan 20 建立VLAN 20 int e1/0/15 进入端口15 port access vlan 20 把端口15加入VLAN 20 int e1/0/24 进入端口24 port link-type trunk 把24端口设为TRUNK端口 port trunk permit vlan all 同上 在SW2上: vlan 10 建立VLAN 10 int e1/0/20 进入端口20 port access vlan 10 把端口20放入VLAN 10 int e1/0/24 进入端口24 port link-type trunk 把24端口设为TRUNK端口 port trunk permit vlan all (port trunk permit vlan 10 只能为vlan 10使用)24端口为所有VLAN使用 disp int e1/0/24 查看端口24是否为TRUNK undo port trunk permit vlan all 删除该句
路由的配置命令¶
system 进入系统模式 sysname 命名 int e1/0 进入端口 ip address 192.168.3.100 255.255.255.0 设置IP undo shutdown 打开端口 disp ip int e1/0 查看IP接口情况 disp ip int brief 查看IP接口情况 user-int vty 0 4 进入口令模式 authentication-mode password(auth pass) 进入口令模式 set authentication password simple 222 37 设置口令 user privilege level 3 进入3级特权 save 保存配置 reset saved-configuration 删除配置(用户模式下运行) undo shutdown 配置远程登陆密码 int e1/4 ip route 192.168.3.0(目标网段) 255.255.255.0 192.168.12.1(下一跳:下一路由器的接口) 静态路由 ip route 0.0.0.0 0.0.0.0 192.168.12.1 默认路由 disp ip rout 显示路由列表 华3C AR-18 E1/0(lan1-lan4) E2/0(wan0) E3/0(WAN1) 路由器连接使用直通线。wan0接wan0或wan1接wan1 计算机的网关应设为路由器的接口地址。
三层交换机配置VLAN-VLAN通讯¶
sw1(三层交换机): system 进入视图 sysname 命名 vlan 10 建立VLAN 10 vlan 20 建立VLAN 20 int e1/0/20 进入端口20 port access vlan 10 把端口20放入VLAN 10 int e1/0/24 进入24端口 port link-type trunk 把24端口设为TRUNK端口 port trunk permit vlan all (port trunk permit vlan 10 只能为vlan 10使用)24端口为所有VLAN使用 sw2: vlan 10 int e1/0/5 port access vlan 10 int e1/0/24 port link-type trunk 把24端口设为TRUNK端口 port trunk permit vlan all (port trunk permit vlan 10 只能为vlan 10使用)24端口为所有VLAN使用 sw1(三层交换机): int vlan 10 创建虚拟接口VLAN 10 ip address 192.168.10.254 255.255.255.0 设置虚拟接口VLAN 10的地址 int vlan 20 创建虚拟接口VLAN 20 ip address 192.168.20.254 255.255.255.0 设置虚拟接口IP VLAN 20的地址 注意:vlan 10里的计算机的网关设为 192.168.10.254 vlan 20里的计算机的网关设为 192.168.20.254
动态路由RIP¶
R1: int e1/0 进入e1/0端口 ip address 192.168.3.1 255.255.255.0 设置IP int e2/0 进入e2/0端口 ip adress 192.168.5.1 255.255.255.0 设置IP rip 设置动态路由 network 192.168.5.0 定义IP network 192.168.3.0 定义IP disp ip rout 查看路由接口 R2: int e1/0 进入e1/0端口 ip address 192.168.4.1 255.255.255.0 设置IP int e2/0 进入e2/0端口 ip adress 192.168.5.2 255.255.255.0 设置IP rip 设置动态路由 network 192.168.5.0 定义IP network 192.168.4.0 定义IP disp ip rout 查看路由接口 (注意:两台PC机的网关设置PC1 IP:192.168.3.1 PC2 IP:192.168.4.1)
IP访问列表¶
int e1/0 ip address 192.168.3.1 255.255.255.0 int e2/0 ip address 192.168.1.1 255.255.255.0 int e3/0 ip address 192.168.2.1 255.255.255.0 acl number 2001 (2001-2999属于基本的访问列表) rule 1 deny source 192.168.1.0 0.0.0.255 (拒绝地址192.168.1.0网段的数据通过) rule 2 permit source 192.168.3.0 0.0.0.255(允许地址192.168.3.0网段的数据通过) 以下是把访问控制列表在接口下应用: firewall enable firewall default permit int e3/0 firewall packet-filter 2001 outbound disp acl 2001 显示信息 undo acl number 2001 删除2001控制列表 扩展访问控制列表 acl number 3001 rule deny tcp source 192.168.3.0 0.0.0.255 destination 192.168.2.0 0.0.0.255 destination-port eq ftp 必须在r-acl-adv-3001下才能执行 rule permit ip source an destination any (rule permit ip) int e3/0 firewall enable 开启防火墙 firewall packet-filter 3001 inbound 必须在端口E3/0下才能执行
命令的标准访问IP列表(三层交换机)¶
允许A组机器访问服务器内资料,不允许访问B组机器(服务器没有限制) sys vlan 10 name server vlan 20 name teacher vlan 30 name student int e1/0/5 port access vlan 10 int e1/0/10 port access vlan 20 int e1/0/15 port access vlan 30 int vlan 10 ip address 192.168.10.1 255.255.255.0 undo sh int vlan 20 ip address 192.168.20.1 255.255.255.0 int vlan 30 ip address 192.168.30.1 255.255.255.0 acl number 2001 rule 1 deny source 192.168.30.0 0.0.0.255 rule 2 permit source any disp acl 2001 查看2001列表 int e1/0/10 port access vlan 20 packet-filter outbound ip-group 2001 rule 1 出口
允许A机器访问B机器的FTP但不允许访问WWW,C机器没有任何限制。¶
vlan 10 vlan 20 vlan 30 int e1/0/5 port access vlan 10 int e1/0/10 port access vlan 20 int e1/0/15 port access vlan 30 int vlan 10 ip address 192.168.10.1 255.255.255.0 undo sh int vlan 20 ip address 192.168.20.1 255.255.255.0 int vlan 30 ip address 192.168.30.1 255.255.255.0 acl number 3001 rule 1 deny tcp source 192.168.30.0 0.0.0.255 destination 192.168.10.0 0.0.0.255 destination-port eq www int e1/0/15 packet-filter inbound ip-group 3001 rule 1 进口
NAT地址转换(单一静态一对一地址转换)¶
R1: sys sysname R1 int e1/0 ip address 192.168.3.1 255.255.255.0 int e2/0 ip address 192.1.1.1 255.255.255.0 R2: sys sysname R2 int e2/0 ip address 192.1.1.2 255.255.255.0 int e1/0 ip address 10.80.1.1 255.255.255.0 回到R1: nat static 192.168.3.1 192.1.1.1 int e2/0 nat outbound static ip route 0.0.0.0 0.0.0.0 192.1.1.2
NAT内部整网段地址转换¶
R1: sys sysname R1 int e1/0 ip address 192.168.3.1 255.255.255.0 int e2/0 ip address 192.1.1.1 255.255.255.0 acl number 2008 rule 0 permit source 192.168.3.0 0.0.0.255 rule 1 deny quit int e2/0 nat outbound 2008 quit ip route-static 0.0.0.0 0.0.0.0 192.1.1.2 preference 60
SNMP配置¶
snmp-agent /使能snmp服务/
snmp-agent local-engineid 000007DB7F000001000049DD /系统自动生成,无需配置/
snmp-agent community read public /设置读团体名:public/
snmp-agent community write private /设置写团体名:private/
snmp-agent sys-info contact Mr.Wang-Tel:3306 /设置联系方式/
snmp-agent sys-info location 3rd-floor /设置设备位置/
snmp-agent sys-info version v1 v3 /配置snmp版本允许V1(默认只允许v3)/
snmp-agent target-host trap address udp-domain 129.102.149.23 udp-port 5000 par
ams securityname public
/允许向网管工作站(NMS)129.102.149.23发送Trap报文,使用的团体名为public/
问题记录¶
Error: Please renew the default configurations¶
重新配置的时候, 出现这种报错, 需要一层层删除配置, 直到恢复默认配置
配置的时候为
port link-type access
port default vlan 4
所以如果出现这种错误,在这里就需要从后往前删除,即
undo port default vlan 4
undo port link-type # 或者直接使用 port link-type trunk 修改
到这里以后 才可以重新更改端口的模式。
或者可以直接清空某个接口的所有配置
clear configuration interface GigabitEthernet 0/0/2
再如
interface GigabitEthernet5/0/15
port link-type trunk
port trunk allow-pass vlan 2 to 4094
先执行undo port trunk allow-pass vlan 2 to 4094
然后就可以通过port link-type access
将端口修改为 access
snmpwalk使用¶
Ubuntu如果没有安装snmp没有这个命令
apt-get install snmp
类RHEL系统
yum install net-snmp-utils
snmpwalk -v 2c -c public localhost .1.3.6.1.4.1.2011.6.3.4.1.2
snmpwalk -v 2c -c xxx-public IP .1.3.6.1.2.1.2.2.1.2
public为团体名,需要在设备上配置
AC6005¶
配置AP¶
修改名字
<AC6005-2>sys
Enter system view, return user view with Ctrl+Z.
[AC6005-2]wlan
[AC6005-2-wlan-view]ap ?
INTEGER<0-4095> AP ID
data-collection Data-collection
id AP ID
modify Modify AP
static-ip Static IP
[AC6005-2-wlan-view]ap id 21
[AC6005-2-wlan-ap-21]ap-sysname 11F-3
配置在射频上绑定射频模板和服务集¶
配置更改后,及时生效
[AC6005-2-wlan-ap-21]ap 21 radio 0
[AC6005-2-wlan-radio-21/1]undo service-set id 6
[AC6005-2-wlan-radio-21/1]undo service-set id 7
[AC6005-2-wlan-radio-21/1]service-set id 1
[AC6005-2-wlan-radio-21/1]service-set id 2
[AC6005-2-wlan-radio-21/1]service-set id 3
[AC6005-2-wlan-radio-21/1]service-set id 4
ap 1 radio 0
radio-profile id 0
channel 20MHz 11
power-level 30
service-set id 0 wlan 1
service-set id 1 wlan 2
service-set id 2 wlan 3
service-set id 3 wlan 4
service-set id 4 wlan 5
service-set id 5 wlan 6
service-set id 6 wlan 7
service-set id 7 wlan 8
service-set id 8 wlan 9
service-set id 9 wlan 10
service-set id 11 wlan 11
ap 1 radio 1
radio-profile id 0
channel 40MHz-plus 157
power-level 30
service-set id 0 wlan 1
service-set id 1 wlan 2
service-set id 2 wlan 3
service-set id 3 wlan 4
service-set id 4 wlan 5
service-set id 5 wlan 6
service-set id 6 wlan 7
service-set id 7 wlan 8
service-set id 8 wlan 9
service-set id 9 wlan 10
service-set id 11 wlan 11
AC开启SNMP¶
使用 v2c
执行 1234
步即可
使用如下命令确认设置
display current-configuration | inc snmp
- 执行命令
system-view
,进入系统视图。 - (可选)执行命令
snmp-agent
,启动SNMP Agent服务。
缺省情况下,没有启动SNMP Agent服务。执行任意snmp-agent的配置命令(无论是否含参数)都可以触发SNMP Agent服务启动,故该步骤可选。
- 执行命令
snmp-agent sys-info version v2c
,配置SNMP的协议版本为SNMPv2c。缺省情况下,使能SNMPv3。
说明:
使用SNMPv2c存在安全风险,建议您使用SNMPv3管理设备。
- 执行命令
snmp-agent community { read | write } { community-name | cipher community-name }
,配置设备的读写团体名。
配置设备的读写团体名之后,使用该团体名的网管拥有Viewdefault视图(即1.3.6.1和1.2.840.10006.300.43视图)的权限。如果需要修改该网管的访问权限,请参考(可选)限制网管对设备的管理权限进行配置。
说明:
NMS上配置的团体名需和Agent上配置的团体名保持一致,否则NMS将无法访问Agent。
- 执行命令
snmp-agent target-host trap-paramsname paramsname v2c securityname securityname [ binding-private-value ] [ private-netmanager ]
,配置设备发送Trap报文的参数信息。 - 执行命令
snmp-agent target-host trap-hostname hostname address { ipv4-addr [ udp-port udp-portid ] | ipv6 ipv6-addr [ udp-port udp-portid ] } trap-paramsname paramsname
,配置设备发送告警和错误码的目的主机。
请参考下面的说明对参数进行选取: 目的UDP端口号缺省是162,如果有特殊需求(比如避免知名端口号被攻击或者配置了端口镜像),可以配置udp-port将UDP端口号更改为非知名端口号,保证网管和被管理设备的正常通信。
- (可选)执行命令snmp-agent sys-info { contact contact | location location },配置设备管理员的联系方式和位置。
缺省情况下,系统维护联系信息为“R&D Shenzhen, Huawei Technologies Co.,Ltd.”,物理位置信息为“Shenzhen China”。 当网管管理多个设备时,为了方便网管管理员记录设备管理员的联系方式和位置,在设备异常时快速联系设备管理员进行故障排除和定位,可配置该功能。
如果需要同时配置设备管理员的联系方式和位置,请执行两次该命令分别配置管理员的联系方式和位置。
华为交换机配置¶
基础配置¶
如何使用命令行¶
用户试图
从终端登录即进入用户试图,屏幕显示 <S5700-28C-EI_1>
|
在用户视图下,用户可以完成查看运行状态和统计信息等功能.
系统视图
在用户视图下,输入命令system-view后回车,进入系统视图。 在系统视图下,用户可以配置系统参数以及通过该视图进入其他的功能配置视图。
接口视图
使用interface命令并指定接口类型及接口编号可以进入相应的接口视图。
说明: X/Y/Z为需要配置的接口的编号,分别对应“堆叠ID/子卡号/接口序号”。 上述举例中GigabitEthernet接口仅为示意。
配置接口参数的视图称为接口视图。在该视图下可以配置接口相关的物理属性、链路层特性及IP地址等重要参数。
路由协议视图
在系统视图下,使用路由协议进程运行命令可以进入到相应的路由协议视图。
路由协议的大部分参数是在相应的路由协议视图下进行配置的。例如IS-IS协议视图、OSPF协议视图、RIP协议视图。
# 用户视图
<S5700-28C-EI_1>
# 进入系统视图
<HUAWEI> system-view
Enter system view, return user view with Ctrl+Z.
[HUAWEI]
# 进入接口视图,指定接口类型及接口编号
[HUAWEI] interface gigabitethernet X/Y/Z
[HUAWEI-GigabitEthernetX/Y/Z]
# 进入路由协议视图
[HUAWEI] isis
[HUAWEI-isis-1]
退出命令行视图
执行quit命令,即可从当前视图退出至上一层视图。
例如,执行quit命令从IS-IS视图退回到系统视图,再执行quit命令退回到用户视图。
[HUAWEI-isis-1] quit
[HUAWEI] quit
<HUAWEI>
如果需要从IS-IS视图直接退回到用户视图,则可以在键盘上键入组合键<Ctrl+Z>或者执行return命令。
# 使用组合键<Ctrl+Z>直接退回到用户视图。
[HUAWEI-isis-1] #键入<Ctrl+Z>
<HUAWEI>
# 执行return命令直接退回到用户视图。
[HUAWEI-isis-1] return
<HUAWEI>
系统将命令进行分级管理,各个视图下的每条命令都有指定的级别。设备管理员可以根据用户需要重新设置命令的级别,以实现低级别用户可以使用部分高级别命令的需求,或者将命令的级别提高,增加设备的安全性。
编辑命令行时的操作技巧
不完整关键字输入
设备支持不完整关键字输入,即在当前视图下,当输入的字符能够匹配唯一的关键字时,可以不必输入完整的关键字。该功能提供了一种快捷的输入方式,有助于提高操作效率。
比如display current-configuration
命令,可以输入d cu 、di cu
或dis cu
等都可以执行此命令,但不能输入 d c
或 dis c
等,因为以 d c、dis c
开头的命令不唯一。
Tab补全
在线帮助通过键入“?”来获取,在命令行输入过程中,用户可以随时键入“?”以获得在线帮助。
完全帮助
在任一命令视图下,键入“?”获取该命令视图下所有的命令及其简单描述。举例如下:
<HUAWEI> ?
User view commands:
backup Backup electronic elabel
cd Change current directory
check Check information
clear Clear information
clock Specify the system clock
compare Compare function
...
键入一条命令的部分关键字,后接以空格分隔的“?”,如果该位置为关键字,则列出全部关键字及其简单描述。举例如下:
<HUAWEI> system-view
[HUAWEI] user-interface vty 0 4
[HUAWEI-ui-vty0-4] authentication-mode ?
aaa AAA authentication
none Login without checking
password Authentication through the password of a user terminal interface
[HUAWEI-ui-vty0-4] authentication-mode aaa ?
<cr>
[HUAWEI-ui-vty0-4] authentication-mode aaa
其中“aaa”和“password”是关键字,“AAA authentication”和“Authentication through the password of a user terminal interface”是对关键字的描述。
“<cr>”表示该位置没有关键字或参数,直接键入回车即可执行。
键入一条命令的部分关键字,后接以空格分隔的“?”,如果该位置为参数,则列出有关的参数名和参数描述。举例如下:
<HUAWEI> system-view
[HUAWEI] ftp timeout ?
INTEGER<1-35791> The value of FTP timeout, the default value is 30 minutes
[HUAWEI] ftp timeout 35 ?
<cr>
[HUAWEI] ftp timeout 35
其中,“INTEGER<1-35791>”是参数取值的说明,“The value of FTP timeout, the default value is 30 minutes”是对参数作用的简单描述。
部分帮助
当用户输入命令时,如果只记得此命令关键字的开头一个或几个字符,可以使用命令行的部分帮助获取以该字符串开头的所有关键字的提示。下面给出几种部分帮助的实例供参考: 键入一字符串,其后紧接“?”,列出以该字符串开头的所有关键字。举例如下:
<HUAWEI> d?
debugging delete
dir display
<HUAWEI> d
键入一条命令,后接一字符串紧接“?”,列出命令以该字符串开头的所有关键字。举例如下:
<HUAWEI> display b?
bootrom bpdu
bpdu-tunnel bridge
buffer
输入命令的某个关键字的前几个字母,按下键,可以显示出完整的关键字,前提是这几个字母可以唯一标示出该关键字,否则,连续按下键,可出现不同的关键字,用户可以从中选择所需要的关键字。
在命令前加undo关键字,即为undo命令行.undo命令行一般用来恢复缺省情况,禁用某个功能或者删除某项配置.几乎每条配置命令都有对应的undo命令行.
示例:
<HUAWEI> system-view
[HUAWEI] sysname Server
[Server] undo sysname
[HUAWEI]
display history-command
访问上一条历史命令 上光标键或者<Ctrl+P> 如果还有更早的历史命令,则取出上一条历史命令,否则响铃警告。
访问下一条历史命令 下光标键或者<Ctrl+N>
查看命令行显示信息¶
在完成一系列配置后,可以执行相应的display命令查看设备的配置信息和运行信息。 例如,在完成FTP服务器的各项配置后,可以执行命令display ftp-server,查看当前FTP服务器的各项参数。display命令的用法和功能可详见各配置指南手册中对应特性的“检查配置结果”。
同时,系统支持查看当前生效的配置信息和当前视图下的配置信息,命令如下:
查看当前生效的配置信息:
display current-configuration
对于正在生效的缺省配置,此命令不予显示。
查看当前视图下的配置信息:
display this
对于某些正在生效的配置参数,如果与缺省工作参数相同,则不显示;对于某些参数,虽然用户已经配置,但如果这些参数所在的功能没有生效,则不予显示。如果还需要显示当前视图下未被修改的缺省配置,可以执行命令
display this include-default
进行查看。
在执行display命令查看显示信息时,可以使用正则表达式(即指定显示规则)来过滤显示信息,过滤命令行显示信息可以帮助用户迅速查找到所需要的信息。
过滤命令行显示信息的使用方法有两种:
在命令中指定过滤方式:在命令行中通过输入begin、exclude或include关键字加正则表达式的方式来过滤显示。
在分屏显示时指定过滤方式:在分屏显示时,使用“/”、“-”或“+”符号加正则表达式的方式,可以对还未显示的信息进行过滤显示。其中,“/”等同关键字begin;“-”等同关键字exclude;“+”等同关键字include。
不是所有的display命令均支持管道符
|
begin regular-expression:输出以匹配指定正则表达式的行开始的所有行。 即过滤掉所有待输出字符串,直到出现指定的字符串(此字符串区分大小写)为止,其后的所有字符串都会显示到界面上。|
exclude regular-expression:输出不匹配指定正则表达式的所有行。 即待输出的字符串中没有包含指定的字符串(此字符串区分大小写),则会显示到界面上;否则过滤不显示。|
include regular-expression:只输出匹配指定正则表达式的所有行。 即待输出的字符串中如果包含指定的字符串(此字符串区分大小写),则会显示到界面上;否则过滤不显示。
首次登陆系统¶
当用户需要为第一次上电的设备进行配置时,可以通过Console口或MiniUSB口登录设备。 设备提供一个Console口和一个MiniUSB口。用户终端的串行口可以与设备Console口直接连接或者将用户终端的USB口与设备MiniUSB口直接连接,实现对设备的本地配置。
说明:
- 在通过MiniUSB口登录设备前,需要在用户终端安装MiniUSB口的驱动程序。
- MiniUSB口和Console口同时接入时,只有MiniUSB口可以使用。
os¶
centos-redhat¶
CentOS-6内核优化¶
CentOS 6.7 内核优化¶
net.ipv4.tcp_fin_timeout = 2
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.ip_local_port_range = 4000 65000
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_max_tw_buckets = 36000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_orphans = 16384
防火墙优化¶
net.nf_conntrack_max = 25000000
net.netfilter.nf_conntrack_max = 25000000
net.netfilter.nf_conntrack_tcp_timeout_established = 180
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.ipv4.tcp_max_syn_backlog = 65536
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
让改动配置立即生效
/sbin/sysctl -p
修改ulimit
1.vi /etc/security/limits.conf
*soft nproc 65535
*hard nproc 65535
*soft nofile 65535
*hard nofile 65535
2. vi /etc/security/limits.d/90-nproc.conf
* softnproc65535
CentOS-6内核优化¶
CentOS 6.7 内核优化¶
net.ipv4.tcp_fin_timeout = 2
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.ip_local_port_range = 4000 65000
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_max_tw_buckets = 36000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_orphans = 16384
防火墙优化¶
net.nf_conntrack_max = 25000000
net.netfilter.nf_conntrack_max = 25000000
net.netfilter.nf_conntrack_tcp_timeout_established = 180
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.ipv4.tcp_max_syn_backlog = 65536
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
让改动配置立即生效
/sbin/sysctl -p
修改ulimit
1.vi /etc/security/limits.conf
*soft nproc 65535
*hard nproc 65535
*soft nofile 65535
*hard nofile 65535
2. vi /etc/security/limits.d/90-nproc.conf
* softnproc65535
CentOS7¶
使用cobbler¶
使用cobbler自动化安装,配置内核参数net.ifnames=0 biosdevname=0,安装即可
CentOS-7 全新安装,直接修改网卡为eth0¶
光盘安装的时候,在安装界面做如下操作:
点击Tab,打开kernel启动选项后,增加net.ifnames=0 biosdevname=0,如下图所示。

CentOS7-1

CentOS7-2
CentOS-7 修改网卡为eth0(已安装-修改)¶
[root@centos7 ~]# cd /etc/sysconfig/network-scripts/ #进入网卡目录
[root@centos7 network-scripts]# mv ifcfg-eno16777728 ifcfg-eth0 #重命名网卡名称
[root@centos7 network-scripts]# cat ifcfg-eth0 #编辑网卡信息
TYPE=Ethernet
BOOTPROTO=static
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no
NAME=eth0 #name修改为eth0
ONBOOT=yes
IPADDR=192.168.56.12
NETMASK=255.255.255.0
GATEWAY=192.168.56.2
DNS1=192.168.56.2
[root@centos7 ~]# cat /etc/sysconfig/grub #编辑内核信息,添加 net.ifnames=0 biosdevname=0 字段的
GRUB_TIMEOUT=5
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto rhgb **net.ifnames=0 biosdevname=0** quiet"
GRUB_DISABLE_RECOVERY="true"
[root@centos7 ~]# **grub2-mkconfig -o /boot/grub2/grub.cfg #生成启动菜单**
Generating
grub configuration file ...
Found
linux image: /boot/vmlinuz-3.10.0-229.el7.x86_64
Found
initrd image: /boot/initramfs-3.10.0-229.el7.x86_64.img
Found
linux image: /boot/vmlinuz-0-rescue-1100f7e6c97d4afaad2e396403ba7f61
Found
initrd image: /boot/initramfs-0-rescue-1100f7e6c97d4afaad2e396403ba7f61.img
Done
[root@centos7 ~]# reboot #必须重启系统生效
[root@centos7 ~]# yum install net-tools #默认centos7不支持ifconfig 需要安装net-tools包
[root@centos7 ~]# ifconfig eth0 #再次查看网卡信息
eth0:
flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.56.12 netmask 255.255.255.0 broadcast 192.168.56.255
inet6 fe80::20c:29ff:fe5c:7bb1 prefixlen 64
scopeid 0x20<link>
ether 00:0c:29:5c:7b:b1 txqueuelen 1000 (Ethernet)
RX packets 152 bytes 14503 (14.1 KiB)
RX errors 0 dropped 0
overruns 0 frame 0
TX packets 98 bytes 14402 (14.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0
collisions 0
5、接着配置规则,根据Centos 官方WIKI的FAQ中得知,如果你有多个接口,并且想要控制其设备名,而不是让内核以它自己的方式命名
vim /etc/udev/rules.d/70-persistent-ipoib.rules
# ACTION=="add", SUBSYSTEM=="net", DRIVERS=="?*", ATTR{type}=="32", ATTR{address}=="?*00:02:c9:03:00:35:73:f2", NAME="eth0"
#修改只需要修改最后面的NAME名称的设备名称和你配置名称一致即可,前面的#号去掉,即可,上面这种方法,同样适用于,所有的克隆的虚拟主机,需要注意,克隆的主机前面的这个MAC地址不能一样需要修改
最后,修改网卡的配置完成了
CentOS 7 1511¶
网卡设置¶
[root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno16777736
TYPE=Ethernet
BOOTPROTO=none
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_PEERDNS=yes
IPV6_PEERROUTES=yes
IPV6_FAILURE_FATAL=no
NAME=eno16777736
UUID=cea75fb8-ffa7-4641-898f-b942f99b1736
DEVICE=eno16777736
ONBOOT=yes
IPADDR0=10.0.0.158
GATEWAY0=10.0.0.2
DNS1=223.5.5.5
重启网卡,添加epel源
systemctl restart network
yum install wget -y
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
CentOS 6、7之间的差异¶
启动方式的差异¶
1. 系统第1个进程(pid=1)为init
2. init进程是所有进程的祖先,kill不了,只能自杀
3. 大多数Linux发行版的init系统是和SystemV相兼容的,被称为sysvinit
4. 代表系统:CentOS6
应用于服务器和PC机的时代。
SysV init运行非常良好,概念简单清晰。
它主要依赖于Shell脚本,这就决定了它的最大弱点:启动太慢
移动平台,需要启动快的系统
因竞争对手太强,被淘汰 代表系统:Ubuntu14,从Ubuntu15开始使用systemd
- 新系统都会采用的技术(RedHat7,CentOS7,Ubuntu15等);
- 设计目标是客服sysvinit固有的缺点,提高系统的启动速度;
- 和Sysvinit兼容,降低迁移成本;
- 最主要有点:并行启动。
三种启动技术对比

start-technology
ABCD四个任务有依赖关系
init:总时间T1+T2+T3+T4+T5+T6
upstart:总时间T1+T2+T3,启动速度加快,但是有依赖关系的服务还是必须先启动。
systemd:总时间T,即使有依赖关系的服务,也能并发启动。
并发启动原理之一:解决socket依赖
并发启动原理之二:解决D-Bus依赖
并发启动原理之三:解决文件系统依赖
网卡名称区别¶
传统上,Linux的网络接口命名为eth0、eth1,但这些名称并不一定符合实际的硬件插槽等,这可能会导致不同的网络配置错误。
基于MAC地址的udev规则在虚拟化的环境中并不有用,这里的MAC地址如端口数量一样无常。
CentOS6/REHL6,引入了一致可预测的网络设备命名网络接口的方法。这些特性可以唯一确定网络接口的名称以使定位和区分设备更容易,并且在这样一种方式下,无论是否重启机器、过了多少时间、或者改变硬件,其名字都是持久不变的。
然而这种命名规则并不是默认开启的。
CentOS7/REHL7,这种可预见的命名规则变成了默认。根据这一规则,接口名称被自动基于固件,拓扑结构和位置信息来确定。
现在,即使添加或移除网络设备,接口名称仍然保持固定,而无需重新枚举,和坏掉的硬件可以无缝替换。
网络配置相关命令区别¶
ip: yum -y install iproute
centos7主推使用ip命令。
ifconfig: yum -y install net-tools
setup: yum -y install setuptool 废弃命令,只是一个图形工具。依赖于其他几个服务。
我们需要安装用到的网络服务,防火墙,系统服务等。
nmtui:替代setup命令
主机名和系统版本号配置文件区别¶
主机名的配置文件变了
/etc/hostname
查看系统版本号
/etc/redhat-release
所有支持systemd系统的统一发行版名称和版本号文件。
[root@centos7 ~]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
运行级别Runlevel区别¶
runlevel
[root@centos7 ~]# cat /etc/inittab
# inittab is no longer used when using systemd.
#
# ADDING CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
#
# Ctrl-Alt-Delete is handled by /usr/lib/systemd/system/ctrl-alt-del.target
#
# systemd uses 'targets' instead of runlevels. By default, there are two main targets:
#
# multi-user.target: analogous to runlevel 3
# graphical.target: analogous to runlevel 5
#
# To view current default target, run:
# systemctl get-default
#
# To set a default target, run:
# systemctl set-default TARGET.target
#
systemctl 查看、设置启动级别
[root@centos7 ~]# systemctl get-default
multi-user.target
[root@centos7 ~]# systemctl set-default graphical.target
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /usr/lib/systemd/system/graphical.target.
[root@centos7 ~]# systemctl get-default
graphical.target
[root@centos7 ~]# ll /etc/systemd/system/default.target ## 本质为软链接
lrwxrwxrwx. 1 root root 40 Dec 10 00:50 /etc/systemd/system/default.target -> /usr/lib/systemd/system/graphical.target
[root@centos7 ~]# systemctl set-default multi-user.target
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /usr/lib/systemd/system/multi-user.target.
runlevel实质
[root@centos7 ~]# ls -lh /usr/lib/systemd/system/runlevel*.target
lrwxrwxrwx. 1 root root 15 Dec 10 00:29 /usr/lib/systemd/system/runlevel0.target -> poweroff.target
lrwxrwxrwx. 1 root root 13 Dec 10 00:29 /usr/lib/systemd/system/runlevel1.target -> rescue.target
lrwxrwxrwx. 1 root root 17 Dec 10 00:29 /usr/lib/systemd/system/runlevel2.target -> multi-user.target
lrwxrwxrwx. 1 root root 17 Dec 10 00:29 /usr/lib/systemd/system/runlevel3.target -> multi-user.target
lrwxrwxrwx. 1 root root 17 Dec 10 00:29 /usr/lib/systemd/system/runlevel4.target -> multi-user.target
lrwxrwxrwx. 1 root root 16 Dec 10 00:29 /usr/lib/systemd/system/runlevel5.target -> graphical.target
lrwxrwxrwx. 1 root root 13 Dec 10 00:29 /usr/lib/systemd/system/runlevel6.target -> reboot.target
所有可用的单元文件存放在/usr/lib/systemd/system 和/etc/systemd/system/目录(后者优先级更高)
[root@centos7 ~]# ls -lh /usr/lib/systemd/system ## 类似/etc/init.d 目录
[root@centos7 system]# cd /usr/lib/systemd/system/
[root@centos7 system]# cat crond.service
[Unit]
Description=Command Scheduler
After=auditd.service systemd-user-sessions.service time-sync.target
[Service]
EnvironmentFile=/etc/sysconfig/crond
ExecStart=/usr/sbin/crond -n $CRONDARGS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
[Install]
WantedBy=multi-user.target
管理服务区别¶
- chkconfig
- service
- /etc/init.d/
- systemctl:融合service和chkconfig的功能于一体,兼容SysV和LSB的启动脚本,而且能够在进程启动过程中更有效地引导加载服务。
[root@centos7 ~]# ll /etc/systemd/system
total 4
drwxr-xr-x. 2 root root 54 Dec 10 00:30 basic.target.wants
lrwxrwxrwx. 1 root root 41 Dec 10 00:29 dbus-org.fedoraproject.FirewallD1.service -> /usr/lib/systemd/system/firewalld.service
lrwxrwxrwx. 1 root root 46 Dec 10 00:29 dbus-org.freedesktop.NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
lrwxrwxrwx. 1 root root 57 Dec 10 00:29 dbus-org.freedesktop.nm-dispatcher.service -> /usr/lib/systemd/system/NetworkManager-dispatcher.service
lrwxrwxrwx. 1 root root 41 Dec 10 00:52 default.target -> /usr/lib/systemd/system/multi-user.target
drwxr-xr-x. 2 root root 85 Dec 10 00:29 default.target.wants
drwxr-xr-x. 2 root root 31 Dec 10 00:29 getty.target.wants
drwxr-xr-x. 2 root root 4096 Dec 10 00:35 multi-user.target.wants
drwxr-xr-x. 2 root root 43 Dec 10 00:29 system-update.target.wants
[root@centos7 ~]#
Sysvinit、systemd命令区别
systemctl 命令补全:bash-completion
[root@centos7 ~]# rpm -ivh http://mirrors.aliyun.com/centos/7/os/x86_64/Packages/bash-completion-2.1-6.el7.noarch.rpm
# 安装之后,需要退出重新登录
使用systemctl 命令使用补全查看服务状态
[root@centos7 ~]# systemctl status crond.service
● crond.service - Command Scheduler
Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2016-12-10 00:39:29 CST; 24min ago
Main PID: 754 (crond)
CGroup: /system.slice/crond.service
└─754 /usr/sbin/crond -n
Dec 10 00:39:29 centos7 systemd[1]: Started Command Scheduler.
Dec 10 00:39:29 centos7 systemd[1]: Starting Command Scheduler...
Dec 10 00:39:30 centos7 crond[754]: (CRON) INFO (RANDOM_DELAY will ...)
Dec 10 00:39:30 centos7 crond[754]: (CRON) INFO (running with inoti...)
Hint: Some lines were ellipsized, use -l to show in full.
查看系统启动项
[root@centos7 system]# systemctl list-unit-files |grep enabled
auditd.service enabled
crond.service enabled
dbus-org.fedoraproject.FirewallD1.service enabled
dbus-org.freedesktop.NetworkManager.service enabled
dbus-org.freedesktop.nm-dispatcher.service enabled
firewalld.service enabled
getty@.service enabled
irqbalance.service enabled
microcode.service enabled
NetworkManager-dispatcher.service enabled
NetworkManager.service enabled
postfix.service enabled
rsyslog.service enabled
sshd.service enabled
systemd-readahead-collect.service enabled
systemd-readahead-drop.service enabled
systemd-readahead-replay.service enabled
tuned.service enabled
default.target enabled
multi-user.target enabled
remote-fs.target enabled
CentOS-RedHat¶
chkconfig管理脚本写法¶
vim /etc/init.d/testd
#!/bin/sh
#
# chkconfig: 2345 68 73
#
echo ┏┛┻━━━━━━┛┓;sleep 0.5;
echo ┃ ┳┛ ┗┳ ┃;sleep 0.5;
echo ┃ ┳┛ ┗┳ ┃;sleep 0.5;
echo ┃ ┻ ┃;sleep 0.5;
echo ┃ ┃;sleep 0.5;
echo ┗━┓ ┏━┛;sleep 0.5;
echo ┃ 神 ┃ ;sleep 0.5;
echo ┃ 兽 ┃ ;sleep 0.5;
echo ┃ 来 ┃ ;sleep 0.5;
echo ┃ 袭 ┃;sleep 0.5;
echo ┃ 吼 ┗━━━┓;sleep 0.5;
echo ┃ ! ┣┓;sleep 0.5;
echo ┃ ┃;sleep 0.5;
echo ┗┓┓┏━┳┓┏┛;sleep 0.5;
echo ┃┫┫ ┃┫┫;sleep 0.5;
echo ┗┻┛ ┗┻┛;sleep 0.5;
for n in `seq 100` ;do echo loading......$n% ;sleep 1;done
# 添加权限
chmod +x /etc/init.d/testd
chkconfig --add testd
chkconfig testd on
chkconfig|grep test
仓库配置¶
cat yjj.repo
[yjj]
name=Server
baseurl=http://192.168.1.157/yum_data/
enabled=1
gpgcheck=0
问题记录¶
在centos7下安装libiconv-1.14.tar.gz时遇到如下错误:¶
./stdio.h:1010:1: error: 'gets' undeclared here (not in a function)
安装了错误的源¶
- 需要移除源
yum remove epel-release
- 删除缓存
/var/cache/yum/ ...
## Linux 查看OS系统块的大小(默认4096 byte) 2011-04-08¶
查看os系统块的大小
[root]# /sbin/tune2fs -l /dev/sda1
查看os系统页的大小
[oracle@skate-test ~]$ getconf PAGESIZE
4096
修改块的大小:
创建文件系统时,可以指定块的大小。如果将来在你的文件系统中是一些比较大的文件的话,
使用较大的块大小将得到较好的性能。将ext2文件系统的块大小调整为4096byte而不是缺省
的1024byte,可以减少文件碎片,加快fsck扫描的速度和文件删除以及读操作的速度。另
外,在ext2的文件系统中,为根目录保留了5%的空间,对一个大的文件系统,除非用作日志
文件,5%的比例有些过多。可以使用命令
mke2fs -b 4096 -m 1 /dev/hda6
将它改为1%并以块大小4096byte创建文件系统。
使用多大的块大小,需要根据你的系统综合考虑,如果系统用作邮件或者新闻服务器,使用较 大的块大小,虽然性能有所提高,但会造成磁盘空间较大的浪费。比如文件系统中的文件平均 大小为2145byte,如果使用4096byte的块大小,平均每一个文件就会浪费1951byte空间。如果 使用1024byte的块大小,平均每一个文件会浪费927byte空间。在性能和磁盘的代价上如何平衡 ,要看具体应用的需要。
coreos¶
coreos
mac¶
MacBook系统安装¶
MacBook安装win10双系统¶
keyword:
Mac双系统
BootCamp 6.x安装双系统不需要U盘
打开BootCamp按照步骤走就行,很简单
- 选择iso镜像
- 设置分区大小
- 下载Windows支持软件(根据网速快慢,可能会需要很长时间)
- 下载完成后会自动进行磁盘分区,拷贝Windows文件等等
- 格式化BOOTCAMP分区,正常流程安装Windows即可
- 安装OSXRESERVED下相关驱动,找到BootCamp,setup即可
- 重启之后,右下角Boot Camp控制面板可以设置触摸板等等
- 如需要删除win10系统,使用bootcamp即可
PyCharm¶
命令行使用pycharm打开PyCharm¶
添加别名到 .zshrc
或者类似 .bashrc
➜ ~ tail -1 .zshrc
alias pycharm="open -a pycharm"
重新打开终端, 或者使用source生效
在当前目录打开PyCharm
➜ records git:(master) ✗ pycharm .
PyCharm快捷键¶
cmd+d 在下一行复制本行的内容
opt + cmd + l 代码块对齐
Tab / Shift + Tab 缩进、不缩进当前行
cmd b 跳转到声明处(cmd加鼠标)
opt + 空格 显示符号代码 (esc退出窗口 回车进入代码)
cmd []光标之前/后的位置
opt + F7 find usage
cmd backspace 删除当前行
cmd +c 复制光标当前行,剪切同理
cmd + f 当前文件搜索(回车下一个 shift回车上一个)
cmd + r 当前文件替换
shift + cmd + f 全局搜索
shift + cmd + R 全局替换
cmd+o 搜索class
shift + cmd + o 搜索文件
opt + cmd + o 搜索符号(函数等)
cmd + l 指定行数跳转
shift enter 在行中的时候直接到下一行
cmd + 展开当前
cmd - 折叠当前
shift cmd + 展开所有
shift cmd - 折叠所有
cmd / 注释/取消注释一行(选中批量注释)
opt + cmd + / 批量注释(pycharm不生效)
ctr + tab 史上最NB的导航窗口(工程文件列表、文件结构列表、命令行模式、代码检查、VCS等,下面两个是可以被替换的)
alt + F12 打开命令行栏
cmd + F12 显示文件结构
cmd j 代码智能补全
alt + F1 定位编辑文件所在位置:
cmd + F6 更改变量
opt + cmd + t 指定代码被注释语句或者逻辑结构、函数包围
MacOS使用技巧¶
Mac_Fn键功能设置¶

MacOS-5
Mac 快捷键¶
Control-Command-F 全屏
Option-Command-D 显示或隐藏 Dock
Command-P 显示“打印”对话框
Option-Command-esc 强制退出

MacOS-6
Safari¶
shitt+comamnd+\:所有标签页,可配合左右键和单指左右滑动。对应手势操作:双指捏合、放开。
command+T:新建标签
command+L/option(alt)+command+F/fn+control+F5:定位地址栏/聚焦搜索栏
option(alt)+command+1:当前标签页显示个人收藏栏,并定位地址栏
command+enter:聚焦地址栏输入时,新标签页后台打开地址或搜索
shift+command+enter:聚焦地址栏输入时,新标签页前台打开地址或搜索
command+点按:新标签页后台打开链接
shift+command+点按:新标签页前台打开链接
control+tab/shift+command+]/shift+command+→:显示下一个标签页,其中箭头对空tab无效。
control+shift+tab/shift+command+[/shift+command+←:显示上一个标签页,其中箭头对空tab无效。
shift+command+H:当前标签页打开默认主页
command+[/]或单指左右滑动:当前标签页内前进/后退
command+W:关闭当前标签页
command+Z:恢复最近关闭的标签页
command+R:刷新重载
command+.:停止刷新重载
shift+command+R:进入阅读器模式
control+command+1: 显示书签边栏
command+D:添加到收藏夹(书签栏)
option(alt)+command+B:管理/编辑书签页
shift+command+N:新建书签文件夹
control+command+2: 显示阅读列表边栏
shift+command+D:将当前页添加到阅读列表
shift+点按:将链接添加到阅读列表
option(alt)+command+↑:阅读列表上一个项目
option(alt)+command+↓:阅读列表下一个项目
shift+command+B:收藏栏
shift+command+T:标签页栏
shift+command+L:侧边栏(书签/阅读)
option(alt)+command+L:下载
option(alt)+command+2:显示历史记录
command+F:查找
command+G/enter:查找下一个
shift+command+G/shift+enter:查找上一个
command++:放大
command+-:缩小
command+0:恢复默认
双指点触:智能缩放
command+,:偏好设置->扩展
Mac OS开启vim语法高亮/着色¶
用户家目录编辑.vimrc文件,或者全局修改/usr/share/vim/vimrc文件(区别在于当前用户生效,还是全局生效)
➜ ~ cat .vimrc
set ai " auto indenting
set ruler " show the cursor position
set hlsearch " highlight the last searched term
set history=1000 " keep 1000 lines of history
syntax on " syntax highlighting
filetype plugin on " use the file type plugins
如果想一直显示行号,可以添加如下语句
set nu
Mac下命令行工具¶
查看端口情况¶
netstat -nat |grep LISTEN
lsof -n -P -i TCP -s TCP:LISTEN
# lsof命令可以列出当前的所有网络情况, 此命令的解释如下:
# -n 表示主机以ip地址显示
# -P 表示端口以数字形式显示,默认为端口名称
# -i 意义较多,具体 man lsof, 主要是用来过滤lsof的输出结果
# -s 和 -i 配合使用,用于过滤输出
mtr¶
WinMTR
➜ ~ mtr -r www.baidu.com
Start: Thu Sep 28 11:28:39 2017
HOST: Y.local Loss% Snt Last Avg Best Wrst StDev
1.|-- 172.16.0.66 0.0% 10 4.2 3.5 1.6 6.3 1.2
2.|-- 172.16.0.66 0.0% 10 4.0 3.1 2.0 4.0 0.3
3.|-- ??? 100.0 10 0.0 0.0 0.0 0.0 0.0
4.|-- ??? 100.0 10 0.0 0.0 0.0 0.0 0.0
5.|-- 61.51.113.37 50.0% 10 14.2 10.4 2.9 28.1 10.9
6.|-- 124.65.56.137 80.0% 10 6.0 5.2 4.4 6.0 1.0
7.|-- 61.148.152.18 0.0% 10 14.8 16.8 3.9 42.7 14.1
8.|-- 123.125.248.42 40.0% 10 8.2 6.8 2.6 11.6 2.9
9.|-- ??? 100.0 10 0.0 0.0 0.0 0.0 0.0
10.|-- 61.135.169.121 0.0% 10 6.1 6.7 2.9 14.6 3.9
其中-c的说明是:–report-cycles COUNT 每秒发送数据包的数量
第三列:是显示的每个对应IP的丢包率
第四列:显示的最近一次的返回时延
第五列:是平均值 这个应该是发送ping包的平均时延
第六列:是最好或者说时延最短的
第七列:是最差或者说时延最常的
第八列:是标准偏差
参数说明
mtr -h 显示帮助信息
mtr -v 显示mtr版本
mtr -r 报告模式显示
mtr -s 用来指定ping数据包的大小
mtr -n no-dns不对IP地址做域名解析
mtr -a 来设置发送数据包的IP地址 这个对一个主机由多个IP地址是有用的
mtr -i 使用这个参数来设置ICMP返回之间的要求默认是1秒
mtr -4 使用IPv4
mtr -6 使用IPv6
dscl – Directory Service command line utility¶
创建用户
dscl . -create /Users/用户名
dscl . -create /Users/用户名 UserShell /bin/bash
dscl . -create /Users/用户名 RealName "真实用户名"
dscl . -create /Users/用户名 UniqueID "502"
dscl . -create /Users/用户名 PrimaryGroupID 80
dscl . -create /Users/用户名 NFSHomeDirectory /Users/用户名
dscl . -passwd /Users/用户名 "密码"
dscl . -append /Groups/admin GroupMembership 用户名
dscl . -delete /Users/用户名
Mac使用过程中问题记录¶
查看端口命令¶
netstat -AaLlnW
lsof -i -P | grep "LISTEN"
查看路由表¶
netstat -rn
默认网关
route -n get default OR route -n get www.yahoo.com
Mac压缩包解压后乱码¶
使用解压软件 The Unarchiver 来解压 Zip 格式的文件。
在压缩包上Get info
-> Open with
-> 选择The Unarchiver
->
并改变所有
重装/升级Mac系统之后发现capslock锁定大小写的键,失灵了,居然可以用来切换输入法了.¶
原因:
使用以下几种方法处理:
方式一:长按 caps lock 键,来切换大小写
方式二:caps lock + shift , 来切换大小写
方式三:在键盘设置里面把大小写切换语言勾点掉就好了,然后按大写就是大写,中文下按大就直接是英语或者拼音。
macOS Sierra 10.12 打开无法确认开发者身份的软件包方法¶
第三方软件安装之后,打开提示已损坏,打不开,可使用如下方法解决¶
1.10.12打不开无法确认开发者身份的软件包方法:
[Gatekeeper] 在系统偏好设置->安全&隐私中默认已经去除了允许安装任何来源App的选项,如需要重新设置成允许任何来源,即关闭Gatekeeper,请在终端中使用spctl命令:
sudo spctl --master-disable
注意,如在偏好设置中重新选择仅允许运行来自MAS或认证开发者App,即重新开启Gatekeeper之后,允许任何来源App的选项会再次消失,可通过上述命令再次关闭。
不显示“任何来源”选项(macOS 10.12默认为不显示)在控制台中执行:
sudo spctl --master-enable
屏幕黑屏¶
重置 PRAM
重启系统, 迅速的同时按住Option + Command + P + R, 直到系统自动复位, 听到启动声后松开按键, 然后让系统正常启动.
重置系统管理控制器(SMC). 系统管理控制器掌管控制电源和传感器. 关闭系统, 连接交流电电源, 按住左手边的Shift + Control + Option键, 接下来按电源按钮, 最后同时松开所有的按键, 再按电源开关重新开机
PRO 触摸板能用, 但是按不下去¶
处理方法(非硬件问题)
关机后 同时按启动键,空格键左边的option,command键还有p和r, 听到开机声音响四声后再松开。一定要同时按! 然后可能就可以用了。
破解密码¶
- 开机瞬间按住
command + R
- 出现苹果logo进度条后, 会进入恢复界面
- 恢复界面工具栏选择
实用工具 -> 终端
- 终端界面输入
resetpassword
- 弹出重置密码界面, 选择登录用户, 下一步
- 设置开机密码, 重启电脑
重启后, 如果Mac提示输入钥匙串密码,
可以到实用工具->钥匙串(点击左上角锁锁住, 再点开, 再点锁住, 会提示重设钥匙串密码)
设置好新钥匙串密码后, 可以在顶部菜单选择解锁所有程序的钥匙串, 不解锁的话, 以后进入程序都会提示输入密码.
Mac安装jdk¶
编辑 .bash_profile
文件,如果没有,则创建
# jdk安装目录
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home
PATH=$JAVA_HOME/bin:$PATH:.
CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:.
export JAVA_HOME
export PATH
export CLASSPATH
生效
source .bash_profile
查看版本
java -version
Mac¶
Mac备份¶
- 私钥公钥
- 相关软件配置文件
- iTerm2
Mac恢复¶
# 安装Homebrew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# 安装wget
brew install wget
# Install oh-my-zsh now
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
可以直接使用settings-sync插件同步配置
命令面板
>shell command
选择 Install "code" command in PATH
vscode配置恢复
vscode User Settings
// 将设置放入此文件中以覆盖默认设置
{
"window.zoomLevel": 1,
"editor.minimap.enabled": true,
"editor.fontSize": 15,
// 七牛图片上传工具开关
"qiniu.enable": true,
// 一个有效的七牛 AccessKey 签名授权。
"qiniu.access_key": "xxxxxxxxxxxx",
// 一个有效的七牛 SecretKey 签名授权。
"qiniu.secret_key": "xxxxxxxxxxxx",
// 七牛图片上传空间。
"qiniu.bucket": "yangjinjie",
// 七牛图片上传路径,参数化命名。
"qiniu.remotePath": "${fileName}",
// 七牛图床域名。
"qiniu.domain": "http://oi45x.bkt.clouddn.com",
"workbench.colorTheme": "Default Light+",
"workbench.iconTheme": "vscode-icons",
}
- Go2Shell
- iTerm2
- The Unarchiver | 避免解压后文件名乱码
- Patterns | 正则
- Kaleidoscope | 比较
- Charles | 抓包
- iStat Menus | 状态栏工具
- Shadowsocks
- Typora
- Bartender 3 | Mac Menu Bar Item Control
- Clearview | 电子书阅读器, 支持 PDF, EPUB, CHM, MOBI, FB2, CBR, CBZ 等流行的图书格式
- 截图 http://jietu.qq.com (有道词典, 划词选词会导致QQ截图之类的失效)
- Camtasia 3 | 录屏
- 远程控制
- Remote Desktop Connection
- TeamView
- 开发类工具
- IntelliJ IDEA
- PyCharm
- Navicat Premium | 数据库
MacOS相关笔记¶
iPhone DFU模式刷机¶
使用iTunes,两种方法
- 使用iTunes下载固件并恢复
- 下好固件之后使用iTunes恢复
使用iTunes刷机¶
- 手动进入DFU模式:开机状态,用数据线跟电脑连接好。先按住关机键2秒,然后,同时按住关机键和Home键8-10秒;最后,只按住Home键15秒。
- 检查否正确进入DFU模式:在DFU模式下,手机是黑屏的。如果没有成功,请重新进行第一步。
直接点击恢复按钮,等待下载完成,并等待完成恢复即可
方法一:之前进DFU模式的时候应该都是连着数据线的,在这个时候只需要同时按住【HOME】键和【POWER】键不放开,然后保持10秒左右。如果听到电脑出现【叮咚】的一声或者看到iPhone上面出现白色的苹果Logo后,这个时候放开【HOME】键和【POWER】键。
接着什么按键也不要按,iPhone会自动进行开机的。iPhone屏幕上会显示苹果的Logo很长一段时间内都不会动,耐心等待,iPhone就会退出DFU模式然后正常启动的。
方法二:使用第三方工具退出DFU模式。第三方的工具可以用tinyumbrella里面的【 Exit Recovery】。链接iPhone到电脑,然后打开tinyumbrella,点击上面的【 Exit Recovery】即可。
iterm2¶
配色¶
选择喜欢的配色方案
在Preferences->Profiles->Colors的load presets可以选择某个配色方案。也可以自己下载。在网站http://iterm2colorschemes.com/,几乎可以找到所有可用的配色方案。
快捷键¶
新建标签:command + t
关闭标签:command + w
切换标签:command + 数字 command + 左右方向键 cmd + { , cmd + }
切换全屏:command + enter
查找:command + f
垂直分屏(横向分布):command + d
水平分屏(竖向分布):command + shift + d
切换屏幕:command + option + 方向键 command + [ 或 command + ]
查看历史命令:command + ;
查看剪贴板历史:command + shift + h
清除当前行,无论光标在什么位置:ctrl + u
到行首:ctrl + a
到行尾:ctrl + e
前进后退:ctrl + f/b (相当于左右方向键)
上一条命令:ctrl + p
搜索命令历史:ctrl + r
删除当前光标的字符:ctrl + d
删除光标之前的字符:ctrl + h
删除光标之前的单词:ctrl + w
删除到文本末尾:ctrl + k
交换光标处文本:ctrl + t
清屏1:command + r = clear 不过只是换到新一屏,不会想 clear 一样创建一个空屏
清屏2:ctrl + l
智能能查找,支持正则查找:cmd + f
⌘← / ⌘→ 到一行命令最左边/最右边 ,这个功能同 C+a / C+e
⌥← / ⌥→ 按单词前移/后移,相当与 C+f / C+b,其实这个功能在Iterm中已经预定义好了,⌥f / ⌥b,看个人习惯了
选择即复制 + 鼠标中键粘贴,这个很实用
⌘ + f 所查找的内容会被自动复制
输入开头命令后 按 ⌘ + ; 会自动列出输入过的命令
⌘ + shift + h 会列出剪切板历史
可以在 Preferences > keys 设置全局快捷键调出 iterm,这个也可以用过 Alfred 实现
一个标签页中开的窗口太多,有时候会找不到当前的鼠标,cmd + /
找到它。
Ctrl+a / Ctrl+e 这个几乎在哪都可以使用
Ctrl+p / !! 上一条命令
Ctrl+k 从光标处删至命令行尾 (本来 Ctrl+u 是删至命令行首,但iterm中是删掉整行)
Ctrl+w A+d 从光标处删至字首/尾
Ctrl+h Ctrl+d 删掉光标前后的自负
Ctrl+y 粘贴至光标后
Ctrl+r 搜索命令历史,这个较常用
iterm2有2种好用的选中即复制模式。
一种是用鼠标,在iterm2中,选中某个路径或者某个词汇,那么,iterm2就自动复制了。
另一种是无鼠标模式,command+f,弹出iterm2的查找模式,输入要查找并复制的内容的前几个字母,确认找到的是自己的内容之后,输入tab,查找窗口将自动变化内容,并将其复制。如果输入的是shift+tab,则自动将查找内容的左边选中并复制。
如下图,设置好系统热键之后,将在正常的浏览器或者编辑器等窗口的上面,以半透明窗口形式直接调出iterm2 shell。
按下同样的系统热键之后,将自动隐藏。这样非常有利于随时随地处理。

iTerm2-Hotkey-2017221
iTerm2 也可以使用历史记录,按 cmd + Shift + h
弹出历史记录窗口。
输入打头几个字母,然后输入command+; iterm2将自动列出之前输入过的类似命令。
这里写图片描述 剪切历史
输入command+shift+h,iterm2将自动列出剪切板的历史记录。如果需要将剪切板的历史记录保存到磁盘,在Preferences > General > Save copy/paste history to disk.中设置。

ITerm2-save history to disk-2017221
command+enter 进入 或 返回全屏模式
command+option+e,并且可以搜索
Window > Save Window Arrangement.
Window > Restore Window Arrangement
可以在Preferences > General > Open saved window arrangement.设置自动恢复快照
sed在Mac下的差异¶
参数 i¶
linux下可选参数 -i
在Mac下被强制要求
执行命令
➜ oam git:(master) ✗ sed -i "s#Updated: 2017-[0-9][0-9]-[0-9][0-9]#Updated: $(date +%F)#g" README.md
sed: 1: "README.md": invalid command code R
操作失败,linux下是OK的。因为两个系统下对参数“i”的要求不一样。
参数“i”的用途是直接在文件中进行替换。为防止误操作,可提供一个后缀名,使sed在替换前对文件进行备份,mac下是强制要求备份的,但linux下是可选的。
所以如果不需要备份,传入空字符串
➜ oam git:(master) ✗ sed -i "" "s#Updated: 2017-[0-9][0-9]-[0-9][0-9]#Updated: $(date +%F)#g" README.md 替代linux下用法即可
或者安装GNU版本的 sed来解决
编码问题¶
说到编辑器,就离不开编码问题,sed作为programmer式的编辑工具也是。
工作时碰到报错
sed: RE error: illegal byte sequence
sed : RE error : illegal byte sequence
这是因为在识别含有多字节编码字符时遇到了解析冲突问题,解决方式是在sed前执行下方命令,或将其加入~/.bash_profile 或 ~/.zshrc
export LC_CTYPE=C
export LANG=C
export LC_CTYPE = C
export LANG = C
改变语言编码环境,使Mac下sed正确处理单字节和多字节字符。再次执行sed命令,OK了。
不过弊端也有,在我们UTF8环境的Mac终端里,这么做会让字符中每个字节都被识别为单独的字符(终端里输入汉字会显示为16进制unicode编码)忽略UTF8的编码规则。
制作U盘启动盘¶
使用磁盘工具¶

Upan-1
打开终端, 执行命令¶
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/Sierra --applicationpath /Applications/Install\ macOS\ High\ Sierra.app --nointeraction
正常情况, 显示
# 提示密码
Erasing Disk: 0%... 10%... 20%... 30%...100%...
Copying installer files to disk...
Copy complete.
Making disk bootable...
Copying boot files...
Copy complete.
Done.
如果提示
/Applications/Install macOS High Sierra.app does not appear to be a valid OS installer application.
,
尝试命令后面添加 && say Done
, 执行
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/Sierra --applicationpath /Applications/Install\ macOS\ High\ Sierra.app --nointeraction && say Done
还有, 如果还是有问题, 可以尝试把商店换到美国区重新下载。
国区 4.68G 的不行,美区 5.18G 的可以。
ubuntu-debian¶
Debian¶
让Debian以root登录¶
# 修改gdm3的登录pam文件
# vi /etc/pam.d/gdm3
# 将auth required pam_succeed_if.so user != root quiet_success注释掉 //本行前加#
# 重启即可
让Debian以root自动登录。¶
首先修改gdm3的设定文件
# vi /etc/gdm3/deamon.conf
AutomaticLogin = false //改为true
AutomaticLogin = root //以root自动登录
# 如果想等几秒再登录,那么用以下的
TimedLoginEnable = true
TimedLogin = root
TimedLoginDelay = 5 //延迟5秒登录,可修改
# 最后gdm3的自动登录pam文件
# vi /etc/pam.d/gdm3-autologin
# 将auth required pam_succeed_if.so user != root quiet_success注释掉。 #//在本行前加#,取消Debian不让root登录的限制。
# s重启即可。
ubuntu-debian
Ubuntu server命令行配置shadowsocks全局代理¶
由于Ubuntu Server是不带用户界面的,所以要为Server配置Shadowsocks还是稍显麻烦。本文就是我配置Shadowsocks的一些经验,以待参考。 安装shadowsocks
由于shadowsocks是基于python开发的,所以必须安装python:
sudo apt-get install python
接着安装python的包管理器pip:
sudo apt-get install python-pip
安装完毕之后,通过pip直接安装shadowsocks:
sudo pip install shadowsocks
配置shadowsocks
新建一个配置文件shawdowsocks.json,然后配置相应的参数:
~/shadowsocks# cat shawdowsocks.json
{
"server" : "104.194.85.161",
"server_port" : 443,
"localPort" : 1080,
"password" : "xxx",
"timeout" : 600,
"method" : "aes-256-cfb"
}
上面的参数需要你的shawdowsocks服务提供商为你提供,当然你也可以自己搭建一个。
配置完成后就可以启动shawdowsocks服务:
sudo sslocal -c shawdowsocks.json -d start
配置全局代理¶
启动shawdowsocks服务后,发现并不能翻墙上网,这是因为shawdowsocks是socks 5代理,需要客户端配合才能翻墙。
为了让整个系统都走shawdowsocks通道,需要配置全局代理,可以通过polipo实现。
首先是安装polipo:
sudo apt-get install polipo
接着修改polipo的配置文件/etc/polipo/config:
logSyslog = true
logFile = /var/log/polipo/polipo.log
proxyAddress = "0.0.0.0"
socksParentProxy = "127.0.0.1:1080"
socksProxyType = socks5
chunkHighMark = 50331648
objectHighMark = 16384
serverMaxSlots = 64
serverSlots = 16
serverSlots1 = 32
重启polipo服务:
sudo /etc/init.d/polipo restart
为终端配置http代理:
export http_proxy="http://127.0.0.1:8123/"
接着测试下能否翻墙:
wget http://www.google.com 如果收到index.html则终端代理成功!
注意事项¶
服务器重启后,下面两句需要重新执行:
sudo sslocal -c shawdowsocks.json -d start
export http_proxy="http://127.0.0.1:8123/"
Ubuntu安装jdk¶
两种方法
Ubuntu通过PPA安装jdk¶
How to Install JAVA on Ubuntu & LinuxMint via PPA
First, you need to add webupd8team Java PPA repository in your system and install Oracle Java 8 using following a set of commands.
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
jdk7
sudo apt-get install oracle-java7-installer
安装器会提示你同意 oracle 的服务条款,选择 ok
然后选择yes 即可
如果你懒,不想自己手动点击.也可以加入下面的这条命令,默认同意条款:
JDK7 默认选择条款
shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections
JDK8 默认选择条款
echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections
接下会是等待(依个人网速定)
如果你因为防火墙或者其他原因,导致installer 下载速度很慢,可以中断操作.然后下载好相应jdk的tar.gz 包,放在:
/var/cache/oracle-jdk7-installer (jdk7)
/var/cache/oracle-jdk8-installer (jdk8)
下面,然后安装一次installer. installer 则会默认使用 你下载的tar.gz包
Verify Installed Java Version
After successfully installing oracle Java using above step verify installed version using the following command.
rahul@tecadmin:~$ java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode, sharing)
Configuring Java Environment
In Webupd8 PPA repository also providing a package to set environment variables, Install this package using the following command.
$ sudo apt-get install oracle-java8-set-default
设置系统默认jdk
JDk7
sudo update-java-alternatives -s java-7-oracle
JDK8
sudo update-java-alternatives -s java-8-oracle
如果即安装了jdk7,又安装了jdk8,要实现两者的切换,可以:
jdk8 切换到jdk7
sudo update-java-alternatives -s java-7-oracle
jdk7 切换到jdk8
sudo update-java-alternatives -s java-8-oracle
使用二进制包安装¶
下载
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u101-b13/jdk-8u101-linux-x64.tar.gz
mkdir /usr/lib/jvm -p
tar xf jdk-8u101-linux-x64.tar.gz -C /usr/lib/jvm/
ln -s /usr/lib/jvm/jdk1.8.0_101/ /usr/lib/jvm/jdk
修改环境变量
sudo vim ~/.bashrc
export JAVA_HOME=/usr/lib/jvm/jdk ## 这里要注意目录要换成自己解压的jdk 目录
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
使环境变量马上生效
source ~/.bashrc
设置系统默认jdk 版本
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.7.0_60/bin/java 300
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk1.7.0_60/bin/javac 300
sudo update-alternatives --install /usr/bin/jar jar /usr/lib/jvm/jdk1.7.0_60/bin/jar 300
sudo update-alternatives --install /usr/bin/javah javah /usr/lib/jvm/jdk1.7.0_60/bin/javah 300
sudo update-alternatives --install /usr/bin/javap javap /usr/lib/jvm/jdk1.7.0_60/bin/javap 300
然后执行
sudo update-alternatives --config java
若是初次安装jdk,会有下面的提示
There is only one alternative in link group java (providing /usr/bin/java):
/usr/lib/jvm/jdk1.7.0_60/bin/java
否者,选择合适的jdk
Ubuntu命令大全¶
作者: 李春梅 邮箱:lichunmei@rivamed.cn QQ:2364640877、2994654462
Ubuntu常用命令大全¶
查看xxx软件安装内容:dpkg -L xxx
查找软件:apt-cache search 正则表达式
查找文件属于哪个包:dpkg -S filename apt-file search filename
查询软件 xxx 依赖哪些包:apt-cache depends xxx
查询软件 xxx 被哪些包依赖:apt-cache rdepends xxx
增加一个光盘源:sudo apt-cdrom add
系统升级:sudo apt-get update、sudo apt-get upgrade、sudo apt-get dist-upgrade
清除所有删除包的残余配置文件:dpkg -l |grep ^rc|awk ‘{print $2}’ |tr [”\n”] [” “]|sudo xargs dpkg -P
编译时缺少 h 文件的自动处理:sudo auto-apt run ./configure
查看安装软件时下载包的临时存放目录:ls /var/cache/apt/archives
备份当前系统安装的所有包的列表:dpkg –get-selections | grep -v deinstall > ~/somefile
从上面备份的安装包的列表文件恢复所有包:dpkg –set-selections < ~/somefile sudo dselect
清理旧版本的软件缓存:sudo apt-get autoclean
清理所有软件缓存:sudo apt-get clean
删除系统不再使用的孤立软件:sudo apt-get autoremove
查看包在服务器上面的地址:apt-get -qq –print-uris install ssh | cut -d\’ -f2
系统¶
查看内核:uname -a
查看 Ubuntu 版本:cat /etc/issue
查看内核加载的模块:lsmod
查看 PCI 设备:lspci
查看 USB 设备:lsusb
查看网卡状态:sudo ethtool eth0
查看 CPU 信息cat /proc/cpuinfo
显示当前硬件信息:lshw
硬盘¶
- 查看硬盘的分区:sudo fdisk -l
- 查看 IDE 硬盘信息:sudo hdparm -i /dev/hda
- 查看 STAT 硬盘信息:sudo hdparm -I /dev/sda或sudo apt-get install blktool、sudo blktool /dev/sda id
- 查看硬盘剩余空间:df -h、df -H
- 查看目录占用空间:du -hs 目录名
- 优盘没法卸载:sync fuser -km /media/usbdisk
内存¶
查看当前的内存使用情况:free -m
进程¶
查看当前有哪些进程:ps -A
中止一个进程:kill 进程号(就是 ps -A 中的第一列的数字) 或者 killall 进程名
强制中止一个进程(在上面进程中止不成功的时候使用):kill -9 进程号 或者 killall -9 进程名
图形方式中止一个程序:xkill 出现骷髅标志的鼠标,点击需要中止的程序即可
查看当前进程的实时状况:top
查看进程打开的文件:lsof -p
ADSL 配置 ADSL:sudo pppoeconf
ADSL 手工拨号:sudo pon dsl-provider
激活 ADSL:sudo /etc/ppp/pppoe_on_boot
断开 ADSL:sudo poff
查看拨号日志:sudo plog
如何设置动态域名:首先去 http://www.3322.org 申请一个动态域名
然后修改 /etc/ppp/ip-up 增加拨号时更新域名指令 sudo vim /etc/ppp/ip-up
最后增加如下行 w3m -no-cookie -dump
网络¶
根据 IP 查网卡地址:arping IP 地址
查看当前 IP 地址:ifconfig eth0 |awk ‘/inet/ {split($2,x,”:”);print x[2]}’
查看当前外网的 IP 地址:w3m -no-cookie -dumpwww.edu.cn|grep-o‘[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}’ 或 w3m -no-cookie -dumpwww.xju.edu.cn|grep-o’[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}’ 或者 w3m -no-cookie -dump ip.loveroot.com|grep -o’[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}’
查看当前监听 80 端口的程序:lsof -i :80
查看当前网卡的物理地址:arp -a | awk ‘{print $4}’ ifconfig eth0 | head -1 | awk ‘{print $5}’
立即让网络支持 nat:sudo echo 1 > /proc/sys/net/ipv4/ip_forward 或者 sudo iptables -t nat -I POSTROUTING -j MASQUERADE
查看路由信息:netstat -rn sudo route -n
手工增加删除一条路由:sudo route add -net 192.168.0.0 netmask 255.255.255.0 gw 172.16.0.1 或者 sudo route del -net 192.168.0.0 netmask 255.255.255.0 gw 172.16.0.1
修改网卡 MAC 地址的方法:sudo ifconfig eth0 down 关闭网卡 sudo ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE 然后改地址 sudo ifconfig eth0 up 然后启动网卡
统计当前 IP 连接的个数:netstat -na|grep ESTABLISHED|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -r -n
netstat -na|grep SYN|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -r -n
统计当前 20000 个 IP 包中大于 100 个 IP 包的 IP 地址:tcpdump -tnn -c 20000 -i eth0 | awk -F “.” ‘{print $1″.”$2″.”$3″.”$4}’| sort | uniq -c | sort -nr | awk ‘ $1 > 100 ‘
屏蔽 IPV6:e cho “blacklist ipv6″ | sudo tee /etc/modprobe.d/blacklist-ipv6
服务¶
- 添加一个服务:sudo update-rc.d 服务名 defaults 99
- 删除一个服务:sudo update-rc.d 服务名 remove
- 临时重启一个服务:/etc/init.d/服务名 restart
- 临时关闭一个服务:/etc/init.d/服务名 stop
- 临时启动一个服务:/etc/init.d/服务名 start
设置¶
配置默认 Java 使用哪个:sudo update-alternatives –config java
修改用户资料:sudo chfn userid
给 apt 设置代理:export http_proxy=http://xx.xx.xx.xx:xxx
修改系统登录信息:sudo vim /etc/motd
中文¶
转换文件名由 GBK 为 UTF8:sudo apt-get install convmv convmv -r -f cp936 -t utf8 –notest –nosmart *
批量转换 src 目录下的所有文件内容由 GBK 到 UTF8:find src -type d -exec mkdir -p utf8/{} \; find src -type f -exec iconv -f GBK -t UTF-8 {} -o utf8/{} \; mv utf8/* src rm -fr utf8
转换文件内容由 GBK 到 UTF8:iconv -f gbk -t utf8 $i > newfile
转换 mp3 标签编码:sudo apt-get install python-mutagen find . -iname “*.mp3” -execdir mid3iconv -e GBK {} \;
控制台下显示中文:sudo apt-get install zhcon 使用时,输入 zhcon 即可
Ubuntu¶
查看系统信息¶
cat /etc/issue
cat /proc/version
uname -a
lsb_release -a
cat /etc/lsb-release
Ubuntu网络¶
root@ubuntu:~# vim /etc/network/interfaces
root@ubuntu:~# /etc/init.d/networking restart
dhclient eth1
APT¶
/etc/apt/
root@ubuntu191:/etc/apt/sources.list.d# cat zabbix.list
#deb http://repo.zabbix.com/zabbix/3.0/ubuntu xenial main
#deb-src http://repo.zabbix.com/zabbix/3.0/ubuntu xenial main
deb http://mirrors.aliyun.com/zabbix/zabbix/3.0/ubuntu xenial main
deb-src http://mirrors.aliyun.com/zabbix/zabbix/3.0/ubuntu xenial main
deb http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse
常用参数
apt-cache search package 搜索软件包
apt-cache show package 获取包的相关信息,如说明、大小、版本等
apt-get install package 安装包
apt-get install package --reinstall 重新安装包
apt-get -f install 修复安装
apt-get remove package 删除包
apt-get remove package --purge 删除包,包括配置文件等
apt-get update 更新源
apt-get upgrade 更新已安装的包
apt-get dist-upgrade 升级系统
apt-cache depends package 了解使用该包依赖那些包
apt-cache rdepends package 查看该包被哪些包依赖
apt-get build-dep package 安装相关的编译环境
apt-get source package 下载该包的源代码
pt-get clean && sudo apt-get autoclean 清理无用的包
apt-get check 检查是否有损坏的依赖
apt-cache search zabbix
软件¶
dpkg -i
apt-get
查看已安装软件
root@ubuntu75:~# dpkg -l
在终端下也可以很方便查看已安装的软件包版本号,也能单独查看所需要的软件包是否已安装和版本号,还能查看可升级的软件包。在终端下要实现这个目标就要用到一个软件工具叫做apt-show-versions,通过apt-get安装:
apt-get install apt-show-versions
apt-show-versions
查看所有已安装的软件包和版本号,可以使用more来显示每屏的内容,或者使用grep抓取等等
apt-show-versions |more
按回车键打印下一行,按下空格键打印下一屏,按下Q键退出打印结果。如果想查看单个软件包的版本,则使用命令:
apt-show-versions –p //是软件包名,不含符号
如果想查看可升级的软件包,则使用命令:
apt-show-versions –u
如果没有任何可以升级的软件包,上面那条命令不会返回任何结果的。更多的参数查看man,这两个方法哪个好用仁者见仁了。
1.查询版本
aptitude show 软件名
- 例如:aptitude show kde-runtime
dpkg -l 软件名
- 例如:dpkg -l gedit
2.查询安装路径
dpkg -L 软件名
- 例如:dpkg -L gedit
whereis 软件名
- 例如:whereis gedit
问题记录¶
自定义nameserver
, 具体相关信息运行命令man resolvconf
- 在网卡的配置文件里面加
iface eth0 inet static
address 192.168.3.3
netmask 255.255.255.0
gateway 192.168.3.1
dns-nameservers 192.168.3.45 192.168.8.10
dns-search foo.org bar.com
- 修改
resolvconf
服务的配置文件:/etc/resolvconf/resolv.conf.d/base
root@ubuntu:~# cat /etc/resolvconf/resolv.conf.d/base
nameserver 223.5.5.5 114.114.114.114
Ubuntu下使用mail命令发送邮件¶
配置¶
修改如下文件
- Ubuntu
/etc/nail.rc或者/etc/s-nail.rc
,具体哪个文件,安装之后查看一下即可 - CentOS
/etc/mail.rc
#...
set from=brave0517@163.com
set smtp=smtp.163.com
set smtp-auth-user=brave0517@163.com
set smtp-auth-password=xxxx
set smtp-auth=login
set smtp-use-starttls
set ssl-verify=ignore
配置完成之后就可以使用mail发送邮件了
使用mail¶
交互形式发送邮件
mail + 邮箱地址,回车 -> 填写主题 -> 填写内容 -> ctrl + d 结束输入 -> cc代表抄送,回车完成发送
使用管道发送
echo “邮件内容” | mail -s “主题” 邮箱地址
读取文件发送
mail -s “主题” “邮箱地址” < “path/filename”
有些敏感的内容,可能会被屏蔽,换内容继续尝试
Ubuntu使用zsh¶
Ubuntu升级内核¶
直接使用deb包进行升级¶
内核下载地址
http://kernel.ubuntu.com/~kernel-ppa/mainline/
根据对应版本, 对应架构下载对应包
# 下载
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.13/linux-headers-4.13.0-041300-generic_4.13.0-041300.201709031731_amd64.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.13/linux-image-4.13.0-041300-generic_4.13.0-041300.201709031731_amd64.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.13/linux-headers-4.13.0-041300_4.13.0-041300.201709031731_all.deb
# 安装
sudo dpkg -i *.deb
# 重启登录, 验证
uname -sr
ubuntu进入单用模式方法¶
- 按任意键停留在grub菜单
- 选择 Advanced options for Ubuntu
- 再选择:recovery mode 按e进入编辑状态
- 将linux开头那行的“ro recovery nomodeset”改为 “rw single init=/bin/bash” (注意:ro 是只读模式,rw是读写模式。) 然后按Ctrl + x, 进入单用户模式
windows¶
windows相关笔记
搜狗输入法皮肤,自己制作
Shadowsocks
Office365卸载工具
Sublime¶
主题配置¶
Preferences.sublime-theme 文件配置 主题设置
{
"color_scheme": "Packages/Theme - Brogrammer/brogrammer.tmTheme",
"font_size": 11.0,
"ignored_packages":
[
"Vintage"
],
"tabset_control": true,
"theme": "Brogrammer.sublime-theme",
"update_check": false,
"word_wrap": "auto"
}
Sublime Text 常用插件说明¶
(1)Alignment: 代码对齐插件,即"="号对齐,变量定义太多,长短不一,可一键对齐,默认快捷键Ctrl+Alt+A可能和QQ截屏功能冲突,可设置其他快捷键如:Ctrl+Shift+Alt+A
(2)AutoFileName: 快捷输入文件名插件,自动完成文件名的输入,如图片选取,输入"/"即可看到相对于本项目文件夹的其他文件。
(3)BracketHighlighter: 代码匹配插件,可匹配[], (), {}, “”, ”, ,高亮标记,便于查看起始和结束标记,点击对应代码即可。
(4)ClipboardHistory: 剪切板历史记录插件,方便使用复制/剪切的内容,Ctrl+alt+v:显示历史记录,Ctrl+alt+d:清空历史记录,Ctrl+shift+v:粘贴上一条记录(最旧),Ctrl+shift+alt+v:粘贴下一条记录(最新)
(5)CodeFormatter: 代码格式化插件,支持PHP、JavaScript/JSON、HTML、CSS/SCSS、Python、Visual Basic、Coldfusion/Railo/Lucee等等。
(6)ConvertToUTF8: 编辑并保存目前编码不被 Sublime Text 支持的文件,特别是中日韩用户使用的GB2312,GBK,BIG5,EUC-KR,EUC-JP ,ANSI等。
(7)DocBlockr: 代码注释插件,标准的注释,包括函数名、参数、返回值等,并以多行显示,省去手动编写。
(8)Emmet: HTML/CSS代码快速编写插件,对于前端来说,可是必备插件。
(9)FileDiffs: 强大的比较代码不同工具,比较当前文件与选中的代码、剪切板中代码、另一文件、未保存文件之间的差别,右键标签页,出现FileDiffs Menu或者Diff with Tab…选择对应文件比较即可。
(10)Git: Git管理插件,基本上实现了Git的所有功能。
(11)IMESupport: 实现中文输入法鼠标跟随插件。
(12)KeymapManager: 快捷键管理插件,通过Ctrl+alt+k或者通过顶部菜单“查看 -> 快捷键管理”打开面板。
(13)PackageControl: 插件管理插件,提供添加、删除、禁用、查找插件等功能。
(14)SideBarEnhancements: 侧边栏右键增强插件,可以自定义打开方式快捷键,非常实用。
(15)SublimeCodeIntel: 代码自动提示插件,支持绝大多数前端开发语言。
(16)SublimeLinter: 代码语法检测插件,支持C/C++、CSS、HTML、Java、JavaScript、Lua、Perl、PHP、Python、Ruby、XML等等。
(17)SyncedSidebarBg: 侧边栏与主题颜色同步更新插件,自动同步侧边栏底色为编辑窗口底色。
(18)Theme-Nil: 完美的编码主题,用过的都说很好。
按快捷键Ctrl+Shift+P,输入 install 并回车,选择相应插件安装即可。或者依次点击“首选项 – 插件控制 – Install Package”进行插件安装。
修改主题的配置文件即可。例如:使用流风清音汉化版,其默认主题为“Nil-Theme”,那么配置文件的相应路径是“Data:raw-latex:Packages:raw-latex:`\Nil`-Theme:raw-latex:Nil.sublime-theme”。
/** Sidebar tree (bg) **/
{
“class”: “sidebar_tree”,
“dark_content”: true,
“row_padding”: [12, 4],
“indent”: 13,
“indent_offset”: 15,
“indent_top_level”: false,
“layer0.tint”: [32,32,32], /* darker gray */ /* 输入喜欢的颜色的对应RGB值即可 */
“layer0.opacity”: 1.0,
“dark_content”: true
},
这是Win10权限问题,一种方法是卸载后重装到系统之外的分区,另一种方法则是以管理员身份运行。
依次点击“首选项” – “设置 – 用户”打开文件,按原有格式添加以下配置即可。提示:记得给原来的最后一行末尾添加一个半角逗号。
“caret_style”: “phase”,
“caret_extra_top”: 0,
“caret_extra_bottom”: 0,
“caret_extra_width”: 1,
在主题模板规则中添加如下配置即可。
{
“class”: “icon_folder”,
“content_margin”: [0,0]
},
{
“class”: “icon_file_type”,
“content_margin”: [0,0]
},
{
“class”: “icon_folder_loading”,
“content_margin”: [0,0]
}
这里以Win7来说明,桌面 – 鼠标右键 – 个性化 – 显示 – 设置自定义文本大小(DPI) – 选择“较小 – 100%(默认)”即可。或者点击“首选项” – “设置 – 用户”打开文件,在末尾加上一行代码覆盖系统的DPI。
“dpi_scale”: 1.0,
Linux基础知识¶
Linux里面软件的安装方法:¶
1、 rpm -ivh 包名.rpm
- 有依赖问题,安装A,A需要先安装B。
- 缺点:不能定制。
2、 yum安装自动解决rpm安装的依赖问题,安装更简单化。
- 优点:简单、易用、高效
- 缺点:不能定制。
3、 编译(C语言源码-编译二进制等)
- ./configure(配置),make(编译),make install(安装)
- 优点:可以定制
- 缺点:复杂、效率低。
4、 定制化制作rpm包,搭建yum仓库,把我定制的rpm包放到yum仓库,进行yum安装
- 优点:结合了2和3的优点
- 缺点:复杂
grub菜单添加密码¶
命令行执行 /sbin/grub-md5-crypt
产生密码
然后修改
vim /etc/grub.conf
在 splashimage和title之间, 添加password –md5 ..生成的加密后的md5值
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.32-642.3.1.el6.x86_64)
root (hd0,0)
kernel /vmlinuz-2.6.32-642.3.1.el6.x86_64 ro root=UUID=d5a03e22-61ab-43b0-9cd7-ca9a5
869205d rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel
=auto KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
initrd /initramfs-2.6.32-642.3.1.el6.x86_64.img
title CentOS 6 (2.6.32-573.el6.x86_64)
root (hd0,0)
kernel /vmlinuz-2.6.32-573.el6.x86_64 ro root=UUID=d5a03e22-61ab-43b0-9cd7-ca9a58692
05d rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=aut
o KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
initrd /initramfs-2.6.32-573.el6.x86_64.img
Linux系统启动流程¶
下面是整个Linux系统的启动过程:
Linux Boot Step
Start BIOS grub/lilo Kernel boot init rc.sysinit rc mingetty login shell 登录系统
一.BIOS自检¶
计算机在接通电源之后首先由BIOS进行POST自检,然后依据BIOS内设置的引导顺序从硬盘、软盘或CDROM中读入引导块。Linux系统是人BIOS中的地址oxFFFF0处开始引导的。BIOS的第1个步骤是加电POST自检。POST的工作是对硬件进行检测。BIOS的第2个步骤是进行本地设备的枚举和初始化。BIOS由两部分组成:POST代码和运行时的服务。当POST完成之后,它被从内存中清理出来,但是BIOS运行时服务依然保留在内存中,目标操作系统可以使用这些服务。
BIOS运行时会按照CMOS的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备甚至是USB闪存。通常,Linux系统都是从硬盘上引导的,其中主引导记录(MBR)中包含主引导加载程序。MBR是一个512字节大小的扇区,位于磁盘上的第一个扇区(0道0柱面1扇区)。当MBR被加载到RAM中之后,BIOS就会将控制权交给MBR。
如果要查看MBR的内容,用户需要以root用户的身份运行如下命令:
dd if=/dev/had of=mbr.bin bs=512 count=1 读入了1+0个块 输出了1+0个块 od –xa mbr.bin … …
它从/dev/had(第一个IDE盘)上读取前512个字节的内容,并将其写入mbr.bin文件中。od命令会以十六进制和ASCII码格式打印这个二进制文件的内容。
二.启动GRUB/LILO¶
GRUB和LILO都是引导加载程序。引导加载程序用于引导操作系统启动。当机器引导它的操作系统时,BIOS会读取引导介质上最前面的512字节(主引导记录)。在单一的MBR中只能存储一个操作系统的引导记录,所以当需要多个操作系统时就会出现问题,需要更灵活的引导加载程序。
所有引导加载程序都以类似的方式工作,满足共同的目的,但LILO和GRUB之间也有很多不同之处:
LILO没有交互式命令界面,而GRUB拥有; LILO不支持网络引导,而GRUB支持; LILO将可以引导操作系统的信息存储在MBR中。
如果修改了LILO配置文件,必须将LILO第一阶段引导加载程序重写到MBR。相对于GRUB,这是一个更为危险的选择,因为错误配置的MBR可能会让系统无法引导。使用GRUB时,如果配置文件配置错误,则只是默认转到GRUB命令行界面。
三.加载内核¶
接下来的步骤就是加载内核映像到内存中,内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。通常它是一个zImage(压缩映像,小于512KB)或是一个bzImage(较大的压缩映像,大于512KB),它是提前使用zlib压缩过的。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压缩,然后将其放入高端内存中。如果有初始RAM磁盘映像,系统就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。
四.执行init进程¶
init进程是系统所有进程的起点,内核在完成核内引导以后,即在本进程空间内加载init程序,它的进程呈是1。Init进程是所有进程的发起者和控制者。因为在任何基于Linux的系统中,它都是第一个运行的进程,所以init进程的编号(PID)永远是1。
init进程有以下两个作用。
init进程的第一个作用是扮演终结父进程的角色。因为init进程永远不会被终止,所以系统总是可以确信它的存在,并在必要的时候以它为参照。如果某个进程在它衍生出来的全部子进程结束之前被终止,就会出现必须以init为参照的情况。此时那些失去了父进程的子进程就都会以init作为它们的父进程。
init的第二个作用是在进入某个特定的运行级别时运行相应的程序,以此对各种运行级别进行管理。它的这个作用是由/etc/inittab文件定义的。
五.通过/etc/inittab文件进行初始化¶
Init的工作是根据/etc/inittab来执行相应的脚本,进行系统初始化,如设置键盘、字体、装载模块,设置网络等。
/etc/rc.d/rc.sysinit¶
在init的配置文件中有如下一行: si::sysinit:/etc/rc.d/rc.sysinit
rc.sysinit是由init执行的第一个脚本,它主要完成一些系统初始化的工作。rc.sysinit是每一个运行级别都要首先运行的重要脚本,它主要完成的工作有:激活交换分区、检查磁盘、加载硬件模块以及其他一些需要优先执行的任务。/etc/rc.d/ rc.sysinit主要完成各个运行模式中相同的初始化工作。包括:
设置初始的$PATH变量; 配置网络; 为虚拟内存启动交换; 调协系统的主机名; 检查root文件系统,以进行必要的修复; 检查root文件系统的配额; 为root文件系统打开用户和组的配额; 以读/写的方式重新装载root文件系统; 清除被装载的文件系统表/etc/mtab; 把root文件系统输入到mtab; 使系统为装入模块做准备; 查找模块的相关文件; 检查文件系统,以进行必要的修复; 加载所有其他文件系统; 清除/etc/mtab、/etc/fastboot和/etc/nologin; 删除UUCP和lock文件; 删除过时的子系统文件; 删除过时的pid文件; 设置系统时钟; 激活交换分区; 初始化串行端口; 装入模块。
/etc/rc.d/rcX.d/[KS]¶
在rc.sysinit执行后,将返回init,继续执行/etc/rc.d/rc程序。以运行级别5为例,init将执行配置文件inittab中的以下内容: 15:5:wait:/etc/rc.d/rc 5
这一行表示以5为参数运行/etc/rc.d/rc,/etc/rc.d/rc是一个shell脚本,它接受5作为参数,去执行/etc/rc.d/rc5.d目录下的所有的rc启动脚本,/etc/rc.d/rc5.d目录中的启动脚本实际上都是一些链接文件,而不是真正的rc启动脚本,真正的rc启动脚本实际上都在/etc/rc.d/init.d目录下。而这些rc启动脚本有着类似的用法,它们一般能接受stat、stop、restart、status等参数。
/etc/rc.d/rc5.d中的rc启动脚本通常是以K或S开头的链接文件,以S开头的启动脚本将以start参数来运行。如果发现相应的脚本也存在K打头的链接,而且已经处于运行状态了(以/var/lock/subsys下的文件作为标志),则将首先以stop为参数停止这些已经启动了的守护进程,然后再重新运行。这样做是为了保证当init改变运行级别时,所有相关的守护进程都将重启。
至于在每个运行级中将运行哪些守护进程,用户可以通过chkconfig来自行设定。常见的守护进程如下。
amd:自动安装NFS守护进程。 apmd:高级电源管理守护进程。 arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对应的数据库。 outofs:自动安装管理进程automount,与NFS相关,依赖于NIS。 crond:Linux系统下计划任务的守护进程。 named:DNS服务器。 netfs:安装NFS、Samba和Netware网络文件系统。 network:激活已配置网络接口的脚本程序。 nfs:打开NFS服务。 portmap:RPCportmap管理器,它管理基于RPC服务的连接。 sendmail:邮件服务器sendmail。 smb:Samba文件共享/打印服务。 syslog:一个让系统引导时启动syslog和klogd系统日志守候进程的脚本。 xfs:X Window字型服务器,为本地和远程X服务器提供字型集。 Xinetd:支持多种网络服务的核心守护进程,可以管理wuftp、sshd、telnet等服务。
这些守护进程启动完毕,rc程序也就执行完了,然后又返回init继续下一步。
执行/etc/ec.d/rc.local¶
RHEL 4中的运行模式2、3、5都把/etc/rc.d/rc.local做为初始化脚本中的最后一个,所以用户可以自己在这个文件中添加一些需要在其他初始化工作之后、登录之前执行的命令。在维护Linux系统时一般会遇到需要系统管理员对开机或关机命令脚本进行修改的情况。如果所做的修改只在引导开机的时候起作用,并且改动不大的话,可以考虑简单地编辑一下/etc/rc.d/rc.local脚本。这个命令脚本程序是在引导过程的最后一步被执行的。
六.执行/bin/login程序¶
login程序会提示使用者输入账号及密码,接着编码并确认密码的正确性,如果账号与密码相符,则为使用者初始化环境,并将控制权交给shell,即等待用户登录。
login会接收mingetty传来的用户名作为用户名参数,然后login会对用户名进行分析。如果用户名不是root,且存在/etc/nologin文件,login将输出nologin文件的内容,然后退出。这通常用来在系统维护时防止非root用户登录。只有在/etc/securetty中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。/etc/usertty文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。
在分析完用户名后,login将搜索/etc/passwd以及/etc/shadow来验证密码以及设置账户的其他信息,比如:主目录什么、使用何种shell。如果没有指定主目录,则将主目录默认设置为根目录;如果没有指定shell,则将shell类型默认设置为/bin/bash。
Login程序成功后,会向对应的终端再输出最近一次登录的信息(在/var/log/lostlog中有记录),并检查用户是否有新邮件(在/usr/spool/mail的对应用户名目录下),然后开始设置各种环境变量。对于bash来说,系统首先寻找/etc/profile脚本文件并执行它;然后如果用户的主目录中存在.bash_profile文件,就执行它,在这些文件中又可能调用了其他配置文件,所有的配置文件执行后,各种环境变量也设好了,这时会出现大家熟悉的命令行提示符,至此整个启动过程就结束了。
python¶
basis¶
Python¶
查看Python语言设计哲学¶
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Python解释器¶
1. Cpython
2. Jpython
3. IronPython
简单入门¶
命令行输入python,回车
# python 2.x
print "Hello World!"
# python 3.x
print('Hello World')
用python执行
➜ cat hello.py
#/usr/bin/env python
print('Hello World')
➜ python hello.py
Hello World!
1. #!/usr/bin/python ## 告诉shell使用/usr/bin/python执行
2. #!/usr/bin/env python ## 操作系统环境不同的情况下指定执行这个脚本用python来解释
#!/usr/bin/env python3
python hello.py
chmod +x hello.py && ./hello.py
# _*_ coding:utf-8 _*_
# -*- coding:utf-8 -*-
# coding:utf-8
代码注释¶
# 只需要在代码前面加上 '#' 号
➜ cat note.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
print("""
My name is xxx
I'm a python developer
My blog is xxx
Life is short,you need python.
""")
执行结果
➜ python note.py
My name is xxx
I'm a python developer
My blog is xxx
Life is short,you need python.
变量¶
命名规则
- 变量只能包含数字、字母、下划线
- 不能以数字开头
- 变量名不能使用
python
内部的关键字
- NAME 一般不大写,全大写用来代表常量
- 首字母大写常被用作类名
python
内部关键字
['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
python
中变量工作方式
- 变量在它第一次赋值时创建;
- 变量在表达式中使用时将被替换成它们所定义的值;
- 变量在表达式中使用时必须已经被赋值,否则会报
name 'xxx is not defined'
; - 变量像对象一样不需要在一开始进行声明.
>>> age = 21
>>> age
21
>>> type(21)
<type 'int'>
上述代码中age并没有指定数据类型,python在运行过程中已经决定了这个值时什么类型,而不用通过指定类型的方式。
在python基础中还有一个比较重要的概念就是垃圾回收机制
>>> a = 1
>>> b = a
>>> id(a),id(b)
(140426418868328, 140426418868328)
通过id()
内置函数可以清楚地看到这两个变量指向同一块内存区域。
>>> name = "yjj"
>>> name = "zt"
>>> name
'zt'
上述实例,可以理解垃圾回收机制
是如何工作的
- 创建一个变量
name
,值通过指针指向yjj
的内存地址; - 如果yjj这个值之前没有在内存中创建,那么现在创建他,并让这个内存地址的引用数
+1
,此时等于1
; - 然后对变量
name
重新赋值,让其指针指向zt
的内存地址; - 那么此时
yjj
的值的引用数就变成0
,当python
检测到某个内存地址的引用数等于0
时,就会把这个内存地址给删掉,从而释放内存; - 最后
name
的值的指针指向了zt
的内存地址,所以name
的值就是zt
>>> name = "yjj"
>>> print(name)
yjj
基本的数据类型¶
数据类型初识
1. 数字
1. int(整型)
2. long(长整型)
3. float(浮点型)
4. complex(复数)
2. 布尔型(bool)
3. 字符串(str)
4. 列表(list)
5. 元组(tuple)(不可变列表)
6. 字典(dict)(无序)
7. 集合(set)
整数类型定义的时候变量赋值直接使用数字
,不要用双引号包起来
>>> age = 20
>>> type(age)
<class 'int'>
>>> num = 2.2
>>> type(num)
<class 'float'>
>>> c = 1j
>>> type(c)
<class 'complex'>
布尔值只有True(真)
,False(假)
>>> if True:
... print("0")
... else:
... print("1")
...
0
# 如果为真输出0,否则输出1
定义字符串类型是需要用单引号或者双引号引起来的
>>> name = "yjj"
>>> type(name)
<type 'str'>
>>> name = 'yjj'
>>> print(name)
yjj
流程控制¶
➜ cat num.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
num = 5
if num > 1 :
print("num大")
else:
print("num小")
运行结果
➜ python num.py
num大
如果num变量大于5,那么就输出num大于5,如果num变量小于5,那么就输出num小于5,否则就输出num等于5
➜ cat num2.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
num = 5
if num > 5:
print("num大于5")
elif num < 5:
print("num小于5")
else:
print("num等于5")
结果
➜ python num2.py
num等于5
定义一个变量count,默认为1,然后执行while循环,输出1~10
,当count大于10,退出
➜ cat while1.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
count = 1
print("Start...")
while count < 11:
print("The count is: ", count)
count += 1
print("End...")
➜ python while1.py
Start...
The count is: 1
The count is: 2
The count is: 3
The count is: 4
The count is: 5
The count is: 6
The count is: 7
The count is: 8
The count is: 9
The count is: 10
End...
跳出当前循环体
➜ cat break.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
count = 1
print("Start...")
while count < 5:
if count == 3:
break
print("The count is: ", count)
count += 1
print("End...")
➜ python break.py
Start...
The count is: 1
The count is: 2
End...
跳出本次循环,继续下一次循环
➜ cat continue.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
count = 1
print("Start...")
while count < 5:
if count == 3:
count += 1
continue
print("The count is: ", count)
count += 1
print("End...")
➜ python continue.py
Start...
The count is: 1
The count is: 2
The count is: 4
End...
if 1 == 1:
if 1 != 2:
if 1 < 1:
if 1 > 1:
if 1 == 1 and 1 > 0:
if 2 > 1 or 2 == 2:
if True:
if False:
Python的交互式输入使用的是input()
函数实现的,注意在Python2.7.x
版本的时候可以使用raw_input()
和input()
函数,但是在Python3.5.x
版本的时候就没有raw_input()
函数了,只能使用input()
例题:用户在执行脚本的时候,让他输入自己的名字,然后打印出来。
➜ cat name.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
username = input("请输入你的名字: ")
print("你的名字是:", username)
注意: 默认所有输入都是字符串
age = int(input("age" "))
# 强制字符串转换
练习¶
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
print("Start...")
count = 1
while count < 11:
print(count)
count += 1
print("End...")
执行结果
Start...
1
2
3
4
5
6
7
8
9
10
End...
思路:定义两个变量,分别是count
和num
,利用while语句循环输出1-100
,然后每次就让count + num
,这样循环一百次之后相加的结果就是1到100的和了。
代码
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
count = 1
num = 0
while count <= 100:
num += count
count += 1
print(num)
输出结果
5050
思路: 利用%整数相除的余,如果余数是1那么当前的count就是奇数,如果余0,那么当前的count就是偶数。
代码
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
count = 1
while count <= 100:
if count % 2 == 1:
print(count)
count += 1
代码
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
count = 1
while count <= 100:
if count % 2 == 0:
print(count)
count += 1
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
count = 1
while count < 100:
if count == 1:
num = count
elif count % 2 == 1:
num = num + count
elif count %2 == 0:
num = num - count
count += 1
print(num)
结果
50
其他方法:
li = [ x for x in range(1,100,2)] + [ -y for y in range(2,100,2)]
print(sum(li))
...
需求:写一个脚本,用户执行脚本的时候提示输入用户名和密码,如果用户名或者密码连续三次输入错误则退出,如果输入正确则显示登陆成功,然后退出。
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import getpass
# username yang
# password 111111
count = 3
while count > 0:
username = input("username: ").strip()
password = getpass.getpass("password: ")
if username == "yang" and password == "111111":
print("\033[34mWelcome %s \033[0m" % username)
break
count -= 1
print("\033[31mYou have {} times\033[0m".format(count))
账号或密码连续三次输入错误则退出程序,并且每次提醒用户剩余多少次登陆的机会。
python数据类型分类¶
- 不可变类型
- 数字
- 字符串
- 元组
- 不可变集合
- 可变类型
- 列表
- 字典
- 可变集合
我们学习的数据类型,只是在学习每一个类型所提供的API,我们所需要的大部分功能,python都已经帮我们封装好了,不需要担心效率的问题。
所有的数据类型所具备的方法都在相应的类里面.
对象是基于类的,也就是说如果我定义一个数据类型是字符串类型的,那么类型字符串就是类
,而定义的变量就是对象,对象所拥有的功能都是从类里面去拿的。
- 数字
- int(整型)
- long(长整型,python3.x里面都是int,没有long)
- float(浮点型)
- complex(复数)
- 布尔型(bool)
- 字符串(str)
- 列表(list)
- 元组(tuple)(不可变列表)
- 字典(dict)(无序)
- 集合(set)
可变类型与不可变类型¶
不可变类型是不支持修改源数据的,每次对不可变类型的数据进行修改时都是重新创建一个对象然后进行赋值
python中对象的赋值都是进行对象引用(内存地址)传递
后文会讲到 赋值, 以及深浅copy的区别
>>> s = "as"
>>> id(s)
4530159088
>>> s = s + ',as'
>>> id(s)
4530211600
>>> s
'as,as'
可变类型支持修改源数据,而不用重新创建新的对象
>>> L = [1,2]
>>> id(L)
4530063480
>>> L[0] = 2
>>> id(L)
4530063480
>>> L
[2, 2]
int类型¶
在python3中,整型、长整形、浮点数、负数等都可以称之为数字类型。
int(整型)
在32位机器上,整数的位数为32位,取值范围为-2**31~2**31-1
,即-2147483648~2147483647
在64位系统上,整数的位数为64位,取值范围为-2**63~2**63-1
,即-9223372036854775808~9223372036854775807
long(长整型)
跟C语言不同,Python的长整数没有指定位宽,即:Python没有限制长整数数值的大小,但实际上由于机器内存有限,我们使用的长整数数值不可能无限大。
注意,自从Python2.2起,如果整数发生溢出,Python会自动将整数数据转换为长整数,所以如今在长整数数据后面不加字母L也不会导致严重后果了。
float(浮点型)
浮点数用来处理实数,即带有小数的数字。类似于C语言中的double类型,占8个字节(64位),其中52位表示底,11位表示指数,剩下的一位表示符号。
complex(复数)
复数由实数部分和虚数部分组成,一般形式为x+yj,其中的x是复数的实数部分,y是复数的虚数部分,这里的x和y都是实数。
注:Python中存在小数字池:-5 ~ 257
创建int对象¶
int
类型通常都是数字,创建数字类型的方式有两种,且在创建的时候值
两边不要加双引号或者单引号(引起来的内容是字符串)。
第一种创建整型的方式
>>> number = 9
>>> type(number)
<type 'int'>
第二种创建整型的方式
>>> number = int(9)
>>> type(number)
<type 'int'>
以上两种方式都可以创建整型对象,但是它们也是有本质的区别,第一种方式实际上会转换成第二种方式,然后第二种方式会把括号内的数据交给__int__
这个构造方法,构造方法是int
类的,然后构造方法会在内存中开辟一块空间用来存放数据,但实际上我们在使用时没有任何区别。
图
def __init__(self, x, base=10): # known special case of int.__init__
通过源码可以看到,__int__的方法有两个参数,其中base=10是可选的参数,x是我们对象的值,base=10其实就是说把我们的值(默认二进制)以十进制的方式输出出来,通过下面的实例可以看到:
>>> var=int('0b100',base=2)
>>> var
4
通过int()
可以将一个数字的字符串变成一个整数,并且如果你指定了第二个参数,还可以将进制数转换为整数:
将数字字符串转换为整数,数字字符串通过进制转换为整数
>>> int('99'),int('100',8),int('40'),int('10000000',2)
(99, 64, 40, 128)
将进制数转换为整数
>>> int('0x40',16),int('0b1000000',2)
(64, 64)
把二进制的数字4通过十进制输出出来,4的二进制就是0b100
,又有一个知识点就是在类的方法中,所以有以__
开头,并且以__
结尾的方法都试python内部自己去调用的,我们在写代码的过程中是不需要去调用的,最简单的例子就是__init__
。
int内部优化机制¶
图
首先我们知道当我们创建第一个对象var1的时候会在内存中开辟一块空间作为存放var1对象的值用的,当我们创建第二个对象var2的时候也会在内存中开辟一块空间来作为var2对象的值。
这样的话对象var1和var2的值相等时内存是否会同时开辟两块?
➜ ~ python ## python版本
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
创建对象var1和var2
>>> var1 = 123
>>> var2 = 123
查看它们的内存地址,可以发现指向同一个地址
>>> id(var1)
4297558784
>>> id(var2)
4297558784
通过上的结果可以看到 var1
和 var2
的内存地址是相同的,就代表它们的值是使用的同一块空间,当我们把 var2
的值改为456
>>> var1
123
>>> var2
123
>>> var2 = 456
>>> id(var1)
4297558784
>>> id(var2)
4321202000
可以看到 var2
的内存地址变了,对象值不一样,所以他才会改变。
当两个或者多个对象的值都是同一个的时候,那么这些对象都会使用同一个内存地址,这里的值是有范围的,默认为
-5~257
图
>>> var1 = 123
>>> var2 = var1
>>> id(var1)
4297558784
>>> id(var2)
4297558784
>>> var1 = 456
>>> id(var1)
4321202000
>>> id(var2)
4297558784
在-5~257
这个范围内对象的值都会引用同一块内存地址
>>> var1 = 12345
>>> var2 = 12345
>>> id(var1)
4323812272
>>> id(var2)
4321202000
var1和var2的值同样是12345,但是它们的内存地址不一样,这就是python在内部做的优化
数字类型的长度限制¶
数字类型在python2.7
里面是分整型和长整形这个区别的,也就是说如果你的数字大到一定的范围,那么python会把它转换为长整形,一个数字类型包含32位,可以存储从-2147483648
到214483647
的整数。
一个长整(long)
型会占用更多的空间,64位的可以存储-922372036854775808
到922372036854775808
的整数。
python3里的long型已经不存在了,而int型可以存储到任意大小的整型,甚至超过64位。
Python内部对整数的处理分为普通整数和长整数,普通整数长度为机器位长,通常都是32位,超过这个范围的整数就自动当长整数处理,而长整数的范围几乎完全没限制,如下:
Python2.7.x
>>> var=123456
>>> var
123456
>>> var=10**20
>>> var
100000000000000000000L
>>> type(var)
# long就是长整型
<type 'long'>
Python3.5.x
>>> var=123456789
>>> var
123456789
>>> var=10**20
>>> var
100000000000000000000
>>> type(var)
<class 'int'>
数字类型所具备的方法¶
mac 或 Linux 在命令行可输入int.
然后连续按两个Tab
键,查看所有方法
bit_length
返回表示该数字时占用的最少位数
>>> num = 20
>>> num.bit_length()
5
conjugate
返回该复数的共轭复数,复数,比如0+2j,其中num.real,num.imag分别返回其实部和虚部,num.conjugate(),返回其共轭复数对象
>>> num = -20
>>> num.conjugate()
-20
>>> num = 0+2j
>>> num.real
0.0
>>> num.conjugate()
-2j
imag
返回复数的虚数
>>> num = 10
>>> num.imag
0
>>> number = 3.1415926
>>> number.imag
0.0
内置的方法还有 denominator
、 from_bytes
、 numerator
、
real
、 to_bytes
,不常用,可以通过 help(int.numerator)
类似方法查看相关的帮助信息。
混合类型¶
所谓混合类型就是浮点数和整数进行运算
>>> 3.1415926 + 10
13.1415926
浮点数如何和一个正整数相加?python会把两个值转换为其中最复杂的那个对象的类型,然后再对相同类型运算。
数字类型的复杂度¶
整数比浮点数简单、浮点数比复数简单。
布尔类型(bool)¶
布尔类型其实就是数字0和1的变种而来,即 真(True/1)
或
假(False/0)
,实际上就是内置的数字类型的子类而已。
集合(set)¶
集合的元素是不允许重复、不可变且无序的。
主要作用:
- 去重, 把一个列表变成集合,就自动去重了
- 关系测试, 测试两组数据之间的交集, 差集, 并集等关系
创建集合
>>> s = set([11,22,33])
>>> s
{33, 11, 22}
>>> type(s)
<class 'set'>
第二种不常用创建集合的方式
>>> s = {11,22,33}
>>> type(s)
<class 'set'>
>>> s
{33, 11, 22}
把其它可迭代的数据类型转换为set集合
>>> li = ["a","b","c"]
>>> se = set(li)
>>> se
{'a', 'b', 'c'}
>>> type(se)
<class 'set'>
>>>
集合同样支持表达式操作符
>>> x = set('abcde')
>>> y = set('bdxyz')
>>> x
{'d', 'a', 'b', 'c', 'e'}
>>> y
{'z', 'x', 'b', 'd', 'y'}
使用in进行成员检查
>>> 'a' in x
True
差集, 项在x中,但不在y中
>>> x - y
{'a', 'e', 'c'}
并集
>>> x | y
{'y', 'e', 'd', 'c', 'b', 'x', 'a', 'z'}
交集
>>> x & y
{'b', 'd'}
对称差集,项在x或y中, 但不会同时出现在二者中
>>> x ^ y
{'y', 'e', 'c', 'x', 'a', 'z'}
比较
>>> x > y
False
>>> x < y
False
>>> x > y , x < y
(False, False)
集合解析
>>> {x for x in 'abc'}
{'a', 'b', 'c'}
>>> {x+'b' for x in 'abc'}
{'ab', 'bb', 'cb'}
集合所提供的方法¶
var = se.copy()
# 返回集合的浅copy
集合var1
中存在,var2
中不存在的元素
>>> var1 = {11,22,33}
>>> var2 = {22,55}
>>> var1.difference(var2)
{33, 11}
>>> var2.difference(var1)
{55}
寻找集合var1中存在,var2中不存在的元素,并把查找出来的元素重新赋值给var1
>>> var1
{33, 11, 22}
>>> var2
{22, 55}
>>> var1.difference_update(var2)
>>> var1
{33, 11}
移除指定元素,不存在不报错
>>> var1 = {11,22,33}
>>> var1.discard(11)
>>> var1
{33, 22}
>>> var1.discard(112111)
>>> var1
{33, 22}
移除指定元素,不存在报错
>>> var1 = {11,22,33}
>>> var1
{33, 11, 22}
>>> var1.remove(11)
>>> var1
{33, 22}
>>> var1.remove(asdf)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'asdf' is not defined
交集,查找元素中都存在的值
>>> var1 = {11,22,33}
>>> var2 = {22,55,77}
>>> var1.intersection(var2)
{22}
取交集并更新到var1中
>>> var1 = {11,22,33}
>>> var2 = {22,55,77}
>>> var1.intersection_update(var2)
>>> var1
{22}
判断有没有交集,如果有返回False,否则返回True
>>> var1 = {11,22,33}
>>> var2 = {22,55,77}
>>> var1.isdisjoint(var2)
False
>>> var2 = {66,44,55}
>>> var1.isdisjoint(var2)
True
是否是子序列,也就是说如果var2的所有元素都被var1所包含,那么var2就是var1的子序列
>>> var1 = {11,22,33,44}
>>> var2 = {11,22}
>>> var2.issubset(var1)
True
是否是父序列
>>> var1 = {11,22,33}
>>> var2 = {22,44,55}
>>> var1.issuperset(var2)
False
>>> var2 = {11,22}
>>> var1.issuperset(var2)
True
移除一个元素,并显示移除的元素,移除时是无序的
>>> var1 = {11,22,33,44}
>>> var1.pop()
33
>>> var1
{11, 44, 22}
>>> var1.pop()
11
>>> var1
{44, 22}
对称交集,把var1存在且var2不存在和var2存在且var1不存在的元素合在一起
>>> var1 = {11,22,33,44}
>>> var2 = {11,55,66,44}
>>> var1.symmetric_difference(var2)
{33, 66, 22, 55}
对称交集,并更新到var1中
>>> var1 = {11,22,33,44}
>>> var2 = {11,55,66,44}
>>> var1
{33, 11, 44, 22}
>>> var1.symmetric_difference_update(var2)
>>> var1
{33, 66, 22, 55}
并集,把两个集合中的所有元素放在一起,如果有重复的则只存放一个
>>> var1 = {11,22,33,44}
>>> var2 = {11,55,66,44}
>>> var1.union(var2)
{33, 66, 11, 44, 22, 55}
更新,把一个集合中的元素更新到另一个集合中
>>> var1 = {11,22,33,44}
>>> var2 = {11,55,66,44}
>>> var1.update(var2)
>>> var1
{33, 66, 11, 44, 22, 55}
字符串(str)¶
字符串类型是Python的序列类型,他的本质就是字符序列,而且Python的字符串类型是不可改变的,你无法将原字符串进行修改,但是可以将字符串的一部分复制到新的字符串中,来达到相同的修改效果.
创建字符串类型¶
创建字符串类型可以使用单引号或者双引号又或者三引号来创建,实例如下:
单引号
>>> string = 'string'
>>> type(string)
<class 'str'>
双引号
>>> string = "yang"
>>> type(string)
<class 'str'>
>>> string
'yang'
三引号
>>> string = """yang"""
>>> type(string)
<class 'str'>
>>> string
'yang'
还可以指定类型
>>> var=str('string')
>>> var
'string'
>>> type(var)
<class 'str'>
字符串方法¶
每个类的方法其实都是很多的,无论我们在学习的过程中还是工作的时候,常用的没有多少,所以没必要去记那么多,只要对方法有印象就行了,需要的时候能够搜索到.
>>> string = "hello"
>>> string. ## 使用tab键查看所有方法
string.capitalize() # 首字母大写
string.center() # 内容居中
string.count() # 计数
string.encode() # 编码
string.endswith()
string.find()
string.format()
string.index()
string.isdigit()
string.join()
string.lower()
string.replace()
string.split()
string.startswith()
string.strip()
string.upper()
...
center¶
内容居中
width:字符串的总宽度;fillchar:填充字符,默认填充字符为空格
center(self,width,fillchar=None):
# 定义一个字符串变量,名为"string",内容为"hello world"
>>> string = "hello world"
# 输出这个字符创的长度,用len(value_name)
>>> len(string)
11
# 字符串的总宽度为11,填充的字符为"*"
>>> string.center(11,"*")
'hello world'
# 如果设置字符串的总长度为12,那么减去字符串长度11还剩下一个位置,这个位置就会被*所占用
>>> string.center(12,"*")
'hello world*'
>>> string.center(13,"*")
'*hello world*'
count¶
统计字符串里某个字符出现的次数,可选参数为字符串搜索的开始与结束位置
count(self,sub,start=None,end=None):
>>> string.count("l")
3
# 默认搜索出来的"l"是出现过三次的
>>> string="hello world"
>>> string.count("l")
3
# 如果指定从第三个位置开始搜索,搜索到第六个位置,"l"出现过一次
>>> string.count("l",3,6)
1
解码¶
decode(self,encoding=None,errors=None):
编码,针对Unicode¶
判断字符串是否是以指定后缀结尾,如果以指定后缀结尾返回TRUE,否则返回False
endswith(self,suffix,start=None,end=None)
参数 | 描述 |
---|---|
suffix | 后缀,可能是一个字符串,或者也可能是寻找后缀的tuple |
start | 开始,切片从这里开始 |
end | 结束,片到此为止 |
# 判断字符串是否以"d"结尾,如果是则返回"True"
>>> string = "hello world"
>>> string.endswith("d")
True
# 判断字符串是否以"t"结尾,不是则返回"False"
>>> string.endswith("t")
False
# 指定搜索的为止,实则是从字符串位置1到7来进行判断,如果第七个位置是"d",则返回True,否则返回False
>>> string.endswith("d",1,7)
False
>>>
把字符串中的tab符号(‘:raw-latex:`\t`’)转为空格,tab符号(‘:raw-latex:`\t`’)默认的空格数是8
expandtabs(self,tabsize=None)
检测字符串中是否包含字符串str,如果指定beg(开始)和end(结束)范围,则检查是否包含在指定范围内,如果包含子字符串返回开始的索引值,否则返回-1
元组¶
元组(tuple)和列表的唯一区别就是列表可以更改,元组不可以更改,其他功能与列表一样
创建元组的两种方法¶
ages = (11,22,33,44,55)
ages = tuple((11,22,33,44,55))
如果元组内只有一个元素,那么需要加上一个逗号,否则就变成其他类型了
>>> t = (1)
>>> t
1
>>> type(t)
<class 'int'>
>>>
>>> t = (1,)
>>> t
(1,)
>>> type(t)
<class 'tuple'>
元组所具备的方法¶
它只有2个方法,一个是count,一个是index
count(self, value)
查看元组中元素出现的次数
>>> ages = tuple((11,22,33,44,55))
>>> ages
(11, 22, 33, 44, 55)
>>> ages.count(11)
1
index(self, value, start=None, stop=None)
查找元素在元组中的位置
>>> ages = tuple((11,22,33,44,55))
>>> ages.index(11)
0
>>> ages.index(44)
3
元组生成器
>>> T = (1,2,3,4,5)
>>> (x * 2 for x in T)
<generator object <genexpr> at 0x101bd9af0>
>>> T1 = (x * 2 for x in T)
>>> T1
<generator object <genexpr> at 0x101bd9b48>
>>> for t in T1: print(t)
...
2
4
6
8
10
元组嵌套修改¶
元组的元素是不可更改的,但是元组内元素的元素就可能是可以更改的
>>> tup=("tup", ["list", {"name": "yang"}])
>>> tup
('tup', ['list', {'name': 'yang'}])
>>> tup[1]
['list', {'name': 'yang'}]
>>> tup[1].append("list_a")
>>> tup[1]
['list', {'name': 'yang'}, 'list_a']
元组的元素本身是不可修改的,但是如果元组的元素是个列表或者字典那么就可以被修改
切片原地修改不可变类型¶
>>> T = (1,2,3)
>>> T = T[:2] + (4,)
>>> T
(1, 2, 4)
列表¶
列表(list)同字符串一样都是有序的,因为他们都可以通过切片和索引进行数据访问,且列表是可变的
创建列表的几种方法¶
第一种
>>> name_list = ['python','php','java']
>>> name_list
['python', 'php', 'java']
第二种
>>> name_list = list(['python','php','java'])
>>> name_list
['python', 'php', 'java']
创建一个空列表
>>> li = list()
>>> type(li)
<class 'list'>
把一个字符串转换成一个列表
>>> var = "abc"
>>> li = list(var)
>>> li
['a', 'b', 'c']
list在把字符串转换成列表的时候,会把字符串用for循环迭代一下,然后把每个值当做list的一个元素
把一个元组转换成列表
>>> tup = ("a","b","c")
>>> li = list(tup)
>>> type(li)
<class 'list'>
>>> li
['a', 'b', 'c']
把字典转换成列表
>>> dic={"k1":"a","k2":"b","k3":"c"}
>>> li=list(dic)
>>> type(li)
<class 'list'>
>>> li
['k1', 'k3', 'k2']
字典默认循环的时候就是key,所以会把key当做列表的元素
>>> dic={"k1":"a","k2":"b","k3":"c"}
>>> li=list(dic.values())
>>> li
['a', 'c', 'b']
列表所提供的方法¶
tab键
>>> li.
li.clear() # 清除列表内所有元素
li.copy()
...
1. append(self,p_object) 在列表末尾添加新的对象
2. count(self,value) 统计某个元素在列表中出现的次数
3. extend(self,iterable) 用于在列表末尾一次性追加另一个序列
4. index(self,value,start=None,stop=None) 从列表中找出某个值第一个匹配项的索引位置
5. insert(self,index,p_object) 将制定对象插入列表
6. pop(self,index=None) 移除列表中的一个元素,并返回该元素的值
7. remove(self,value) 移除列表中某个值得第一个匹配项(删除元素还可以使用del,或者用切片赋值进行元素删除L[1:2]=[])
8. reverse(self) 反向输出列表中的元素
9. sort(self,cmp=None,key=None,reverse=False) 对原有列表进行排序,如果指定参数,则使用比较函数指定的比较函数
清除列表内所有元素
>>> li
['a', 'c', 'b']
>>> li.clear()
>>> li
[]
同字符串一样,列表也支持解析,称为列表解析
>>> li = [x for x in range(1,20)]
>>> li
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
列表操作¶
>>> names = ["yang","liu","zhao","qian","sun","wu"]
>>> names[1:4] # 取下标1至4之间的数字,包括1,不包括4 顾头不顾尾
['liu', 'zhao', 'qian']
>>> names[1:-1] # 取下标1至-1,不包括-1
['liu', 'zhao', 'qian', 'sun']
>>> names[0:3]
['yang', 'liu', 'zhao']
>>> names[:3] # 从头开始取,可以省略0,效果同上
['yang', 'liu', 'zhao']
>>> names[3:] # 如果想取最后一个,只能这么写
['qian', 'sun', 'wu']
>>> names[3:-1] # 这样 -1 不被包含
['qian', 'sun']
>>> names[0::2] # 后面的2代表步长
['yang', 'zhao', 'sun']
>>> names[::2] # 效果同上
['yang', 'zhao', 'sun']
>>> names
['yang', 'liu', 'zhao', 'qian', 'sun', 'wu']
>>> names.append("haha")
>>> names
['yang', 'liu', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> names
['yang', 'liu', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> names.insert(2,"插入到zhao前面")
>>> names
['yang', 'liu', '插入到zhao前面', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> names
['yang', 'liu', '插入到zhao前面', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> names[2] = "换人"
>>> names
['yang', 'liu', '换人', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> names
['yang', 'liu', '换人', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> del names[2]
>>> names
['yang', 'liu', 'zhao', 'qian', 'sun', 'wu', 'haha']
>>> names.remove("zhao") # 删除指定元素
>>> names
['yang', 'liu', 'qian', 'sun', 'wu', 'haha']
>>> names.pop() # 删除列表最后一个值
'haha'
>>> names
['yang', 'liu', 'qian', 'sun', 'wu']
>>> names
['yang', 'liu', 'qian', 'sun', 'wu']
>>> b = [1,2,3]
>>> names.extend(b)
>>> names
['yang', 'liu', 'qian', 'sun', 'wu', 1, 2, 3]
>>> names
['yang', 'liu', 'qian', 'sun', 'wu', 1, 2, 3]
>>> name_copy = names.copy()
>>> name_copy
['yang', 'liu', 'qian', 'sun', 'wu', 1, 2, 3]
>>> names
['yang', 'liu', 'qian', 'sun', 'wu', 1, 2, 3]
>>> names.insert(2,"wu")
>>> names.count("wu")
2
>>> names
['yang', 'liu', 'wu', 'qian', 'sun', 'wu', 1, 2, 3]
>>> names.sort()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str() # 3.x 不同数据类型不能放在一起排序
>>> names[-3] = '1'
>>> names[-2] = '2'
>>> names[-1] = '3'
>>> names
['liu', 'qian', 'sun', 'wu', 'wu', 'yang', '1', '2', '3']
>>> names.sort()
>>> names
['1', '2', '3', 'liu', 'qian', 'sun', 'wu', 'wu', 'yang']
>>>
>>> names.reverse() # 翻转
>>> names
['yang', 'wu', 'wu', 'sun', 'qian', 'liu', '3', '2', '1']
>>> names
['yang', 'wu', 'wu', 'sun', 'qian', 'liu', '3', '2', '1']
>>> names.index("wu")
1 # 返回找到的第一个下标
字典数据类型¶
字典(dict)在基本的数据类型中使用频率也是相当高的,而且它的访问方式是通过键来获取到对应的值,当然存储的方式也是键值对
了,属于可变类型
特性:
- dict是无序的
- key必须是唯一的,天生去重
创建字典的两种方法¶
第一种
>>> dic = {"k1":"123","k2":"456"}
>>> dic
{'k1': '123', 'k2': '456'}
>>> type(dic)
<class 'dict'>
第二种
>>> dic = dict ({"k1":"123","k2":"456"})
>>> dic
{'k1': '123', 'k2': '456'}
>>> type(dic)
<class 'dict'>
在创建字典的时候,__init__
初始化的时候还可以接受一个可迭代的变量作为值
>>> li = ["a","b","c"]
>>> dic = dict(enumerate(li))
>>> dic
{0: 'a', 1: 'b', 2: 'c'}
默认dict在添加元素的时候会把li列表中的元素for循环一遍,添加的时候列表中的内容是字典的值,而键默认是没有的,可以通过enumerate方法给他加一个序列,也就是键
与其变量不同的是,字典的键不仅仅支持字符串,而且还支持其他数据类型
# 数字
>>> D = {1:3}
>>> D[1]
3
# 元组
>>> D = {(1,2,3):3}
>>> D[(1,2,3)]
3
字典解析
>>> D = {x: x*2 for x in range(10)}
>>> D
{0: 0, 1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 14, 8: 16, 9: 18}
# 可以使用zip
>>> D = {k:v for (k,v) in zip(['a','b','c'],[1,2,3])}
>>> D
{'a': 1, 'b': 2, 'c': 3}
字典所提供的常用方法¶
>>> dict. # 使用tab键查看
1. clear(self) 删除字典中的所有元素
2. copy(self) 返回一个字典的浅复制
3. fromkeys(S,v=None) 创建一个新字典,以序列seq中元素做字典的键,value为字典所有键对应的初始值
4. get(self,k,d=None) 返回指定键的值,如果值不在字典中返回默认值
5. items(self) 以列表返回可遍历的(键,值)元组数组
6. keys(self) 以列表的形式返回一个字典所有的键
7. pop(self,k,d=None) 删除指定给定键所对应的值,返回这个值并从字典中把它移除
8. popitem(self) 随机返回并删除字典中的一对键和值,因为字典是无序的,没有所谓的"最后一项"或是其他顺序
9. setdefault(self,k,d=None) 如果key不存在,则创建,如果存在,则返回已存在的值且不修改
10. update(self,E=None,**F) 把字典dic2的键/值更新到dic1里 dic1.update(dic2)
11. values(self) 显示字典中所有的值
字典操作¶
info = {
'stu1101': "TengLan Wu",
'stu1102': "LongZe Luola",
'stu1103': "XiaoZe Maliya",
}
>>> info["stu1104"] = "苍井空"
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1101': 'TengLan Wu', 'stu1104': '苍井空'}
>>>
>>> info['stu1101'] = "武藤兰"
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1101': '武藤兰', 'stu1104': '苍井空'}
>>>
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1101': '武藤兰', 'stu1104': '苍井空'}
>>> info.pop("stu1101") # 标准删除
'武藤兰'
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1104': '苍井空'}
>>> del info["stu1103"] # 其他删除
>>> info
{'stu1102': 'LongZe Luola', 'stu1104': '苍井空'}
>>>
>>> info.popitem() # 随机删除
('stu1102', 'LongZe Luola')
>>> info
{'stu1104': '苍井空'}
>>> info = {'stu1102': 'LongZe Luola', 'stu1103': 'XiaoZe Maliya'}
>>> "stu1102" in info # 标准用法
True
>>> info.get("stu1102") # 获取,安全的获取
'LongZe Luola'
>>> info["stu1102"] # 同上,但是看下面,如果一个key不存在,则会报错,get不会,不存在只会返回None
'LongZe Luola'
>>> info["stu1105"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'stu1105'
av_catalog = {
"欧美":{
"www.youporn.com": ["很多免费的,世界最大的","质量一般"],
"www.pornhub.com": ["很多免费的,也很大","质量比yourporn高点"],
"letmedothistoyou.com": ["多是自拍,高质量图片很多","资源不多,更新慢"],
"x-art.com":["质量很高,真的很高","全部收费,屌比请绕过"]
},
"日韩":{
"tokyo-hot":["质量怎样不清楚,个人已经不喜欢日韩范了","听说是收费的"]
},
"大陆":{
"1024":["全部免费,真好,好人一生平安","服务器在国外,慢"]
}
}
>>> av_catalog["大陆"]["1024"][1] += ",可以用爬虫爬下来"
>>> print(av_catalog["大陆"]["1024"])
['全部免费,真好,好人一生平安', '服务器在国外,慢,可以用爬虫爬下来']
# values
>>> info = {'stu1102': 'LongZe Luola', 'stu1103': 'XiaoZe Maliya'}
>>> info.values()
dict_values(['XiaoZe Maliya', 'LongZe Luola'])
# keys
>>> info.keys()
dict_keys(['stu1103', 'stu1102'])
# setdefault
>>> info.setdefault("stu1106","xxxx")
'xxxx'
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1106': 'xxxx'}
>>> info.setdefault("stu1102","泷泽萝拉")
'LongZe Luola'
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1106': 'xxxx'}
# update
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': 'LongZe Luola', 'stu1106': 'xxxx'}
>>> b = {1:2,3:4,"stu1102":"泷泽萝拉"}
>>> info.update(b)
>>> info
{'stu1103': 'XiaoZe Maliya', 'stu1102': '泷泽萝拉', 3: 4, 'stu1106': 'xxxx', 1: 2}
# items
>>> info.items()
dict_items([('stu1103', 'XiaoZe Maliya'), ('stu1102', '泷泽萝拉'), (3, 4), ('stu1106', 'xxxx'), (1, 2)])
# 通过一个列表生成默认dict,有个没办法解释的坑,少用这个
>>> dict.fromkeys([1,2,3],'testd')
{1: 'testd', 2: 'testd', 3: 'testd'}
python 2.x
判断一个key是不是在字典里
d = {1:1}
d.has_key(1) # 返回 True 或 False
# 遍历字典key
for key in info:
print(key,info[key])
# 遍历字典值
# for v in info.values():
print(v)
# 遍历字典项
for k,v in info.items(): # 会先把dict转成list,数据大时莫用
print(k,v)
赋值与运算符¶
赋值¶
a = 123
运算符¶
假定a=10, b=20
运算符 | 描述 | 实例 |
---|---|---|
== |
等于,比较两个对象是否相等 | a==b 返回False |
!= |
不等于,比较两个对象是否不相等 | a!=b 返回 True |
<> |
不等于,比较两个对象是否不相等 | a<>b 返回 True |
> |
大于,比较x是否大于y | a>b 返回 False |
< |
小于,比较x是否小于y | a<b 返回 True |
>= |
大于等于,比较x是否大于等于y | a>=b 返回 False |
<= |
小于等于,比较x是否小于等于y | a<=b 返回 True |
运算符 | 描述 | 实例 |
---|---|---|
= |
赋值运算符 | c=a+b ,将a+b 的运算结果赋值给c |
+= |
加法赋值运算符 | c += a 等效于 c = c + a |
-= |
减法赋值运算符 | c -= a 等效于 c = c - a |
*= |
乘法赋值运算符 | c *= a 等效于 c = c *= a |
/= |
除法赋值运算符 | c /= a 等效于 c = c / a |
%= |
取模赋值运算符 | c %= a 等效于 c = c % a |
**= |
幂赋值运算符 | c **= a 等效于 c = c ** a |
//= |
取整除赋值运算符 | c //= a 等效于 c = c // a |
运算符 | 描述 | 实例 |
---|---|---|
and | “与” | a and b 返回 True |
or | “或” | a or b 返回 True |
not | “非” | no(a and b) 返回 False |
运算符 | 描述 |
---|---|
in | 如果在指定的序列中找到值返回True,否则返回False |
not in | 如果在指定的序列中没有找到值返回True,否则返回false |
& 按位与运算符 (a & b)输出结果12,二进制解释: 0000 1100
| 按位或运算符 (a | b)输出结果61,二进制解释: 0011 1101
^ 按位异或运算符 (a ^ b)输出结果49,二进制解释: 0011 0001
~ 按位取反运算符 (~a)输出结果-61,二进制解释: 1100 0011,在一个有符号二进制数的补码形式
<< 左移动运算符 a << 2 输出结果240,二进制解释: 1111 0000
>> 右移动运算符 a >> 2 输出结果15,二进制解释: 0000 1111
运算 | 解释 |
---|---|
spam=‘Spam’ | 基本形式 |
spam, ham=‘yum’, ‘YUM’ | 元组赋值运算 |
[spam, han]=[‘yum’, ‘YUM’] | 列表赋值运算 |
a,b,c,d=‘spam’ | 序列赋值运算,通用性 |
a, *b=‘spam’ | 扩展的序列解包 |
spam = ham = ‘hello’ | 多目标赋值运算 |
spams += 42 | 增强赋值运算 |
序列运算
>>> nudge = 1
>>> wink = 2
>>> A,B = nudge,wink
>>> A,B
(1, 2)
>>> A
1
>>> B
2
>>> ((a,b),c) = ('sp','am')
>>> a,b,c,
('s', 'p', 'am')
扩展的序列解包
一个列表赋给了带星号的名称,该列表收集了序列中没有赋值给其他名称的所有项.
先定义一个seq序列用于测试
>>> seq = [1,2,3,4]
a
匹配序列中的第一项,b
匹配剩下的内容
>>> a,*b = seq
>>> a,b
(1, [2, 3, 4])
b
匹配序列中的最后一项,a
匹配序列中最后一项前的所有内容
>>> *a,b = seq
>>> a,b
([1, 2, 3], 4)
第一项和最后一项分别赋值给了a
,c
,而b
获取了二者之间的所有内容
>>> a,*b,c = seq
>>> a,b,c
(1, [2, 3], 4)
带星号的名称可能只匹配单个的项,但是,总会向其赋值一个列表,如果没有剩下的内容可以匹配,那么会返回一个空列表
>>> a,b,c,*d = seq
>>> print(a,b,c,d)
1 2 3 [4]
>>> a,b,c,d,*e = seq
>>> print(a,b,c,d,e)
1 2 3 4 []
多目标赋值语句就是直接把内容,直接赋值给左侧的变量
>>> a = b = c = 'jie'
>>> a,b,c
('jie', 'jie', 'jie')
# 所引用的值也是同一个
>>> id(a),id(b),id(c)
(4324210760, 4324210760, 4324210760)
流程控制¶
if¶
if,条件判断,当满足不同的条件的时候执行不同的操作
if <条件一>:
<条件一代码块>
elif <条件二>:
<条件二代码块>
else:
<上面两个或者多个条件都不满足则运行这里的代码块>
举个🌰
➜ python_test cat if.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 提示用户输入一个数字,并将其赋值给变量n
n = int(input("Num: "))
if n > 10:
print("n > 10")
elif n == 10:
print("n = 10")
else:
print("n < 10")
三元运算¶
var = 值1 if 条件 else 值2
如果条件成立,把值1赋值给var,如果条件不成立,把值2赋值给var
>>> var = "True" if 1==1 else "False"
>>> var
'True'
for循环¶
循环控制语句,可用来遍历某一对象,还具有一个附带的可选的else块,主要用于处理for循环中包含的break语句
IndentationError: expected an indented block
>>> li = ['yang', 'jie']
>>> for n in range(len(li)):
... print(li[n])
...
yang
jie
enumerate¶
enumerate
函数用于遍历序列中的元素以及它们的下标
>>> li = ["computer", "note", "phone", "lalala"]
>>> for key,value in enumerate(li):
... print(key, value)
...
0 computer
1 note
2 phone
3 lalala
为了给用户良好的体验,需要从1开始,然后用户如果输出相对应的序列,那么就打印出序列对应的值
➜ python_test cat 008-1.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
li = ["computer","note","phone","lalala"]
# enumerate默认从0开始自增,可以改为从1开始自增
for key,value in enumerate(li,1):
print(key,value)
# 让用户选择商品的序列
li_num=input("请选择: ")
# print输出的时候让序列减1
print(li[int(li_num)-1])
执行效果如下
➜ python_test python3 008-1.py
1 computer
2 note
3 phone
4 lalala
请选择: 2
note
range和xrange¶
range()函数返回在特定区间的数字序列,range()函数的用法类似切片:range(start,stop,step),start的默认值为0,即从0开始,stop的参数是必须输入的,输出的最后一个数值是stop的前一个,step的默认值是1,即步长为1
>>> for n in range(5):
... print(n)
...
0
1
2
3
4
反向输出
>>> for n in range(5,-1,-1):
... print(n)
...
5
4
3
2
1
0
range在python2.7与3.5的差别
range在python2.7中,会把所有的序列都输出出来,每一个序列都在内存中占用空间
➜ ~ python
Python 2.7.10 (default, Oct 23 2015, 19:19:21)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> range(1,100)
[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]
>>>
xrange不会一次性把序列全部都存放在内存中,而是用到for循环进行迭代的时候才会一个一个的存到内存中,相当于python3.5中的range
>>> for i in xrange(10):
... print(i)
...
0
1
2
3
4
5
6
7
8
9
➜ ~ python3
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> range(1,100)
range(1, 100)
range在python3.5中不会一次性的占用那么多空间,它会在我们需要用到的时候再在内存中开辟一块空间给这个序列,不是一次性分配完,相当于python2.7中的xrange
while¶
while
循环不同于for
循环,while
循环是只要条件满足,那么就会一直运行代码块,否则就运行else
条件一代码块
while <条件>:
<代码块>
else:
<如果条件不成立执行这里的代码块>
举个🌰
➜ python_test cat 008-2-while.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
flag = True
while flag:
print(flag)
flag = False
else:
print('else',flag)
➜ python_test python3 008-2-while.py
True
else False
练习¶
有如下值集合[11,22,33,44,55,66,77,88,99,90],将所有大于66的值保存至字典的第一个key中,将小于66的值保存至第二个key的值中
即: {'k1':大于66的所有值,'k2':小于66的所有值}
➜ python_test cat 008-3-exercise-1.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
num = [11,22,33,44,55,66,77,88,99,90]
dict = {
'k1':[],
'k2':[]
}
for n in range(len(num)):
if num[n] <= 66:
dict['k1'].append(num[n])
else:
dict['k2'].append(num[n])
print(dict.get("k1"))
print(dict.get("k2"))
➜ python_test python3 008-3-exercise-1.py
[11, 22, 33, 44, 55, 66]
[77, 88, 99, 90]
查找列表中元素,移动空格,并查找以a或A开头,并且以c结尾的所有元素
li = ["alec", " aric", "Alex", "Tony", "rain"]
tu = ("alec", " aric", "Alex", "Tony", "rain")
dic = {'k1': "alex", 'k2': ' aric', "k3": "Alex", "k4": "Tony"}
列表的方式
➜ python_test cat 008-3-exercise-2.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
li = ["alec", " aric", "Alex", "Tony", "rain"]
for n in range(len(li)):
string = str(li[n]).replace(" ","").capitalize()
# 去掉左右两边的空格然后输出内容并且把首字母换成大写
# string = str(li[n]).strip().capitalize()
# 把字符串中的空格替换掉,然后首字母转换成大写
if string.find("A") == 0 and string.rfind("c") == len(string) - 1:
print(li[n],"--->",string)
➜ python_test python3 008-3-exercise-2.py
alec ---> Alec
aric ---> Aric
元组的方式
➜ python_test cat 008-3-exercise-3.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
tu = ("alec", " aric", "Alex", "Tony", "rain")
for n in range(len(tu)):
string = str(tu[n]).replace(" ","").capitalize()
if string.find("A") == 0 and string.rfind("c") == len(string) - 1:
print(tu[n],"--->",string)
字典的方式
➜ python_test cat 008-3-exercise-4.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
dic = {
'k1': "alex",
'k2': ' aric',
"k3": "Alex",
"k4": "Tony"
}
for key,val in dic.items():
string = str(val).replace(" ","").capitalize()
if string.find("A") == 0 and string.rfind("c") == len(string) - 1:
print(key,val,"--->",string)
➜ python_test python3 008-3-exercise-4.py
k2 aric ---> Aric
用户输入序号,显示用户选中的商品
商品
li = ["phone","computer","fish","wahaha"]
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
li = ["phone","computer","fish","wahaha"]
for key,value in enumerate(li,1):
print(key,".",value)
li_num = input ("please input your choice: ")
print(li[int(li_num)-1])
执行结果
➜ python_test python3 008-3-exercise-5.py
1 . phone
2 . computer
3 . fish
4 . wahaha
please input your choice: 1
phone
功能要求
要求用户输入总资产,例如:2000 显示商品列表,让用户根据序号选择商品,加入购物车 购买,如果商品总额大于总资产,提示账户余额不足,否则,购买成功 附加:可充值,某商品移除购物车
goods = [
{"name": "电脑", "price": 1999},
{"name": "鼠标", "price": 10},
{"name": "游艇", "price": 20},
{"name": "美女", "price": 998},
]
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
goods = [
{"name": "电脑", "price": 1999},
{"name": "鼠标", "price": 10},
{"name": "游艇", "price": 20},
{"name": "美女", "price": 998},
]
switch = "on"
gouwuche = {
"wuping":[],
"jiage":[]
}
# 用户输入卡内余额
while True:
try:
money = int(input("请输入卡内余额:"))
# 异常判断,如果类型不是整型则执行except内的代码
except ValueError:
# 提示用户输入的格式错误,让其重新输入
print("error,余额格式输入错误,请重新输入!\n例如: 5000")
# 中断当前循环让 循环重新执行
continue
break
# 进入购买商品的流程
while switch == "on":
# 打印出所有的商品
print("\n","序列","商品","单价")
# 以序列的方式输出现拥有的商品及商品单价
for num,val in enumerate(goods,1):
for n in range(len(goods)):
if num-1 == n:
print(" ",num,goods[n]["name"],goods[n]["price"])
# 判断用户输入的序列是否规范
while True:
try:
# 输入产品序列,类型转换为整型
x = int(input("请选择商品序列: "))
# 如果用户输入的非整型,提示用户重新输入
except ValueError:
print("error,请选择商品序列")
continue
# 如果用户输入的序列不在产品序列当中让用户重新输入
if x > num:
print("error,请选择商品的序列")
continue
break
# 输出购买物品的信息
print("您已经把商品",goods[x-1]["name"],"加入购物车","物品单价是: ",goods[x-1]["price"],"\n")
# 把物品名称放入gouwuche的wuping列表中
gouwuche["wuping"].append(goods[x-1]["name"])
# 把物品单价放入gouwuche的jiage列表中
gouwuche["jiage"].append(goods[x-1]["price"])
# 用户输入选项
while switch == "on":
# 输出现有选项
print("================\n查看购物车: p\n结算: w \n删除商品: d \n继续购买: n \n充值: i \n显示余额: m \n退出: q")
# 把用户输入的选项转换为字符串
xx = str(input("请输入您的选择: "))
if xx == "p":
for wp_num,val in enumerate(gouwuche["wuping"],1):
print(wp_num,val)
elif xx == "w":
zje = 0
for n in range(len(gouwuche["jiage"])):
zje += gouwuche["jiage"][n]
if zje > money:
print("sorry , 钱不够哦~~ \n")
else:
switch = "off"
print("购物愉快,您本次消费",zje,"RMB","剩余",money-zje,"RMB")
elif xx == "d":
for wu_num,val in enumerate(gouwuche["wuping"],1):
print(wp_num,val)
while True:
try:
delete = int(input("请选择要删除的商品序列: "))
except ValueError:
print("error,请输如正确的序列号!")
continue
if delete > wp_num:
print("error,请输如正确的序列号!")
continue
# 提示用户购物车内被删除的商品信息
print("您已经删除产品",gouwuche["wuping"],[delete - 1],"单价为: ",gouwuche["jiage"][delete - 1])
# 删除商品
gouwuche["wuping"].pop(delete - 1)
# 删除金额
gouwuche["jiage"].pop(delete - 1)
break
elif xx == "i":
while True:
try:
user = int(input("请输入您的账号: "))
except ValueError:
print("error,账号格式输入错误,请重新输入..")
continue
break
while True:
try:
pwd = int(input("请输入账号密码: "))
except ValueError:
print("error,格式错误,请重新输入...")
continue
break
if user == 123 and pwd == 123:
while True:
try:
newmoney = int(input("请输入充值的金额: "))
except ValueError:
print("error,金额格式错误,请重新输入..")
continue
break
money += newmoney
print("您已充值成功",newmoney,"RMB, 当前余额为: ",money," RMB \n")
else:
print("账号或密码错误\n")
elif xx == "m":
print("账户余额: ",money,"\n")
elif xx == "q":
switch = "off"
elif xx == "n":
break
else:
print("请输入正确的选项! ")
用户交互,显示省市县的选择
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
area = {
"湖北": {
"武汉":["汉口","武昌","汉阳"],
"咸宁":["崇阳","通城","白霓"]
},
"河南": {
"郑州市":["中原区","二七区","金水区"],
"洛阳市":["涧西区","西工区","老城区"]
},
"湖南": {
"长沙":["芙蓉区","岳麓区"],
"岳阳":["hello","哈哈"]
}
}
switch = "on"
print("\n====China====\n")
while switch == "on":
sheng = []
shi = []
zhen = []
while switch == "on":
for sheng_n,sheng_v in enumerate(area,1):
print(sheng_n,sheng_v)
sheng.append(sheng_v)
try:
sheng_inp = int(input("please input sheng: "))
except ValueError:
print("input error")
continue
if sheng_inp > sheng_n or sheng_inp == 0:
print("input error")
continue
print("\nChina --->",sheng[sheng_inp - 1],"\n")
break
while True:
ify = input("b back\nn continue\nq quit\n please input your choice: ")
if ify == "q" or ify == "b" or ify == "n":
break
else:
print("input error")
continue
if ify == "b":
continue
elif ify == "q":
switch = "off"
continue
while switch == "on":
for shi_n,shi_v in enumerate(area[sheng[sheng_inp - 1]],1):
print(shi_n,shi_v)
shi.append(shi_v)
try:
shi_inp = int(input("please input your choice "))
except ValueError:
print("input error")
continue
if shi_n < shi_inp or shi_inp == 0:
print("input error")
continue
print("\n China --->",sheng[sheng_inp - 1],"--->",shi[shi_inp - 1])
while True:
ify = input("b back\nn continue\nq quit\n please input your choice: ")
if ify == "q" or ify == "b" or ify == "n":
break
else:
print("input error")
continue
if ify == "b":
continue
elif ify == "q":
switch = "off"
continue
while switch == "on":
for zhen_n,zhen_v in enumerate(area[sheng[sheng_inp - 1]][shi[shi_inp - 1]],1):
print(zhen_n,zhen_v)
zhen.append(zhen_v)
try:
zhen_inp = int(input("please input your choice: "))
except ValueError:
print("input error")
continue
if zhen_n < zhen_inp or zhen_inp == 0:
print("input error")
continue
print("\n China --->",sheng[sheng_inp - 1],"--->",shi[shi_inp - 1],"--->",zhen[zhen_inp - 1])
while True:
ify = input("b back\nq quit\n please input your choice: ")
if ify == "q" or ify == "b":
break
else:
print("input error")
continue
if ify == "b":
continue
elif ify == "q":
switch = "off"
continue
执行结果
➜ python_test python3 008-3-exercise-sanjiliandong.py
====China====
1 湖北
2 河南
3 湖南
please input sheng: 1
China ---> 湖北
b back
n continue
q quit
please input your choice: n
1 武汉
2 咸宁
please input your choice 1
China ---> 湖北 ---> 武汉
b back
n continue
q quit
please input your choice: n
1 汉口
2 武昌
3 汉阳
please input your choice: 1
China ---> 湖北 ---> 武汉 ---> 汉口
b back
q quit
please input your choice: q
深浅拷贝¶
深浅拷贝分两部分,一部分是数字和字符串另一部分是列表,元组,字典等其他数据类型
数字和字符串¶
对于数字
和字符串
而言,赋值,浅拷贝和深拷贝无意义,因为他们的值永远都会指向同一个内存地址
>>> import copy
>>> var1 = 123
>>> id(var1)
4297558784
>>> var2 = var1
>>> id(var2)
4297558784
>>> var3 = copy.copy(var1)
>>> id(var3)
4297558784
>>> var4 = copy.deepcopy(var1)
>>> id(var4)
4297558784
其他数据类型¶
对于字典,元组,列表而言,进行赋值,浅拷贝和深拷贝时,其内存地址的变化是不同的
创建一个字典var1
var1 = {"k1": "1", "k2": 2, "k3": ["abc", 456]}
赋值¶
赋值,只是创建一个变量,该变量指向原来内存地址
>>> var1 = {"k1": "1", "k2": 2, "k3": ["abc", 456]}
>>> var2 = var1
>>> id(var1)
4321886600
>>> id(var2)
4321886600

python-010-1
浅拷贝¶
# 导入拷贝模块
>>> import copy
>>> var1 = {"k1":"1","k2":2,"k3":["abc",456]}
# 使用浅拷贝的方式
>>> var2 = copy.copy(var1)
# 两个变量的内存地址是不一样的
>>> id(var1)
4321886536
>>> id(var2)
4324453960
# 但是他们的元素内存地址是一样的
>>> id(var1["k1"])
4322821824
>>> id(var2["k1"])
4322821824
如图所示:

python-010-copy
import copy
a = ['a',['b',10]]
# 浅copy的三种方法
a1 = copy.copy(a)
a2 = a[:]
a3 = list(a)
a4 = copy.deepcopy(a)
a[1][0] = ["c"]
print(a)
print(a1)
print(a2)
print(a3)
print(a4)
运行结果
['a', [['c'], 10]]
['a', [['c'], 10]]
['a', [['c'], 10]]
['a', [['c'], 10]]
['a', ['b', 10]]
深拷贝¶
深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:python内部对字符串和数字的优化)
# 导入拷贝模块
>>> import copy
>>> var1 = {"k1":"1","k2":2,"k3":["abc",456]}
# 使用深拷贝的方式把var1的内容拷贝给var2
>>> var2 = copy.deepcopy(var1)
# var1和var2的内存地址是不相同的
>>> id(var1)
4321886600
>>> id(var2)
4324386696
# var1和var2的元素"k3"内存地址是不相同的
>>> id(var1["k3"])
4325328136
>>> id(var2["k3"])
4325328072
# var1和var2的"k3"元素的内存地址是相同的
>>> id(var1["k3"][1])
4322765872
>>> id(var2["k3"][1])
4322765872
如图所示

python-010-deepcopy
总结¶
- Python中对象的赋值都是进行对象引用(内存地址)传递
- 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
- 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
- 对于非容器类型(如数字、字符串、和其他’原子’类型的对象)没有被拷贝一说
- 如果元组变量只包含原子类型对象,则不能深拷贝,看下面的例子
函数¶
定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需要调用其函数名即可
特性
- 减少重复代码
- 使程序变得可扩展
- 使程序变得易维护
定义函数
# x为函数的参数
def sum(x,y): # x,y为形参 # 函数名
print(x+y)
sum(1,2) # 1,2 实参 # 调用函数
函数的返回值¶
函数的返回值需要使用到return
这个关键字,获取函数的执行结果.
注意:
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果
- 如果未在函数中指定return,那这个函数的返回值就是None
>>> def re():
... if 1==1:
... return True
... else:
... return False
...
>>> re()
True
函数return后面是什么值,re就返回什么值,如果没有指定return返回值,那么会返回一个默认的参数None
在函数中,当return
执行完成之后,return
后面的代码是不会执行的
>>> def re():
... print("123")
... return True
... print("abc")
...
>>> re()
123
True
函数参数¶
形参 : 变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元.因此,形参只在函数内部有效.函数调用结束返回主调函数后则不能再使用该形参变量.
实参 : 可以是常量,变量,表达式,函数等,无论实参是何种类型的量, 在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参.因此应预先用赋值,输入等方式使参数获得确定值
函数定义中定义参数默认值
如果我们在创建函数的时候给函数定义了参数,那么在调用函数的时候如果不填写参数,程序就会报错
>>> def re():
... print(x)
KeyboardInterrupt
>>> def re(x):
... print(x)
...
>>> re()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: re() missing 1 required positional argument: 'x'
>>>
要解决这个问题可以给函数的参数指定一个默认值,指定函数的默认值需要在def
这一行指定,指定之后,当调用这个函数时,不传入参数,就是使用参数默认值
>>> def ret(x="Hello World"):
... print(x)
...
>>> ret()
Hello World
# 如果指定默认值,那么实际参数替换掉形式参数
>>> ret("Pythoner")
Pythoner
传入参数的值是按照顺序依次赋值过去的
# x == 形式参数,形式参数有几个,那么实际参数就要传几个,默认参数除外
>>> def ret(x):
... print(x)
...
# "Hello World"实际参数
>>> print(ret("Hello World"))
Hello World
None
正常情况下,给函数传参数要按顺序,不想按顺序就可以用关键参数,只需指定参数名即可,但记住一个要求就是,关键参数必须放在位置参数之后。
stu_register(age=22,name=‘alex’,course=“python”,)
>>> def ret(a,b,c):
... print(a,"a")
... print(b,"b")
... print(c,"c")
...
>>> ret(b="bbb",a="aaa",c="ccc")
aaa a
bbb b
ccc c
默认情况在函数ret括号内如果要输入函数参数的值,是要按照顺序来的,但是如果在ret括号内制定的参数的值,那么就不需要按照顺序来了
如果给函数创建了默认值,那么有默认值的这个参数必须在最后面定义,不能够在没有默认参数的值的前面.
动态参数把接收过来的实际参数当做一个元组,每一个参数都是元组中的一个元素.
定义第一种动态参数需要在参数前面加上一个*
号
>>> def ret(*args): # *args 会把传入的参数变成一个元组形式
... print(args,type(args))
...
>>> ret(11,22,33)
(11, 22, 33) <class 'tuple'>
定义非固定关键字参数需要在参数前面加上两个*
号,给参数传参的时候是一个key对应一个value,相当于一个字典的键值对,而且返回的类型就是字典类型.
使用两个星号可以将参数收集到一个字典中,参数的名字是字典的键,对应参数的值是字典的值.
>>> def ret(**kwargs):
... print(kwargs,type(kwargs))
...
>>> ret(k1=123,k2=456)
{'k1': 123, 'k2': 456} <class 'dict'>
万能动态参数,可接受所有传参
>>> def ret(*args,**kwargs):
... print(args,type(args))
... print(kwargs,type(kwargs))
...
>>> ret(11,222,333,k1=111,k2=222)
(11, 222, 333) <class 'tuple'>
{'k1': 111, 'k2': 222} <class 'dict'>
字典🌰
>>> def arg(**kwargs):
... print(kwargs,type(kwargs))
...
>>> dic = {"k1":123,"k2":456}
>>> arg(k1=dic)
{'k1': {'k1': 123, 'k2': 456}} <class 'dict'>
>>> arg(**dic)
{'k1': 123, 'k2': 456} <class 'dict'>
如果不想在函数内部修改参数值而影响到外部对象的值,我们可以使用切片的方式进行参数的传递
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
L = ['a','b']
def changer(L):
L[0] = 0
print(L)
changer(L)
"""
['a','b']
[0,'b']
"""
# changer(L[:])
"""
['a','b']
['a','b']
"""
print(L)
In [2]: def f(a, b, c, d): print(a, b, c, d)
In [3]: args = (1, 2)
In [4]: args += (3, 4)
In [5]: f(*args)
1 2 3 4
又或者使用
def f(a, b, c, d): print(a, b, c, d)
args = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
f(**args)
函数调用中: 位置参数 -> 关键字参数 -> 元组形式 -> 字典形式
函数定义头部: 一般参数 -> 默认参数 -> 元组形式 -> 字典形式
>>> def func(name,age=None,*args,**kwargs):
... print(name,age,args,kwargs)
...
>>> func('yang',18,*(1,2,3),**{'blog':'xxx.com'})
yang 18 (1, 2, 3) {'blog': 'xxx.com'}
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def email(mail):
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
msg = MIMEText('邮件内容','plain','utf-8')
# 发件人信息,前面为 发件人,后面发件人邮箱
msg['From'] = formataddr(["测试",'brave0517@163.com'])
# 收件人
msg['To'] = formataddr(["aha",'adsda@163.com'])
# 主题
msg['Subject'] = "nihao"
server = smtplib.SMTP("smtp.163.com",25)
server.login("brave0517@163.com","xxxxxxxx")
server.sendmail('brave0517@163.com',[mail,],msg.as_string())
server.quit()
email("493535459@qq.com")
执行上面的脚本,会给邮箱493535459@qq.com
发送邮件
全局变量和局部变量¶
子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量.
全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序.
当全局变量与局部变量同名时
在定义局部变量的子程序内,局部变量起作用;在其他地方全局变量起作用.
# 全局变量
n1 = 1
def num():
# 局部变量
n2 = 2
print(n1)
print(n2)
num()
定义的全局变量都可以在函数内调用,但是不能在函数内修改,局部变量也不能够直接调用,如果要在函数内修改全局变量,那么就需要用到关键字global
n1 = 1
def num():
n2 = 2
global n1
n1 = 3
print(n1)
print(n2)
num()
nonlocal语句¶
nonlocal
是用来修改嵌套作用域中的变量,类似于global
,只需要在嵌套函数中声明变量名即可,但是这个变量名是必须已经存在的否则就会报错,如果要修改的变量在作用域中查找不到,那么不会继续到全局或内置作用域中查找
In [1]: def func1(arg1):
...: n = arg1
...: print(n)
...: def func2():
...: nonlocal n
...: n += 1
...: func2()
...: print(n)
...:
In [2]: func1(10)
10
11
递归¶
在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数.
递归特性
- 必须有一个明确的结束条件
- 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用时通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会增加一层帧,每当函数返回,栈就会减一层帧.由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
匿名函数(Lambda表达式)¶
Lambda(Lambda expressions)表达式是用lambda关键字创建的匿名函数,Lambda函数可以用于任何需要函数对象的地方,在语法上,它被局限于只能有一个单独的表达式
匿名函数主要是和其他函数搭配使用
使用Lambda
表达式创建函数
>>> f = lambda x,y : x+y
>>> f(1,2)
3
使用def创建函数
>>> def f(x,y):
... return x + y
...
>>> f(1,2)
3
---
res = map(lambda x:x**2,[1,5,7,4,8])
for i in res:
print(i)
输出
1
25
49
16
64
对于比较简单的函数我们可以通过lambda来创建,它的好处是缩短行数
lambda创建的函数和def创建的函数对应关系如图所示:

python-011-lambda
>>> def action(x):
... return (lambda y:x+y)
...
>>> act = action(99)
>>> print(act)
<function action.<locals>.<lambda> at 0x101ce08c8>
>>> result = act(2)
>>> print(result)
101
lambda
也能够获取到任意上层lambda
中的变量
>>> action = lambda x : (lambda y : x+y)
>>> act = action(99)
>>> print(act)
<function <lambda>.<locals>.<lambda> at 0x101ce07b8>
>>> print(act(3))
102
函数式编程介绍¶
函数是python內建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元.
函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射.也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态.比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的.
python对函数式编程提供部分支持.由于python允许使用变量,因此,python不是纯函数式变成语言.
定义:
简单说,“函数式编程”是一种“编程范式”(programming paradigm),也就是如何编写程序的方法论.
主要思想是把运算过程尽量写成一系列嵌套的函数调用.举例来说,现在有这样一个数学表达式:
(1+2)*3 - 4
传统的过程式编程,可能这样写
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样
var result = subtract(multiply(add(1,2),3),4);
这样代码再演进下,可以变成这样子
add(1,2).multiply(3).subtract(4)
这基本就是自然语言的表达了.再看下面的代码,大家应该一眼就能够明白它的意思吧
merge([1,2],[3,4]).sort().search("2")
因此,函数式编程的代码更容易理解.
要想学好函数式编程,不要玩py,玩Erlang,Haskell
高阶函数¶
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
def add(x,y,f):
return f(x) + f(y)
res = add(3,-6,abs)
print(res)
测试题¶
简述普通参数,指定参数,默认参数,动态参数的区别
普通参数即是用户在调用函数时填入的参数,且参数位置必须与参数保持一致
指定参数即在用户调用函数的时候不需要按照函数中参数的位置所填写,指定参数需要指定参数对应的值
默认参数可以写在定义参数的后面,如果用户调用函数时没有指定参数,那么就会用默认参数,如果用户指定了参数,那么用户指定的参数就会代替默认参数
动态参数可以接受用户输入的任何参数,包括字典,列表,元组等数据类型
计算传入字符串中数字,字母,空格以及其他字符的个数
➜ python_test cat 011-exercise-3.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def var(s):
all_num = 0
space_num = 0
digit_num = 0
others_num = 0
for i in s:
if i.isdigit():
digit_num += 1
elif i.isspace():
space_num += 1
elif i.isalpha():
all_num += 1
else:
others_num += 1
return("字母: ",all_num,"空格: ",space_num,"数字: ",digit_num,"其他字符: ",others_num)
num = var("13213 321 32 eaf adsf dasf dasf d4 4$%%&^%$*##@$#@$")
print(num)
执行结果
➜ python_test python3 011-exercise-3.py
('字母: ', 16, '空格: ', 8, '数字: ', 12, '其他字符: ', 15)
写函数,判断用户传入的对象(字符串,列表,元组)长度是否大于5,如果大于5就返回True,如果小于5就返回False
# 定义一个函数num
def num(x):
# 判断函数的值如果长度大于5就返回True
if len(x) > 5:
return True
# 否则就返回False
else:
return False
ret = num(["asd","asdasd","asdasd","asdasd"])
print(ret)
ret = num("asdasdasd")
print(ret)
写函数,判断用户传入的对象(字符串,列表,元组)的每一个元素是否含有空内容,如果有空就返回False
➜ python_test cat 011-exercise-4.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def num(x):
# 遍历x里面的所有内容
for n in x:
# 数据类型转换为字符串
n = str(n)
# 如果有空格就返回False
if n.isspace():
return False
ret = num(" ")
print(ret)
ret = num("adfa")
print(ret)
ret = num(["adsa","321",123," "])
print(ret)
➜ python_test python3 011-exercise-4.py
False
None
False
写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def num(x):
# 如果列表中的长度大于2,那么就输出列表前两个内容,否则就返回一个空
if len(x) > 2 :
return x[:2]
else:
return ""
print(num(["11","22","33"]))
print(num(["33"]))
➜ python_test python3 011-exercise-5.py
['11', '22']
➜ python_test
写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def num(x):
# 定义一个空列表用于接收奇数位索引的元素
result = []
# 循环输出列表中的所有元素值
for n in range(len(x)):
# 如果列表中的位置为奇数位索引就把值添加到result列表中
if n % 2 == 1:
result.append(x[n])
# 返回result列表中的内容
return result
ret = num([11,22,33,44,55,66])
print(ret)
➜ python_test python3 011-exercise-6.py
[22, 44, 66]
写函数,检查传入字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者
dic = {"k1":"v1v1","k2": [1111,22,33,44]}
PS:字典中的value只能是字符串或列表
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
dic = {"k1":"v1v1","k2": [1111,22,33,44]}
def dictt(x):
# 循环字典中所有的key
for k in x.keys():
# 如果字典中k对应的元素是字符串类型就进行下面的判断
if type(x[k]) == str:
# 如果元素的长度大于2
if len(x[k]) > 2:
# 那么就让这个元素重新赋值,新的值只保留原来值得前两个
x[k]=x[k][0:2]
# 如果字典中k对应的元素类型是列表,就进行下面的判断
elif type(x[k]) == list:
# 遍历列表中的值
for i in x[k]:
# 把元素赋值给string变量
string = str(i)
# 如果元素的长度大于2
if len(string) > 2:
# 获取元素的索引值
num = x[k].index(i)
# 先把这个元素给删除
x[k].pop(x[k].index(i))
# 然后再添加一个新的元素,新元素的值保留原来元素的前两个
x[k].insert(num,string[:2])
# 结果return出来
return dic
ret = dictt(dic)
print(ret)
➜ python_test python3 011-exercise-7.py
{'k2': ['11', 22, 33, 44], 'k1': 'v1'}
Python3内置函数¶
The Python interpreter has a number of functions and types built into it that are always available. They are listed here in alphabetical order.
内置函数详解¶
all()会循环括号内的每一个元素,如果括号内的所有元素都是真的,或者如果iterable为空,则返回True
,如果有一个为假,那么久返回False
>>> all([])
True
>>> all([1,2,3])
True
>>> all([1,2,""])
False
>>> all([1,2,None])
False
假的参数有:False、0、None、“”、[]、()、{}等,查看一个元素是否为假可以使用bool进行查看。
在对象的类中寻找__repr__
方法,获取返回值
>>> class Foo:
... def __repr_(self):
... return "hello"
...
>>> obj = Foo()
>>> r = ascii(obj)
>>> print(r)
<__main__.Foo object at 0x101a9fe80>
# 返回的是一个可迭代的对象
将整数x转换为二进制字符串,如果x不为python中int类型,x必须包含方法__index__()
并且返回值为integer
# 返回一个整数的二进制
>>> bin(999)
'0b1111100111'
>>> class myType:
... def __index__(self):
... return 35
...
>>> myvar = myType()
>>> bin(myvar)
'0b100011'
bytearray([source[,encoding[,errors]]])
返回一个byte数组,Bytearray类型是一个可变的序列,并且序列中的元素的取值范围为[0,255]
source参数:
- 如果source为整数,则返回一个长度为source的初始化数组;
- 如果source为字符串,则按照指定encoding将字符串转换为字节序列;
- 如果source为可迭代类型,则元素必须为[0,255]中的整数;
- 如果source为与buffer接口一致的对象,则此对象也可以被用于初始化bytearray.
>>> bytearray(3)
bytearray(b'\x00\x00\x00')
返回一个对象是否可以被执行
>>> def func():
... return 123
...
>>> callable(func)
True
>>> func = 123
>>> callable(func)
False
返回函数的类方法
compile(source,filename,mode,flags=0,dont_inherit=False,optimize=-1)
把字符串编译成python可执行的代码, 代码对象可以使用exec来执行或者eval()进行求值
>>> str = "for i in range(0,10):print(i)"
>>> c = compile(str,'','exec')
>>> exec(c)
0
1
2
3
4
5
6
7
8
9
complex([real[,imag]])
创建一个值为real+imag*j的复数或者转化一个字符串(或数字)为复数.如果第一个参数为字符串,则不需要指定第二个参数
>>> complex(1,2)
(1+2j)
# 数字
>>> complex(1)
(1+0j)
# 当做字符串处理
>>> complex("1")
(1+0j)
# 注意:这个地方在"+"号两边不能有空格,也就是不能写成"1 + 2j",否则会报错
>>> complex("1+2j")
(1+2j)
删除对象的属性值
>>> class cls:
... @classmethod
... def echo(self):
... print('CLS')
...
>>> cls.echo()
CLS
>>> delattr(cls,'echo')
>>> cls.echo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'cls' has no attribute 'echo'
>>>
创建一个数据类型为字典
>>> dic = dict({"k1":"123","k2":"456"})
>>> dic
{'k1': '123', 'k2': '456'}
返回一个对象中的所有方法
>>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
为元素生成下标
>>> li = ["a","b","c"]
>>> for n,k in enumerate(li):
... print(n,k)
...
0 a
1 b
2 c
eval(expression,globals=None,locals=None)
把一个字符串当做一个表达式去执行
>>> string = "1+3"
>>> string
'1+3'
>>> eval(string)
4
exec(object[,globals[,locals]])
把字符串当做python代码执行
>>> exec("for n in range(5):print(n)")
0
1
2
3
4
筛选过滤,循环可迭代的对象,把迭代的对象当做函数的参数,如果符合条件就返回True
,否则就返回False
>>> def func(x):
... if x == 11 or x == 22:
... return True
...
>>> ret = filter(func,[11,22,33,44])
>>> for n in ret:
... print(n)
...
11
22
>>> list(filter((lambda x : x > 0),range(-5,5)))
[1, 2, 3, 4]
将整数和字符串转换成浮点数
>>> float("123")
123.0
>>> float("123.45")
123.45
>>> float("-123.45")
-123.45
frozenset是冻结的集合,它是不可改变的,存在哈希值,好处是它可以作为字典的key,也可以作为其他集合的元素.缺点是一旦创建便不能更改,没有add,remove方法.
返回对象的命名属性的值,name
必须是字符串,如果字符串是对象属性之一的名称,则结果是该属性的值.
获取或修改当前文件内的全局变量
>>> a = "12"
>>> b = "434d"
>>> globals()
{'__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'b': '434d', '__spec__': None, '__name__': '__main__', '__package__': None, 'a': '12'}
参数是一个对象和一个字符串,如果字符串是对象的某个属性的名称,则结果为True,否则为False
查看一个类的所有详细方法,或者查看某个方法的使用详细信息
>>> help(list)
Help on class list in module builtins:
class list(object)
| list() -> new empty list
| list(iterable) -> new list initialized from iterable's items
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __contains__(self, key, /)
| Return key in self.
|
| __delitem__(self, key, /)
| Delete self[key].
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
......
交互式输入
>>> name = input("Please input your name: ")
Please input your name: yang
>>> print(name)
yang
获取一个数的十进制
>>> int("51")
51
也可以做进制转换
>>> int(10)
10
>>> int('0b11',base=2)
3
>>> int('0xe',base=16)
14
创建一个可迭代的对象
>>> obj = iter([11,22,33,44])
>>> obj
<list_iterator object at 0x101bfe048>
>>> for n in obj:
... print(n)
...
11
22
33
44
返回当前作用域的局部变量,以字典形式输出
>>> def func():
... name="yang"
... print(locals())
...
>>> func()
{'name': 'yang'}
对一个序列中的每一个元素都传到函数中执行并返回
>>> list(map((lambda x : x + 10),[11,22,33,44]))
[21, 32, 43, 54]
max(iterable,*[,key,default])
max(arg1,arg2,*args[,key])
取一个对象中的最大值
>>> li = [11,22,33,44]
>>> li
[11, 22, 33, 44]
>>> max(li)
44
返回对象obj的内存查看对象
>>> import struct
>>> buf = struct.pack("i"*12,*list(range(12)))
>>> x = memoryview(buf)
>>> y = x.cast('i',shape=[2,2,3])
>>> print(y.tolist())
[[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]
>>>
min(iterable,*[,key,default])
min(arg1,arg2,*args[,key])
取一个对象中的最小值
>>> li = list([11,22,33])
>>> min(li)
11
next(iterable[,default])
每次只拿取可迭代对象的一个元素
>>> obj = iter([11,22,33,44])
>>> next(obj)
11
>>> next(obj)
22
>>> next(obj)
33
>>> next(obj)
44
# 如果没有可迭代的元素,就会报错
>>> next(obj)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
返回一个新的无特征对象
open(file,mode=‘r’,buffering=-1,encoding=None,errors=None,newline=None,closefd=True,opener=None)
文件操作的函数,用来做文件操作
>>> f = open("a.txt","r")
print(*objects,sep=‘’,end=‘:raw-latex:`\n`’,file=sys.stdout,flush=False)
打印输出
>>> print("Hello World")
Hello World
properyt(fget=None,fset=None,fdel=None,doc=None)
生成一个序列
>>> range(10)
range(0, 10)
>>> for n in range(5):
... print(n)
...
0
1
2
3
4
对一个对象的元素进行反转
>>> li = [11,22,33,44]
>>> reversed(li)
<list_reverseiterator object at 0x101e96518>
>>> for n in reversed(li):
... print(n)
...
44
33
22
11
返回函数的静态方法
str(object=b’‘,encoding=’utf-8’,errors=‘strict’)
字符串
>>> a = str(111)
>>> type(a)
<class 'str'>
生成随机验证码例子¶
生成一个六位的随机验证码,且包含数字,数字的位置随机
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import random
temp = ""
for i in range(6):
num = random.randrange(0,4)
if num == 3 or num == 1:
rad2 = random.randrange(0,10)
temp += str(rad2)
else:
rad1 = random.randrange(65,91)
c1 = chr(rad1)
temp += c1
print(temp)
➜ python_test python3 012-exercise-2.py
81FZ62
Win字符编码深解¶
python基础¶
advance¶
文件操作¶
python可以对文件进行查看,创建等功能,可以对文件内容进行添加,修改,删除,且所使用到的函数在python3.5.x中为open
,在python2.7.x中同时支持file
和open
,但是在3.5.x系列移除了file
函数
对文件操作流程
- 打开文件,得到文件句柄并赋值给一个变量
- 通过句柄对文件进行操作
- 关闭文件
python文件打开方式¶
f = open('1.txt') # 打开文件
# 文件句柄 = open('文件路径','打开模式')
first_line = f.readline() # 读一行, 并赋值
print('first line:', first_line)
data = f.read() # 读取剩下的所有内容, 文件大时不要使用
print(data) # 打印内容
f.close() # 关闭文件
ps:文件句柄相当于变量名,文件路径可以写为绝对路径也可以写为相对路径
python打开文件的模式¶
基本的模式
模式 | 说明 | 注意事项 |
---|---|---|
r | 只读模式 | 文件必须存在 |
w | 只写模式 | 文件不存在则创建文件,文件存在则清空文件内容 |
x | 只写模式 | 文件不可读,文件不存在则创建,存在则报错 |
a | 追加模式 | 文件不存在创建文件,文件存在则在文件末尾添加内容 |
带+
的模式
模式 | 说明 |
---|---|
r+ | 读写 |
w+ | 写读 |
x+ | 写读 |
a+ | 写读 |
带b
的模式
模式 | 说明 |
---|---|
rb | 二进制读模式 |
wb | 二进制写模式 |
xb | 二进制只写模式 |
ab | 二进制追加模式 |
提示:以b方式打开文件时,读取到内容是字节类型,写入时也需要提供字节类型
带+
和b
的模式
模式 | 说明 |
---|---|
rb+ | 二进制读写模式 |
wb+ | 二进制读写模式 |
xb+ | 二进制只写模式 |
ab+ | 二进制读写模式 |
python文件读取方式¶
模式 | 说明 |
---|---|
read([size]) | 读取文件全部内容,如果设置了size,那么就读取size字节 |
readline([size]) | 一行一行的读取 |
readlines() | 读取到的每一行内容作为列表中的一个元素 |
测试文件名是hello.txt
,文件内容为:
Hello Word!
123
abc
456
abc
789
abc
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 以只读的方式打开文件hello.txt
f = open("hello.txt","r")
# 读取文件内容赋值给变量c
c = f.read()
# 关闭文件
f.close
# 输出c的值
print(c)
输出结果
➜ python_test python3 013-exercise-1-read.py
Hello Word!
123
abc
456
abc
789
abc
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 以只读模式打开文件hello.txt
f = open("hello.txt","r")
# 读取第一行
c1 = f.readline()
# 读取第二行
c2 = f.readline()
# 读取第三行
c3 = f.readline()
# 关闭文件
f.close()
# 输出读取文件第一行内容
print(c1)
# 输出读取文件第二行内容
print(c2)
# 输出读取文件第三行内容
print(c3)
执行结果
➜ python_test python3 013-exercise-2-readline.py
Hello Word!
123
abc
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
f = open("hello.txt","r")
c = f.readlines()
print(type(c))
f.close()
for n in c:
print(n)
执行结果
➜ python_test python3 013-exercise-3-readlines.py
<class 'list'>
Hello Word!
123
abc
456
abc
789
abc
python文件写入方式¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 以只写的模式打开文件write.txt,没有则创建,有则覆盖内容
file = open("write.txt","w")
# 向文件中写入内容
file.write("test write")
# 关闭文件
file.close()
write.txt
文件内容
➜ python_test python3 013-exercise-4-write.py
➜ python_test cat write.txt
test write
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
f = open("wr_lines.txt","w",encoding="utf-8")
f.writelines(["11","22","33"])
f.close
wr_lines.txt
文件内容
➜ python_test python3 013-exercise-5-writelines.py
➜ python_test cat wr_lines.txt
112233
python文件操作所提供的方法¶
文件描述符
>>> f = open("hello.txt","r")
>>> ret = f.fileno()
>>> f.close()
>>> print(ret)
3
判断文件是否是tty设备,如果是tty设备则返回True
,否则返回False
>>> f = open("hello.txt","r")
>>> ret = f.isatty()
>>> f.close()
>>> print(ret)
False
是否可读,如果可读返回True
,否则返回False
>>> f = open("hello.txt","r")
>>> ret = f.readable()
>>> f.close()
>>> print(ret)
True
每次仅读一行数据
>>> f = open("hello.txt","r")
>>> print(f.readline())
Hello Word!
>>> print(f.readline())
123
>>> f.close()
把每一行内容当做列表中的一个元素
>>> f = open("hello.txt","r")
>>> print(f.readlines())
['Hello Word!\n', '123\n', 'abc\n', '456\n', 'abc\n', '789\n', 'abc\n']
>>> f.close()
指定文件中指针位置
>>> f = open("hello.txt","r")
>>> print(f.tell())
0
>>> f.seek(3)
3
>>> print(f.tell())
3
>>> f.close()
写入文件的字符串序列,序列可以是任何迭代的对象字符串生产,通常是一个字符串列表
>>> f = open("wr_lines.txt","w")
>>> f.writelines(["11","22","33"])
>>> f.close()
读取指定字节数据,后面不加参数默认读取全部
>>> f = open("wr_lines.txt","r")
>>> print(f.read(3))
112
>>> f.seek(0)
0
>>> print(f.read())
112233
>>> f.close()
往文件里面写内容
>>> f = open("wr_lines.txt","w")
>>> f.write("abcdef")
6
>>> f.close()
文件内容
➜ python_test cat wr_lines.txt
abcdef
with语句¶
为了避免打开文件后忘记关闭,可以通过管理上下文,即
with open('log','r') as f:
代码块
如此方式,当with代码块执行完毕时,内部会自动关闭并释放文件资源
在python2.7及以后,with又支持同时对多个文件的上下文管理,即
with open('log1') as obj1, open('log2') as obj2:
pass
递归¶
递归就是函数调用本身,知道满足指定条件之后,一层层退出函数
利用函数编写一个斐波那契数列,斐波那契数列就是前面两个数相加得到后面一个数,依次往后
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def counter(n1,n2):
# 当要计算的值大于10000就退出
if n1 > 10000:
return
# 输出当前计算到的值
print("Counter: ",n1)
n3 = n1 + n2
counter(n2,n3)
counter(0,1)
执行结果
➜ python_test python3 014-exercise-1.py
Counter: 0
Counter: 1
Counter: 1
Counter: 2
Counter: 3
Counter: 5
Counter: 8
Counter: 13
Counter: 21
Counter: 34
Counter: 55
Counter: 89
Counter: 144
Counter: 233
Counter: 377
Counter: 610
Counter: 987
Counter: 1597
Counter: 2584
Counter: 4181
Counter: 6765
利用递归获取斐波那契数列中的第10个数,并将该值返回给调用者
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def counter(index,start,end):
print("times: %d, num 1st: %d ,num 2nd: %d" % (index,start,end))
if index == 10:
return start
N = start + end
Number = counter(index + 1,end,N)
return Number
result = counter(1,0,1)
print("num: ",result)
执行结果
➜ python_test python3 014-exercise-2.py
times: 1, num 1st: 0 ,num 2nd: 1
times: 2, num 1st: 1 ,num 2nd: 1
times: 3, num 1st: 1 ,num 2nd: 2
times: 4, num 1st: 2 ,num 2nd: 3
times: 5, num 1st: 3 ,num 2nd: 5
times: 6, num 1st: 5 ,num 2nd: 8
times: 7, num 1st: 8 ,num 2nd: 13
times: 8, num 1st: 13 ,num 2nd: 21
times: 9, num 1st: 21 ,num 2nd: 34
times: 10, num 1st: 34 ,num 2nd: 55
num: 34
装饰器¶
装饰器是由函数去生成的,用于装饰某个函数或者方法或者类,他可以让这个函数在执行之前或者执行之后做一些操作
实例¶
定义一个函数func
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def func(arg):
print(arg)
func("Hello World!")
执行脚本,输出的结果为
Hello World!
现在,要在执行func
这个函数前后执行一些操作,就可以创建一个装饰器来实现
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 创建一个装饰器函数,接受的参数arg参数就是func函数名
def decorator(func):
def inner(*args,**kwargs):
print("before exec func: ")
ret = func(*args,**kwargs)
print("after exec func: ")
return ret
return inner
# 如果要让某个函数使用装饰器,只需要在这个函数上面加上@装饰器名字
@decorator
def func(arg):
print(arg)
func("Hello World!")
输出结果
before exec func:
Hello World!
after exec func:
实例2
import time
def timer(func):
def deco():
start_time = time.time()
func()
stop_time = time.time()
print('the func run time is %s' % (stop_time - start_time))
return deco
@timer
def test1():
time.sleep(3)
print('test1')
# @timer
# def test2(name):
# print("test:", name)
test1()
# test2()
把注释内容取消注释, 会报错, 这是因为这个装饰器不支持参数, 后文会讲到带参数的装饰器.
多个装饰器装饰同一个函数¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def decorator1(func):
def inner():
print("decorator1 >>> start")
ret = func()
print("decorator1 >>> end")
return ret
return inner
def decorator2(func):
def inner():
print("decorator2 >>> start...")
ret = func()
print("decorator2 >>> end...")
return ret
return inner
@decorator1
@decorator2
def index():
print("执行函数...")
index()
执行结果
➜ python_test python3 015-exercise-3.py
decorator1 >>> start
decorator2 >>> start...
执行函数...
decorator2 >>> end...
decorator1 >>> end
更多实例¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
"""
函数装饰器
"""
def decorator(func):
def wrapped(*args,**kwargs):
return func(*args,**kwargs)
return wrapped
@decorator
def func(a,b):
return a + b
print(func(1,2))
"""
类装饰器
"""
class decorator:
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
return self.func(*args,**kwargs)
@decorator
def func(a,b):
return a + b
print(func(1,2))
"""
带参数的函数装饰器
"""
def parameter(a,b):
print(a,b)
def decorator(func):
def wrapped(*args,**kwargs):
return func(*args,**kwargs)
return wrapped
return decorator
@parameter(1,2)
def func(a,b):
return a + b
print(func(10,20))
"""
带参数的类装饰器
"""
def parameter(a,b):
print(a + b)
class decorator:
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
return self.func(*args,**kwargs)
return decorator
@parameter(1,2)
def func(a,b):
return a + b
print(func(10,20))
"""
带参数的类装饰器
"""
def parameter(a,b):
print(a,b)
def decorator(cls):
class wrapped:
def __init__(self,*args,**kwargs):
self.cls = cls(*args,**kwargs)
def __getattr__(self,item):
return getattr(self.cls,item)
return wrapped
return decorator
@parameter(1,2)
class CLS:
def __init__(self):
self.a = 'a'
def P(self,v):
print(v)
obj = CLS()
print(obj.a)
obj.P('Hello')
"""
为函数中和类中的方法添加装饰器
"""
def Call(aClass):
calls = 0
def onCall(*args,**kwargs):
nonlocal calls
calls +=1
print('call %s to %s' % (calls,func.__name__))
return aClass(*args,**kwargs)
return onCall
@Call
def func(a,b):
return a + b
print(func(1,2))
class CLS:
def __init__(self):
self.a = 'a'
@Call
def b(self):
return self.a
obj = CLS()
print(obj.b())
执行结果
➜ python_test python3 015-exercise-4.py
3
3
1 2
30
3
30
1 2
a
Hello
call 1 to onCall
3
call 1 to onCall
a
字符串格式化¶
This PEP proposes a new system for built-in string formatting operations, intended as a replacement for the existing ‘%’ string formatting operator.
python目前提供的字符串格式化方法有两种:
- 百分号方式
- format方式
这两种方式在python2
和python3
中都适用,百分号方式是python一直内置存在的,format
方式为近期才出的.
旧式格式化¶
参数格式: %[(name)][flags][width].[precision]typecode
[(name)]
可选,用于选择指定key
[flags]
可选,可供选择的值:
值 | 说明 |
---|---|
右对齐;正数前加正号,负数前加负号 | |
左对齐;正数前无符号,负数前加负号 | |
space | 右对齐;正数前加空格,负数前加负号 |
0 | 右对齐;正数前无符号,负数前加负号;用0填充空白处 |
[width]
可选,占有宽度
.[precision]
可选,小数点后保留的位数
typecode
必选,参数如下:
注:python百分号格式化是不存在自动将整数转换成二进制表示的方法
格式化实例¶
常用字符串格式化方式
>>> string = "My name is : %s" % ("yang")
>>> string
'My name is : yang'
字符串中出现%
的次数要与%
之后所提供的数据项个数相同,如果需要插入多个数据,则需要将他们封装进一个元组
>>> string = "My name is: %s ,I am %d years old" % ("yang",18)
>>> string
'My name is: yang ,I am 18 years old'
给参数起一个名字,后面传值得时候必须是一个字典
>>> string = "My name is: %(name)s ,I am %(age)d years old" % {"name":"yang","age":18}
>>> string
'My name is: yang ,I am 18 years old'
去浮点数后面的位数
>>> string = "percent %.2f" % 99.9121
>>> string
'percent 99.91'
给浮点数起一个名字(key)
>>> string = "percent %(p).2f" % {"p":99.232}
>>> string
'percent 99.23'
两个百分号代表一个百分号
>>> string = "percent %(p).2f%%" % {"p":99.232}
>>> string
'percent 99.23%'
使用{}和format的新格式化¶
[[fill]align][sign][#][0][width][,][.precision][type]
[fill]
可选,空白处填充的字符
align
可选,对其方式(需配合width使用)
[sign]
可选,有无符号数字
参数 | 说明 |
---|---|
正号加正,负号加负 | |
证号不变,负号加负 | |
space | 正号空格,负号加负 |
[#]
可选,对于二进制,八进制,十六进制,如果加上#,会显示0b/0o/0x,否则不显示
[,]
可选,为数字添加分隔符,如: 1,000,000
[width]
可选,格式化位所占宽度
[.precision]
可选,小数位保留精度
[type]
可选,格式化类型
传入“字符串类型”的参数
参数 | 说明 |
---|---|
s | 格式化字符串类型数据 |
空白 | 未指定类型,则默认是None,同s |
传入“整数类型”的参数
参数 | 说明 |
---|---|
b | 将十进制整数自动转换成二进制表示然后格式化 |
c | 将十进制整数自动转换成其对应Unicode字符 |
d | 十进制整数 |
o | 将十进制整数自动转换成8进制表示然后格式化 |
x | 将十进制整数自动转换成十六进制表示然后格式化(小写x) |
X | 将十进制整数自动转换成十六进制表示然后格式化(大写X) |
传入“浮点型或小数类型”的参数
参数 | 说明 |
---|---|
e | 转换为科学计数法(小写e)表示,然后格式化 |
E | 转换为科学计数法(大写E)表示,然后格式化 |
f | 转换为浮点型(默认小数点后保留6位)表示,然后格式化 |
F | 转换为浮点型(默认小数点后保留6位)表示,然后格式化 |
g | 自动在e和f中切换 |
G | 自动在e和f中切换 |
% | 显示百分比(默认显示小数点后6位) |
format格式化实例¶
第一种基本format格式化方式
>>> string = "My name is: {},I am {} years old, {} Engineer".format("yang",18,"python")
>>> string
'My name is: yang,I am 18 years old, python Engineer'
第二种基本format格式化方法
>>> string = "My name is: {},I am {} years old, {} Engineer".format(*["yang",18,"python"])
>>> string
'My name is: yang,I am 18 years old, python Engineer'
给传入的参数加一个索引
>>> string = "My name is:{0},I am {1} years old, {0} Engineer".format(*["yang",18,"python"])
>>> string
'My name is:yang,I am 18 years old, yang Engineer'
给参数起一个名字(key)
>>> string = "My name is:{name},I am {age} years old, {job} Engineer".format(name="yang",age=18,job="python")
>>> string
'My name is:yang,I am 18 years old, python Engineer'
字典的方式
>>> string = "My name is:{name},I am {age} years old, {job} Engineer".format(**{"name":"yang","age":18,"job":"python"})
>>> string
'My name is:yang,I am 18 years old, python Engineer'
索引内的索引
>>> string = "My name is:{0[0]},I am {0[1]} years old, {0[2]} Engineer".format(["yang",18,"python"],["Ya",11,"IT"])
>>> string
'My name is:yang,I am 18 years old, python Engineer'
指定参数类型
>>> string = "My name is:{:s},I am {:d} years old, {:f} wage".format("yang",18,32424.324)
>>> string
'My name is:yang,I am 18 years old, 32424.324000 wage'
指定名称(key)的值类型
>>> string = "My name is: {name:s},I am {age:d} years old".format(name="yang",age=18)
>>> string
'My name is: yang,I am 18 years old'
异类实例
>>> string = "numbers: {:b},{:o},{:d},{:x},{:X}, {:%}".format(15,15,15,15,15,15.87232,2)
>>> string
'numbers: 1111,17,15,f,F, 1587.232000%'
显示颜色¶
# 字体颜色
\033[30m 黑色字 \033[0m
\033[31m 红色字 \033[0m
\033[32m 绿色字 \033[0m
\033[33m 黄色字 \033[0m
\033[34m 蓝色字 \033[0m
\033[35m 紫色字 \033[0m
\033[36m 天篮字 \033[0m
\033[37m 白色字 \033[0m
# 底色
\033[40m \033[0m
\033[41m \033[0m
\033[42m \033[0m
\033[43m \033[0m
\033[44m \033[0m
\033[45m \033[0m
\033[46m \033[0m
\033[47m \033[0m
\033[40m 黑底色 \033[0m
\033[41m 红底色 \033[0m
\033[42m 绿底色 \033[0m
\033[43m 黄底色 \033[0m
\033[44m 蓝底色 \033[0m
\033[45m 紫底色 \033[0m
\033[46m 天篮底色 \033[0m
\033[47m 白底色 \033[0m
列表生成式,迭代器与生成器¶
列表生成式¶
需求
把列表里[0,1,2,3,4,5,6,7,8,9]每个值加1,如何实现?
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> b = []
>>> for i in a:b.append(i+1)
...
>>> b
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = b
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 原值修改
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for index,i in enumerate(a): a[index] += 1
...
>>> print(a)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a = map(lambda x:x+1, a)
>>> a
<map object at 0x101be8128>
>>> for i in a:print(i)
...
1
2
3
4
5
6
7
8
9
10
还有一种写法:
>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这就叫列表生成
生成器¶
仅仅拥有生成某种东西的能力,如果不用__next__
方法是获取不到值的.
通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定有限.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了.
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量空间.在python中,这种一边循环一遍计算的机制,称为生成器: generator
生成器, 只有在调用时才会生成相应的数据.
创建生成器
()
yield
要创建一个generator,有很多种方法,第一种方法很简单,只需要把一个列表生成式的[]
改成()
,就创建了一个generator
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x101bd9e08>
创建L和g的区别仅在于最外层的[]
和 ()
,
L是一个list,而g是一个generator
我们可以直接打印出list的每一个元素,我们如何打印出generator的每一个元素?
使用next()
函数,获得generator的下一个返回值
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
generator保存的是算法那,每次调用next(g),就计算出g的下一个元素的值,知道计算到最后一个元素,没有更多的元素时,抛出一个StopIteration
的错误.
使用next()
实在是太变态了,正确的方法是使用for循环,因为generator也是可以迭代的对象
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
但是用for循环调用generator时,发现拿不到generator的return语句的返回值.如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的Value中.
>>> g = (x * x for x in range(5))
>>> while True:
... try:
... x = next(g)
... print('g: ',x)
... except StopIteration as e:
... print("Generator return value: ",e.value)
... break
...
g: 0
g: 1
g: 4
g: 9
g: 16
Generator return value: None
另一种创建方式,使用yield
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
>>> def scq():
... print("11")
... yield 1
... print("22")
... yield 2
... print("33")
... yield 3
...
把生成器赋值给一个对象
>>> r = scq()
查看r的数据类型并且输出r的值
>>> print(type(r),r)
<class 'generator'> <generator object scq at 0x101bd9eb8>
当执行生成器的__next__
的时候,代码会按照顺序去执行,当执行到yield
时会返回并退出,yield
后面的值就是返回值,然后记录代码执行的位置,并退出.
>>> ret = r.__next__()
11
第二次执行的时候会根据上次代码执行的位置继续往下执行
>>> ret = r.__next__()
22
>>> ret = r.__next__()
33
如果__next__
获取不到值得时候就会报StopIteration
错误
>>> ret = r.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
利用生成器创建一个类似xrange
的功能
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 创建一个生成器函数,函数名为range,n是传入的参数,也是输出的数的最大值
def range(n):
# 默认从0开始
start = 0
# 进入while循环,如果最小值小于最大值就进入循环
while start < n:
# 第一次返回start,下面代码不执行
yield start
# 第二次进来的时候start += 1 ,然后进入下一次循环
start += 1
# 停止的参数为5
obj = range(5)
# 第一个数赋值给n1
n1 = obj.__next__()
# 第一个数赋值给n2
n2 = obj.__next__()
n3 = obj.__next__()
n4 = obj.__next__()
n5 = obj.__next__()
# 输出这五个数
print(n1,n2,n3,n4,n5)
执行结果
➜ python_test python3 018-exercise-1.py
0 1 2 3 4
通过生成器实现协程并行运算
#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
def producer(name):
c = consumer('A')
c2 = consumer('B')
c.__next__()
c2.__next__()
print("老子开始准备做包子啦!")
for i in range(10):
time.sleep(1)
print("做了2个包子!")
c.send(i)
c2.send(i)
producer("alex")
#列表解析
sum([i for i in range(100000000)])#内存占用大,机器容易卡死
#生成器表达式
sum(i for i in range(100000000))#几乎不占内存
迭代器¶
可直接用作for循环的数据类型有以下几种:
一类是集合数据类型,如list,tuple,dict,set,str等
一类是generator,包括生成器和带yield的generator function
这些可以直接作用于for循环的对象统称为可迭代对象: Iterable
可以使用isinstance()
判断一个对象是否是Iterable
对象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance("abc", Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100,Iterable)
False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了.
可以被next()函数调用并不断返回下一个值的对象称为迭代器: Iterator
具有访问生成器的能力,可以访问到生成器的值,类似于生成器的__next__
方法,一个值一个值的去迭代,只能够按照顺序的去查找
可以使用isinstance()判断一个对象是否是Iterator对象:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance("abc", Iterator)
False
生成器都是Iterator对象,但是list,dict,str虽然是Iterable,却不是Iterator
把list,dict,str等Iterable
变成Iterator
可以使用iter()
函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
python的Iterator
对象表示的是一个数据流,Iterator
对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误.可以把这个数据流看作是一个有序序列,但我们却不能提前直到序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时才会计算.
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
特点:
- 访问者不需要关心迭代器内部的结果,仅需要通过next()方法不断去取下一个内容
- 不能随机访问集合中的某个值,只能从头到尾依次访问
- 访问到一半时不能往回退
- 便于循环比较大的数据集合,节省内存
优化上面range
或xrange
的生成器
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def irange(start,stop,step=1):
while start != stop:
yield start
start += step
else:
raise StopIteration
for n in irange(1,10):
"""for循环只要遇到StopIteration就会停止"""
print(n)
ret = irange(1,20)
print(ret) # 返回一个生成器,相当于只在内存中创建了一个值
print(list(ret)) # 如果想要得到全部的值,变成列表就可以
➜ python_test python3 018-exercise-2.py
1
2
3
4
5
6
7
8
9
<generator object irange at 0x101bd9990>
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
yield from¶
def gen1():
for c in 'AB':
yield c
for i in range(3):
yield i
print(list(gen1()))
def gen2():
yield from 'AB'
yield from range(3)
print(list(gen2()))
结果
['A', 'B', 0, 1, 2]
['A', 'B', 0, 1, 2]
小结¶
- 凡是可作用与for循环的对象都是Iterable类型
- 凡是可作用域next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列
- 集合数据类型如list,dict,str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象
- python的for循环本质上就是通过不断调用next()函数实现的.
例如:
for x in [1, 2, 3, 4, 5]:
pass
实际上完全等价于:
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
反射¶
反射的定义¶
根据字符串的形式去某个对象中操作成员
- 根据字符串的形式去一个对象中寻找成员
- 根据字符串的形式去一个对象中设置成员
- 根据字符串的形式去一个对象中删除成员
- 根据字符串的形式去一个对象中判断成员是否存在
初始反射¶
通过字符串的形式,导入模块
根据用户输入的模块名称,导入对应的模块并执行模块中的方法
# python 版本
➜ python3 -V
Python 3.5.3
# commons.py为模块文件
➜ ls commons.py reflection.py
commons.py reflection.py
# commons.py文件内容
➜ cat commons.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def f1():
return "F1"
def f2():
return "F2"
# reflection.py文件内容
➜ cat reflection.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
mod_name = input("请输入模块名称>>>")
print(mod_name,type(mod_name))
# 通过__import__的方式导入模块,并赋值给dd
dd = __import__(mod_name)
# 执行f1()函数
ret = dd.f1()
print(ret)
# 执行
➜ python3 reflection.py
请输入模块名称>>>commons
commons <class 'str'>
F1
通过字符串的形式,去模块中寻找指定函数,并执行
用户输入模块名称和函数名称,执行指定模块内的函数or方法
➜ python_test cat commons.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def f1():
return "F1"
def f2():
return "F2"
➜ python_test cat reflection-2.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 输入模块名
mod_name = input("请输入模块名称>>>")
# 输入函数名
func_name = input("请输入函数名>>>")
# 导入模块
dd = __import__(mod_name)
# 导入模块中的方法
target_func = getattr(dd,func_name)
# 查看target_func和dd.f1的内存地址
print(id(target_func),id(dd.f1))
# 执行 target_func()函数
result = target_func()
# 输出结果
print(result)
➜ python_test python3 reflection-2.py
请输入模块名称>>>commons
请输入函数名>>>f1
# 返回内存地址
4315949528 4315949528
F1
反射相关的函数¶
根据字符串的形式去一个对象中寻找成员
➜ python_test cat commons.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
name = "yang"
def f1():
return "F1"
def f2():
return "F2"
>>> import commons
>>> getattr(commons,"f1")
<function f1 at 0x101c80ea0>
>>> getattr(commons,"f1f")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'commons' has no attribute 'f1f'
执行获取到的函数
>>> target_func = getattr(commons,"f1")
>>> target_func
<function f1 at 0x101c80ea0>
>>> target_func()
'F1'
通过设置默认值可以避免获取不到方法时报错
# 设置一个默认值为None
>>> target_func = getattr(commons,"f1f",None)
>>> target_func
>>>
通过getattr获取模块中的全局变量
>>> import commons
>>> getattr(commons,"name",None)
'yang'
根据字符串的形式去一个对象中设置成员
设置全局变量
>>> getattr(commons,"age",None)
>>> setattr(commons,"age",18)
>>> getattr(commons,"age",None)
18
>>> setattr(commons,"as",lambda : print("as"))
>>> getattr(commons,"as")
<function <lambda> at 0x101c80e18>
>>> aa = getattr(commons,"as")
>>> aa()
as
根据字符串的形式去一个对象中删除成员
>>> getattr(commons,"name")
'yang'
>>> delattr(commons,"name")
>>> getattr(commons,"name")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'commons' has no attribute 'name'
>>>
根据字符串的形式去一个对象中判断成员是否存在
>>> hasattr(commons,"name")
False
>>> setattr(commons,"name","yang")
>>> hasattr(commons,"name")
True
到common.py所在目录的上一层目录,执行python
>>> m = __import__("python_test.commons")
>>> m
<module 'python_test' (namespace)>
>>> m = __import__("python_test.commons",fromlist=True)
>>> m
<module 'python_test.commons' from '/root/python_test/commons.py'>
基于反射模拟web框架路由系统¶
find_index.py
文件内容
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
url = input("请输入url: ")
target_module,target_func = url.split('/')
m = __import__("lib." + target_module,fromlist=True)
if hasattr(m,target_func):
target_func = getattr(m,target_func)
r = target_func()
print(r)
else:
print("404")
目录结构及文件内容
➜ tree .
.
├── find_index.py
└── lib
├── account.py
└── commons.py
1 directory, 3 files
➜ cat lib/commons.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
name = "yang"
def f1():
return "F1"
def f2():
return "F2"
➜ cat lib/account.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
def login():
return "login"
def logout():
return "logout"
执行
➜ python3 find_index.py
请输入url: account/login
login
➜ python3 find_index.py
请输入url: account/logout
logout
➜ python3 find_index.py
请输入url: commons/f1
F1
➜ python3 find_index.py
请输入url: commons/f2
F2
➜ python3 find_index.py
请输入url: commons/fdas
404
异常处理¶
当程序出现错误的时候,进行捕捉,然后根据捕捉到的错误信息进行对应的处理
Even if a statement or expression is syntactically correct, it may cause an error when an attempt is made to execute it. Errors detected during execution are called exceptions and are not unconditionally fatal: you will soon learn how to handle them in Python programs.
try:
code
except (Error1,Error2) as e: # 多种错误统一处理
print(e)
# except Exception: # 捕获所有错误,不建议使用,不知道具体什么错误
# print("未知错误")
else:
print("一切正常" ) # 没有任何错误,执行else
finally:
print("不管有没有错,都执行")
初识异常处理¶
如:
让用户进行输入,提示用户输入一个数字,如果输入的是一个数字,那么就把输入的数字转换为int
类型,然后输出用户输入的数字,如果用户输入的不是一个数字,那么类型转换就会出错,如果出错,就提示用户“输入类型错误,你应该输入一个数字”
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
try:
n = int(input("请输入一个数字>>> "))
print("你输入的数字是: ",n)
except Exception as e:
print("输入类型错误,你应该输入一个数字.")
输出
➜ python_test python3 020-exercise-1.py
请输入一个数字>>> 3
你输入的数字是: 3
➜ python_test python3 020-exercise-1.py
请输入一个数字>>> fad
输入类型错误,你应该输入一个数字.
异常分类¶
常用异常
对不同的异常进行不同的处理
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
try:
n = int(input("请输入一个数字>>> "))
except ValueError as e:
print("ValueError")
except Exception as e:
print("出现异常")
输出
➜ python_test python3 020-exercise-2.py
请输入一个数字>>> 1231
➜ python_test python3 020-exercise-2.py
请输入一个数字>>> dfa
ValueError
在处理异常时,如果出现错误,那么会首先匹配ValueError
,然后再匹配Exception
捕获多个错误
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
try:
raise IndexError("出错了")
except (IndexError,NameError) as e: # 捕获括号内的错误,并把错误信息赋值给e
print(e)
错误异常的基本结构¶
try:
# 主代码
pass
except KeyError as e:
# 异常时,执行该块
pass
else:
# 主代码执行完,执行该块
pass
finally:
# 无论异常与否,最终执行该块
pass
执行流程
- 如果出现错误,那么就执行
except
代码块,然后再执行finally
- 如果没有出现错误,那么就执行
else
代码块,然后再执行finally
- 不管有没有出现异常都会执行
finally
主动触发异常¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
try:
# raise表示主动触发异常,然后创建一个Exception对象,Exception括号内的值就是Exception对象的值
raise Exception("主动触发异常")
except Exception as e:
# 输出Exception对象的值
print(e)
➜ python_test python3 020-exercise-4.py
主动触发异常
如果需要捕获和处理一个异常,又不希望异常在程序中死掉,一般都会利用raise传递异常
>>> try:
... raise IndexError('Index')
... except IndexError:
... print('error')
... raise
...
error
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: Index
断言¶
如果条件成立则成立,如果条件不成立则报错
>>> assert 1 == 1
>>> assert 1 == 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
自定义异常¶
用户自定义的异常通过类编写,且通常需要继承Exception
内置的异常类,基于类的异常允许脚本建立异常类型,继承行为以及附加状态信息.
>>> class Bar(Exception):
... pass
...
>>> def doomed():
... raise Bar()
...
>>> try:
... doomed()
... except Bar as e:
... print("error")
...
error
如果要自定义错误显示信息,我们只需要在类中定义字符串重载(__str__
,__repr__
)方法中的其中一个即可:
>>> class MyError(Exception):
... def __str__(self):
... return "出错了."
...
>>> try:
... raise MyError()
... except MyError as e:
... print(e)
...
出错了.
class XxxException(Exception):
def __init__(self,msg):
self.message = msg
# 下面可以不写__str__ Exception里面已经定义了,默认是msg
def __str__(self):
return self.message
try:
raise XxxException('xxx')
except XxxException as e:
print(e)
02-advance¶
oriented¶
面向对象基础¶
python编程方式
- 面向过程编程
- 面向函数编程
- 面向对象编程
各种定义
- 如果函数没有在类中,称之为
函数
- 如果函数在类中,称之为
方法
类¶
创建类
# 创建一个类,类名是 Class_basis
>>> class Class_basis:
# 在类里面创建了一个方法ret,类里面的方法必须加一个self关键字
... def ret(self):
# 当调动方法的时候输出ret
... print("ret")
...
使用类
# 通过 Class_basis 类创建一个对象obj(创建一个 Class_basis实例),类名后面加括号
>>> obj = Class_basis()
# 通过对象调用类中的ret方法
>>> obj.ret()
ret
类的内存地址对应关系

python-021-class
方法¶
- 构造方法
- 析构方法
- 私有方法
属性¶
- 实例变量
- 类变量
- 私有属性
__var
对象¶
对象: 实例化一个类之后得到的对象
面向对象之self¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 创建一个类,类名是 Class_basis
class Class_basis:
# 在类里面创建了一个方法ret
def ret(self,):
# 输出self的内存地址
print("方法ret的self内存地址",id(self))
# 创建一个对象obj,类名后面加括号
obj = Class_basis()
# 输出对象obj的内存地址
print("obj对象内存地址",id(obj))
# 通过对象调用类中的ret方法
obj.ret()
执行结果
➜ python_test python3 021-exercise-1-self.py
obj对象内存地址 4321835888
方法ret的self内存地址 4321835888
通过上面的测试可以很清楚的看到obj
对象和类的方法中self
内存地址是一样的,那么方法中的self
就等于obj

python-021-class-2
self
是形式参数,由python自行传递
面向对象之封装¶
把一些功能的实现细节不对外暴露
封装就是将内容封装到某个地方,以后再去调用被封装在某处的内容,在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
def ret(self):
# 输出backend变量的内容
print(self.backend)
obj = Foo()
# 在对象中创建一个backend变量
obj.backend = "as"
obj.ret()
执行结果
➜ python_test python3 021-exercise-2.py
as
上面的封装是一种非主流的封装方式,下面的__init__构造函数
封装方式是主流的封装方式
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 进入类的时候首先执行 __init__方法
def __init__(self,name):
"""
__init__称之为构造方法
:param name: Foo传递过来的参数
"""
# 在类中创建一个成员Name,它的值是传过来的形参name
self.Name = name
# 类的方法
def user(self):
# 输出Name的值
print(self.Name)
# 创建对象,并且将"yang"封装到对象中,类+括号的时候会自动执行__init__方法
obj = Foo("yang")
# 执行user方法
obj.user()
执行结果
➜ python_test python3 021-exercise-3.py
yang
__del__解释器销毁对象时自动调用,特殊名称:析构方法
封装的应用场景之一就是当同一类型的方法具有相同参数时,直接封装到对象即可.
通过用户输入年龄和姓名输出用户的个人信息
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
def __init__(self,name,age):
self.Name = name
self.Age = age
def info(self):
print("""
My name is: %s
My age is: %d
""" % (self.Name,self.Age))
yang = Foo("Yang",18)
yang.info()
xiaoming = Foo("xiaoming",30)
xiaoming.info()
执行结果
➜ python_test python3 021-exercise-4.py
My name is: Yang
My age is: 18
My name is: xiaoming
My age is: 30
封装的应用场景之二就是把类当作模块,创建多个对象(对象内封装的数据可以不一样)
面向对象之继承基础¶
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容
- 代码的重用
- 单继承
- 多继承
- 2.7 经典类:深度优先 新式类: 广度优先
- 3.x 均是广度优先
# 继承自object
class Foo(object)
def __init__(self,name,age,sex,salary,course):
self.salary = salary
self.course = course
实例:
创建一个人
信息相关的类,比如说人拥有四肢
,头发
,眼
,耳朵
等信息,再创建一个中国人和非洲黑人的类,中国人的语言是中文,皮肤是黄色,非洲黑人的语言是英文,皮肤是黑色.
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class People:
def __init__(self):
print("""
相同特征: 四肢,头发,眼睛,耳朵
""")
class China(People):
def info(self):
print("""
你是中国人,你的语言是中文,皮肤是黄色
""")
class Africa(People):
def info(self):
print("""
你是非洲黑人,你的语言是英语,皮肤是黑色
""")
c = China()
c.info()
a = Africa()
a.info()
➜ python_test python3 021-exercise-5.py
相同特征: 四肢,头发,眼睛,耳朵
你是中国人,你的语言是中文,皮肤是黄色
相同特征: 四肢,头发,眼睛,耳朵
你是非洲黑人,你的语言是英语,皮肤是黑色
People
-> 父类
or 基类
China
and Africa
-> 子类
or 派生类
- 派生类可以集成基类中所有的功能
- 派生类和基类同时存在,优先找派生类
- python类可以同时继承多个类
面向对象之继承之多继承(新式类)¶
多继承就是在class My(China,Africa):
括号内放入多个父类名
多继承顺序
当My(China,Africa)
时,因为My
类中有info
这个方法,所以输出的结果是我就是我
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class China:
def info(self):
print("你是中国人")
class Africa:
def info(self):
print("你是非洲人")
class My(China,Africa):
def info(self):
print("我就是我")
c = My()
c.info()
执行结果
➜ python_test python3 021-exercise-6.py
我就是我
当My(China,Africa)
时,My
类中没有info
这个方法,输出的结果就是你是中国人
,默认括号内左边的类优先
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class China:
def info(self):
print("你是中国人")
class Africa:
def info(self):
print("你是非洲人")
class My(China,Africa):
pass
c = My()
c.info()
执行结果
➜ python_test python3 021-exercise-7.py
你是中国人
当My(China,Africa)
时,My
类中没有info
这个方法,China
类中也没有info
这个方法,输出的结果是你是非洲人
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class China:
pass
class Africa:
def info(self):
print("你是非洲人")
class My(China,Africa):
pass
c = My()
c.info()
执行结果
➜ python_test python3 021-exercise-8.py
你是非洲人
多态¶
接口重用,一种接口,多种实现
面向对象进阶及类成员¶
深入了解多继承¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class A:
def bar(self):
print("BAR")
self.f1()
class B(A):
def f1(self):
print("B")
class C:
def f1(self):
print("C")
class D(C,B):
pass
obj = D()
obj.bar()
执行结果
➜ python_test python3 022-exercise-1.py
BAR
C
脚本释义:
- 创建了类A,B,C,D;
D
继承了C
和B
,B
继承了A
,D
内什么都不做,pass
;- 创建一个对象
obj
,类是D
,当执行D
的bar
方法的时候会先从C
里面寻找有没有bar
方法; C
内没有bar
方法,然后继续从B
里面查找,B
里面也没有,B
的父类是A
,A
里面有bar
方法,所以就执行了A
的bar
方法;A
的bar
方法首先输出了BAR
;- 然后又执行了
self.f1()
,self=obj
,相当于执行了obj.f1()
; - 执行
obj.f1()
的时候先从C
里面查找有没有f1
这个方法,C
里面有f1
这个方法; - 最后就执行
C
里面的f1
方法了,输出了C
.
执行父类的构造方法¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Animal:
def __init__(self):
print("Animal的构造方法")
self.ty = "动物"
class Cat(Animal):
def __init__(self):
print("Cat的构造方法")
self.n = "猫"
# 寻找Cat类的父类,然后执行父类的构造方法
super(Cat,self).__init__()
m = Cat()
print(m.__dict__)
执行结果
➜ python_test python3 022-exercise-2.py
Cat的构造方法
Animal的构造方法
{'ty': '动物', 'n': '猫'}
先执行了Cat的构造方法,然后又执行了Annimal的构造方法。
第二种执行父类的方法如下:
Annimal.__init__(self)
不推荐使用
利用反射查看面向对象成员归属¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
def __init__(self,name):
self.name = name
def show(self):
print("show")
obj = Foo("as")
# 如果是类,就只能找到类里面的成员
print(hasattr(Foo,"show"))
# 如果是对象,既可以找到对象,也可以找到类里的成员
print(hasattr(obj,"name"))
print(hasattr(obj,"show"))
执行结果
➜ python_test python3 022-exercise-3.py
True
True
True
利用反射导入模块,查找类,创建对象,查找对象中的字段¶
s1
脚本文件内容:
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 导入模块
m = __import__('s2',fromlist=True)
# 去模块中找类
class_name = getattr(m,"Foo")
# 根据类创建对象
obj = class_name("yang")
# 去对象中name对应的值
print(getattr(obj,"name"))
s2
脚本内容
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
def __init__(self,name):
# 普通字段,保存在对象中
self.name = name
执行结果
➜ python_test python3 s1.py
yang
➜ python_te
面向对象类成员之静态字段(类变量)¶
静态字段存在类中
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 静态字段存在的意义就是将每个对象中重复的东西在类里面保存一份即可, 节省开销
class Province:
country = "China"
def __init__(self,name):
self.name = name
def show(self):
print(Province.country,self.name)
hebei = Province("河北")
hebei.show()
hubei = Province("湖北")
hubei.show()
执行结果
➜ python_test python3 022-exercise-4.py
China 河北
China 湖北
类里面的成员用类去访问,对象内的成员用对象去访问
面向对象类成员之静态方法¶
只是名义上归类管理,实际上在静态方法里访问不了类或实例中的任何属性.
@staticmethod # 实际上跟类没什么关系了
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 静态方法括号内没有self,并且方法前一行要加上@staticmethod
@staticmethod
# def static(arg1,arg2): # 也可以设置参数
def static():
print("static")
# 静态方法通过类名.方法名即可执行
Foo.static()
# Foo.static("arg1","arg2")通过类调用的时候传入对应的参数即可
# 静态方法也可以通过对象去访问,对于静态方法用类去访问
obj = Foo()
obj.static()
执行结果
➜ python_test python3 022-exercise-5.py
static
static
class Dog(object):
n = 123
def __init__(self,name):
self.name = name
@staticmethod # 实际上跟类没什么关系了
def eat(self):
print("%s is eating %s" % (self.name,self.n))
def talk(self):
print("%s is talking" % self.name)
d = Dog("xx")
d.eat(d)
d.talk()
面向对象类成员之类方法¶
只能访问类变量,不能访问实例变量
@classmethod
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 创建类方法的时候需要在方法前面加上@classmethod
@classmethod
def ClassMethod(cls): # 并且方法的括号内必须带有cls关键字,类方法的参数是当前类的类名
print("类方法")
# 调用类方法
Foo.ClassMethod()
执行结果
➜ python_test python3 022-exercise-6.py
类方法
class Dog(object):
n = 123
def __init__(self,name):
self.name = name
@classmethod
def eat(self):
print("%s is eating %s" %(self.name,self.n))
d = Dog("xx")
d.eat()
执行会报如下错误
AttributeError: type object 'Dog' has no attribute 'name'
属性方法¶
面向对象类成员特性: 特性的存在就是将方法伪装成字段
把类方法当做普通字段去调用,即用对象调用的时候后面不加括号
# 把一个方法变成一个静态属性,不加括号调用即类似 d.eat(d为一个实例)
@property
def eat(self):
print('%s is eating %s' % (self.name,'xx')
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
@property
def Characteristic(self):
print("类方法的特性")
# 创建一个对象
obj = Foo()
# 调用类方法的时候方法后面不用加括号
obj.Characteristic
输出
➜ python_test python3 025-exercise-1.py
类方法的特性
class Dog(object):
n = 123
def __init__(self,name):
self.name = name
self.__food = "骨头"
@property
def eat(self):
print("%s is eating %s" % (self.name,self.__food))
@eat.setter
def eat(self,food):
print("set to food: %s" % food)
self.__food = food
@eat.deleter # 删除一个属性
def eat(self):
del self.__food
print("删完了")
d = Dog("xxx")
d.eat
d.eat = "包子"
d.eat
del d.eat
属性方法的作用
设置类方法的值
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 获取Characteristic值
@property
def Characteristic(self):
return "获取Characteristic值"
# 意思是下面的Characteristic函数用来给上面的Characteristic函数设置值
@Characteristic.setter
def Characteristic(self,value):
return "设置Characteristic值"
obj = Foo()
# 获取Characteristic的值
print(obj.Characteristic)
# 设置Characteristic的值
obj.Characteristic = 123
输出
➜ python_test python3 025-exercise-2.py
获取Characteristic值
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 特殊字段
@property
def pp(self):
# 调用特殊字段的时候输出aaa
print("property")
@pp.setter
def pp(self,value):
# 调用设置方法的时候输出value的值
print(value)
@pp.deleter
def pp(self):
# 调用删除方法的时候输出del
print("deleter")
# 创建一个对象obj
obj = Foo()
# 自动执行@property
obj.pp
# 自动执行@pp.setter
obj.pp = 999
# 自动执行@pp.deleter
del obj.pp
输出
➜ python_test python3 025-exercise-3.py
property
999
deleter
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
def f1(self):
print("f1")
def f2(self,value):
print("f2")
def f3(self):
print("f3")
SpeciaFields = property(fget=f1,fset=f2,fdel=f3,doc="我是注释")
# 创建一个对象
obj = Foo()
# 调用类的f1方法
obj.SpeciaFields
# 调用类的f2方法
obj.SpeciaFields = 123
# 调用类的f3方法
del obj.SpeciaFields
# 调用类的doc,这里只能通过类去访问,对象无法访问
print(Foo.SpeciaFields.__doc__)
输出
➜ python_test python3 025-exercise-4.py
f1
f2
f3
我是注释
面向对象类成员内容梳理¶
字段
- 静态字段(类变量, 每个对象都有该属性, 节省开销)
- 普通字段(实例变量, 每个对象都不同的数据)
方法
- 静态方法(无需使用对象封装内容)
- 类方法
- 普通方法(适用对象中的数据)
特性
- 普通特性(将方法未造成字段?)
快速判断,类执行,对象执行:
- self -> 对象调用
- 无self -> 类调用
面向对象成员修饰符¶
成员修饰符就是设置类的成员哪些是公开的哪些是私有的,公开的是在外部通过对象或者类可以调用,但是私有的只能通过类的内部才能调用
静态字段修饰¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 公有的静态字段
ClassMembers = "公开的"
# 私有的静态字段
__ClassMembers = "私有的"
# 执行共有的静态字段
print(Foo.ClassMembers)
# 执行私有的静态字段
print(Foo.__ClassMembers)
➜ python_test python3 023-exercise-1.py
公开的
Traceback (most recent call last):
File "023-exercise-1.py", line 10, in <module>
print(Foo.__ClassMembers)
AttributeError: type object 'Foo' has no attribute '__ClassMembers'
私有的是不能够直接调用的,需要在类中进行调用
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 私有的静态字段
__ClassMembers = "私有的"
# 通过类中的方法调用私有的静态字段进行输出
def Members(self):
print(Foo.__ClassMembers)
# 创建一个对象
obj = Foo()
# 执行类中的Members方法
obj.Members()
执行结果
➜ python_test python3 023-exercise-2.py
私有的
普通字段修饰¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 类的构造方法
def __init__(self,url):
# 普通字段
self.url = url
# 私有字段
self.__Blog = url
# 创建一个对象,传入一个值
obj = Foo("http://yjj.top")
# 普通字段
print(obj.url)
#私有的普通字段
print(obj.__Blog)
输出
➜ python_test python3 023-exercise-3.py
http://yjj.top
Traceback (most recent call last):
File "023-exercise-3.py", line 10, in <module>
print(obj.__Blog)
AttributeError: 'Foo' object has no attribute '__Blog'
若要输出私有的普通字段,需要在类中调用私有的普通字段进行输出
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 类的构造方法
def __init__(self,url):
# 私有普通字段
self.__Blog = url
# 直接在构造方法里面输出传入的url
print(self.__Blog)
# 创建一个对象,传入一个值
obj = Foo("http://yjj.top")
输出
➜ python_test python3 023-exercise-4.py
http://yjj.top
对于私有成员,只能够在类中进行访问,即使继承关系也不可以
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
# 父类的构造方法
def __init__(self):
# 私有普通字段
self.__Blog = "http://yjj.top"
# Bar继承了Foo类
class Bar(Foo):
# 由于Bar类没有构造方法,所以会执行父类的构造方法
# 创建了一个类方法fetch
def fetch(self):
# 输出self.__Blog
print(self.__Blog)
# 创建一个对象
obj = Bar()
# 执行类中的fetch方法
obj.fetch()
输出
➜ python_test python3 023-exercise-5.py
Traceback (most recent call last):
File "023-exercise-5.py", line 15, in <module>
obj.fetch()
File "023-exercise-5.py", line 11, in fetch
print(self.__Blog)
AttributeError: 'Bar' object has no attribute '_Bar__Blog'
对于普通方法,静态方法类方法也是如此,只要成员前面加两个下环线就代表是私有的,即外部不能够访问,只有内部才可以访问.
通过特殊的方法去访问类中的私有成员¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Foo:
def __init__(self):
# 私有普通字段
self.__Blog = "http://yjj.top"
# 创建一个对象
obj = Foo()
# 通过特殊的方法访问,一个下划线,一个类名,私有变量名
print(obj._Foo__Blog)
输出
➜ python_test python3 023-exercise-6.py
http://yjj.top
面向对象特殊方法¶
跟运算符无关的特殊方法
类别 | 方法名 |
---|---|
字符串/字节序列表示形式 | __repr__ ,__str__ ,__
format__ ,__bytes__ |
__call__
__len__
__repr__
,__str__
__getitem__
,__setitem__
,__delitem__
__dict__
,dir()
__slots__
__iter__
__getattr__
,__getattribute__
__mro__
,super()
__call__
¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class SpeciaMembers:
# 类的构造方法
def __init__(self):
print("My name is yang")
# 对象的构造方法
def __call__(self):
print("My age is 18")
# 创建一个对象,并且执行类的构造方法
obj = SpeciaMembers()
# 执行对象的构造方法
obj()
# 先执行类的构造方法,然后再执行对象的构造方法
SpeciaMembers()()
输出
➜ python_test python3 024-exercise-1.py
My name is yang
My age is 18
My name is yang
My age is 18
实例2
class Dog(object):
def __init__(self,name):
self.name = name
self.__food = "骨头"
def __call__(self, *args, **kwargs):
print("running call ",args,kwargs)
d = Dog("xxx")()
执行结果
running call () {}
修改成
d = Dog("xxx")
d(1,2,3,name="xxx")
执行结果则为
running call (1, 2, 3) {'name': 'xxx'}
__len__
¶
当要使用内建函数len(),而参数是DictDemo的实例的时候,那一定要实现类型中的__len__()
方法
__repr__
,__str__
¶
__repr__
__str__
# 字符串表示形式, 把一个对象用字符串的形式表达出来以便辨认
# repr 通过 __repr__ 这个特殊方法来得到一个对象的字符串表示形式
# 如果没有实现 __repr__ , 我们在控制台打印一个向量的实例时, 得到的可能就是地址
# __repr__ 和 __str__ 的区别在于, 后者是在str()调用的时候被使用, 或是在用print函数打印一个对象的时候才被调用
# 如果你只想实现两个特殊方法中的一个, __repr__ 会是更好的选择, 因为如果一个对象没有 __str__ 函数, 而Python需要调用它的时候, 解释器会用 __repr__ 代替
class Dog(object):
def __init__(self,name):
self.name = name
self.__food = "骨头"
def __call__(self, *args, **kwargs):
print("running call ",args,kwargs)
def __str__(self):
return "<obj: %s>" % self.name
d = Dog("xxx")
print(d) # __str__
__getitem__
,__setitem__
,__delitem__
¶
用于索引操作,如字典。以上分别表示获取、设置、删除数据
__getitem__
__setitem__
__delitem__
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class SpecialMembers:
# 当执行obj['value']的时候就会自动执行 __getitem__ 方法, 并且把对象括号内的值当做__getitem__的值
def __getitem__(self,item):
print(item)
def __setitem__(self,key,value):
print(key,value)
def __delitem__(self,key):
print(key)
# 创建一个对象
obj = SpecialMembers()
# 自动执行__getitem__方法
obj['value']
# 自动执行__setitem__方法
obj['k1'] = "values"
# 自动执行__delitem__方法
del obj['key']
输出
➜ python_test python3 024-exercise-2.py
value
k1 values
key
特殊的
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class SpecialMembers:
# 当执行obj['value']的时候就会自动执行__getitem__方法,并且把对象括号内的值当做__getitem__的值
def __getitem__(self,item):
print(item,type(item),"__getitem__")
def __setitem__(self,key,value):
print(key,value)
def __delitem__(self,key):
print(key)
obj = SpecialMembers()
obj[1:3] # __getslice__/__getitem__
obj[1:3] = [11,22,33] # __setslice__/__setitem__
del obj[1:3] # __delslice__/__delitem__
输出
➜ python_test python3 024-exercise-3.py
slice(1, 3, None) <class 'slice'> __getitem__
slice(1, 3, None) [11, 22, 33]
slice(1, 3, None)
实例2
class Foo(object):
def __init__(self):
self.data = {}
def __getitem__(self, item):
print('__getitem__',item)
return self.data.get(item)
def __setitem__(self, key, value):
print('__setitem__',key,value)
self.data[key] = value
def __delitem__(self, key):
print('__delitem__',key)
f = Foo()
res = f['k1']
f['k2'] = 'xxx'
del f['k1']
print(type(f)) # <class '__main__.Foo'>
print(type(Foo)) # <class 'type'> 类是由type类实例化产生的
__dict__
,dir()¶
__dict__
dir()
__dict__
是一个字典, 用来存储对象属性,其键为属性名,值为属性的值。
dir()
是一个函数, 返回的是一个list
dir()
函数会自动寻找一个对象的所有属性,包括__dict__
中的属性。
__dict__
是dir()
的子集,dir()
包含__dict__
中的属性。
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class SpeciaMembers:
"""
类的注释
"""
def __init__(self):
self.Name = "yang"
self.Blog = "http://yjj.top"
# 获取类中的成员
print(SpeciaMembers.__dict__)
# 创建一个对象
obj = SpeciaMembers()
# 获取对象中的成员
print(obj.__dict__)
执行结果
{'__doc__': '\n 类的注释\n ', '__dict__': <attribute '__dict__' of 'SpeciaMembers' objects>, '__weakref__': <attribute '__weakref__' of 'SpeciaMembers' objects>, '__module__': '__main__', '__init__': <function SpeciaMembers.__init__ at 0x1024029d8>}
{'Name': 'yang', 'Blog': 'http://yjj.top'}
实例2
class Dog(object):
def __init__(self,name):
self.name = name
self.__food = "骨头"
def __call__(self, *args, **kwargs):
print("running call ",args,kwargs)
print(Dog.__dict__) # 类调用,打印类里面的所有属性,不包括实例属性
d = Dog("xxx")
print(d.__dict__) # 实例调用,打印所有实例属性,不包括类属性
__slots__
¶
如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name
和age
属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
__iter__
¶
一个对象如果可以被for
循环迭代时,说明对象中有__iter__
方法,
且方法中有yield
值
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class SpecialMembers:
def __iter__(self):
yield 1
yield 2
yield 3
# 创建一个对象
obj = SpecialMembers()
# 如果执行for循环对象时,自动执行对象的__iter__方法,此时的__iter__就是一个生成器
for i in obj:
print(i)
输出
➜ python_test python3 024-exercise-5.py
1
2
3
MRO列表, super()¶
我们定义的每一个类,Python 会计算出一个方法解析顺序(Method Resolution Order, MRO)列表,它代表了类继承的顺序,我们可以使用下面的方式获得某个类的 MRO 列表:
>>> C.mro() # or C.__mro__ or C().__class__.mro()
[__main__.C, __main__.A, __main__.B, __main__.Base, object]
MRO列表顺序是通过C3 线性化算法来实现的, 一个类的MRO列表就是合并所有父类的MRO列表, 并遵循以下规则
- 子类永远在父类前面
- 如果有多个父类, 会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择, 选择第一个父类
super原理
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
cls
代表类, inst
代表实例, 上面代码做了两件事
- 获取
inst
的MRO列表 - 查找
cls
在当前MRO列表中的index
, 并返回它的下一个类, 即mro[index + 1]
当我们使用super(cls, inst)
时,
Python会在inst
的MRO列表上搜索cls的下一个类
面向对象python2.7类继承¶
继承类定义¶
单继承
class <类名>(父类名)
<代码块>
类的多重继承
class 类名(父类1,父类2,......,父类n)
<代码块>
- python的类可以继承多个类,Java和C#中则只能继承一个类
- python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是: 深度优先和广度优先

python-026-1
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了更多的功能,也是之后推荐的写法,从写法上区分的话,如果当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类
class C1: # C1 经典类
pass
class C2(C1): # C2 经典类
pass
class N1(object): # N1 是新式类
pass
class N2(N1): # N2 是新式类
pass
经典类多继承
class D:
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
def bar(self):
print 'B.bar'
class A(B, C):
def bar(self):
print 'A.bar'
a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> D --> C
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
新式类多继承
class D(object):
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
def bar(self):
print 'B.bar'
class A(B, C):
def bar(self):
print 'A.bar'
a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> C --> D
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
- 经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
- 新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
注意:
bar
在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找
运算符重载¶
运算符重载概念
- 运算符重载让类拦截常规的python运算;
- 类可重载所有python表达式运算符;
- 类也可重载打印,函数调用,属性点号运算等内置运算;
- 重载是类实例的行为像内置类型;
- 重载是通过提供特殊名称的类方法来实现的.
常见的运算符重载方法¶
__bool__ | 布尔测试 | bool(X),真测试
lt,
gt,
le,
ge,
eq,
ne| 特定的比较 | XY…
radd| 右侧加法 | Other + X
iadd| 增强的加法 | X += Y
iter,
next| 迭代环境 | I=iter(X),next(I)
contains
| 成员关系测试 | item in X(任何可迭代对象)
index| 整数值 | hex(X),bin(X),oct(X),o[X],O[X:]
enter,
exit| 环境管理器 | with obj as var:
get,
set,
delete| 描述符属性 | X.attr,X.attr=Value,del X.attr
new| 创建 | 在
init`之前创建对象
所有重载方法的名称前后都有两个下划线字符,以便把同类中定义的变量名区别开来
构造函数和表达式: __init__
和 __sub__
¶
>>> class Number:
... def __init__(self,start):
... self.data = start
... def __sub__(self,other):
... return Number(self.data - other)
...
>>> X = Number(5)
>>> Y = X - 2
>>> Y
<__main__.Number object at 0x101bdd940>
>>> Y.data
3
索引和分片: __getitem__
和 __setitem__
¶
基本索引
>>> class Index:
... def __getitem__(self,item):
... return item ** 2
...
>>> for i in range(5):
... I = Index()
... print(I[i],end=' ')
...
0 1 4 9 16 >>>
切片索引
>>> class Index:
... data = [5,6,7,8,9]
... def __getitem__(self,item):
... print('getitem: ',item)
... return self.data[item]
... def __setitem__(self,key,value):
... self.data[key] = value
...
>>> X = Index()
>>> print(X[1:4])
getitem: slice(1, 4, None)
[6, 7, 8]
>>> X[1:4] = (1,1,1)
>>> print(X[1:4])
getitem: slice(1, 4, None)
[1, 1, 1]
索引迭代: __getitem__
¶
如果重载了这个方法,for循环每次循环时都会调用类的getitem方法
>>> class stepper:
... def __getitem__(self,item):
... return self.data[item].upper()
...
>>> X = stepper()
>>> X.data = 'yang'
>>> for item in X:
... print(item)
...
Y
A
N
G
迭代器对象: __iter__
和 __next__
¶
>>> class Squares:
... def __init__(self,start,stop):
... self.value = start - 1
... self.stop = stop
... def __iter__(self):
... return self
... def __next__(self):
... if self.value == self.stop:
... raise StopIteration
... self.value += 1
... return self.value ** 2
...
>>> for i in Squares(1,5):
... print(i)
...
1
4
9
16
25
成员关系: __contains__
, __iter__
和 __getitem__
¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Iters:
def __init__(self,value):
self.data = value
def __getitem__(self,item):
print('get[%s]' % item,end='')
return self.data[item]
def __iter__(self):
print('iter>==',end='')
self.ix = 0
return self
def __next__(self):
print('next:',end='')
if self.ix == len(self.data) : raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
def __contains__(self,item):
print('contains: ',end=' ')
return item in self.data
X = Iters([1,2,3,4,5])
print(3 in X)
for i in X:
print(i,end='|')
print([i ** 2 for i in X])
print(list(map(bin,X)))
I = iter(X)
while True:
try:
print(next(I),end='@')
except StopIteration as e:
break
属性引用: __getattr__
和 __setattr__
¶
当通过未定义的属性名称和实例通过点号进行访问时,就会用属性名称作为字符串调用这个方法,但如果类使用了继承,并且在超类中可以找到这个属性,那么就不会触发.
>>> class empty:
... def __getattr__(self,item):
... if item == 'age'
File "<stdin>", line 3
if item == 'age'
^
SyntaxError: invalid syntax
>>> class empty:
... def __getattr__(self,item):
... if item == 'age':
... return 18
... else:
... raise AttributeError(item)
...
>>> x = empty()
>>> print(x.age)
18
>>> print(x.name)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __getattr__
AttributeError: name
>>> class accesscontrol:
... def __setattr__(self,key,value):
... if key == 'age':
... self.__dict__[key] = value
... else:
... raise AttributeError(key + ' not allowed')
...
>>> x = accesscontrol()
>>> x.age = 14
>>> print(x.age)
14
>>> x.name = "Hello"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
AttributeError: name not allowed
__repr__
和 __str__
会返回字符串表达式¶
__repr__
和
__str__
都是为了更友好的显示,具体来说,如果在终端下print(Class)则会调用__repr__
,非终端下会调用__str__
方法,且这两个方法只能返回字符串
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class adder:
def __init__(self,value=0):
self.data = value
def __add__(self,other):
self.data += other
def __repr__(self):
return 'addrepr(%s)' % self.data
def __str__(self):
return 'N: %s' % self.data
x = adder(2)
x + 1
print(x)
print((str(x),repr(x)))
➜ python_test python3 027-exercise-2.py
N: 3
('N: 3', 'addrepr(3)')
右侧加法和原处加法: __radd__
和 __iadd__
¶
只有当+右侧的对象时类实例,而左边对象不是类实例的时候,python才会调用__radd__
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Commuter:
def __init__(self,val):
self.val = val
def __add__(self,other):
print('add',self.val,other)
return self.val + other
def __radd__(self,other):
print('radd',self.val,other)
return other + self.val
x = Commuter(88)
y = Commuter(99)
print(x + 1)
print('')
print(1 + y)
print('')
print(x + y)
执行结果
➜ python_test python3 027-exercise-3.py
add 88 1
89
radd 99 1
100
add 88 <__main__.Commuter object at 0x101b9ff98>
radd 99 88
187
使用__iadd__
进行原处加法
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Number:
def __init__(self,val):
self.val = val
def __iadd__(self,other):
self.val += other
return self
x = Number(5)
x += 1
x += 1
print(x.val)
class Number:
def __init__(self,val):
self.val = val
def __add__(self,other):
return Number(self.val + other)
x = Number(5)
x += 1
x += 1
print(x.val)
Call表达式: __call__
¶
当调用类实例时执行__call__
方法
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Callee:
def __call__(self,*args,**kwargs):
print('Callee:',args,kwargs)
C = Callee()
C(1,2,3)
C(1,2,3,x=1,y=2,z=3)
class Prod:
def __init__(self,value):
self.value = value
def __call__(self,other):
return self.value * other
x = Prod(3)
print(x(3))
print(x(4))
输出
➜ python_test python3 027-exercise-5.py
Callee: (1, 2, 3) {}
Callee: (1, 2, 3) {'y': 2, 'z': 3, 'x': 1}
9
12
比较: __lt__
, __gt__
和其他方法¶
类可以定义方法来捕获所有的6种比较运算符: < , > , <= , >= , == 和 !=
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class C:
data = 'spam'
def __gt__(self,other):
return self.data > other
def __lt__(self,other):
return self.data < other
x = C()
print(x > 'han')
print(x < 'han')
输出
➜ python_test python3 027-exercise-6.py
True
False
布尔值测试: bool 和 len¶
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
class Truth:
def __bool__(self):
return True
X = Truth()
if X: print('yes')
class Truth:
def __bool__(self):
return False
X = Truth()
print(bool(X))
输出
➜ python_test python3 027-exercise-7.py
yes
False
如果没有这个方法,python退而求其次的求长度,因为一个非空对象看作是真
>>> class Truth:
... def __len__(self): return 0
...
>>> X = Truth()
>>> if not X : print('no')
...
no
如果两个方法都有,__bool__
会胜过__len__
>>> class Truth:
... def __bool__(self): return True
... def __len__(self): return 0
...
>>> X = Truth()
>>> bool(X)
True
如果两个方法都没有定义,对象毫无疑义的看作为真
>>> class Truth: pass
...
>>> bool(Truth)
True
对象解析函数: __del__
¶
每当实例产生时,就会调用init构造函数,每当实例空间被收回时,它的对立面__del__
,也就是解析函数,就会自动执行
>>> class Life:
... def __init__(self,name='unknown'):
... print('Hello,',name)
... self.name = name
... def __del__(self):
... print('Goodbye',self.name)
...
>>> brian = Life('Brian')
Hello, Brian
>>> brian = 'loretta'
Goodbye Brian
03-面向对象¶
network¶
socket¶
socket是网络连接端点.例如,当你的web浏览器请求baidu的时候,你的web浏览器创建一个socket并命令它去连接baidu的web服务器,web服务器也对过来的请求在一个socket上进行监听.两端使用各自的socket来发送和接收信息.
在使用的时候,每个socket都被绑定到一个特定的IP地址和端口,IP地址是一个由4个数组成的序列,这4个数均是范围0255中的值;端口数值的取值范围是065535.端口数小于1024的为系统保留端口,最大的保留数被存储在socket模块的IPPORT_RESERVED变量中.
不是所有的IP地址都对世界的其他地方可见,实际上,一些是专门为那些非公共的地址所保留的(比如,192.168.y.z或10.x.y.z). 127.0.0.1是本机回环地址,程序可以使用这个地址来连接运行在同一计算机上的其他程序.
python提供了两个基本的的socket模块
- 第一个是Socket,它提供了标准的BSD Sockets API
- 第二个是SocketServer,它提供了服务器中心类,可以简化网络服务器的开发.
Socket对象¶
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
参数一: 地址簇
参数 | 描述 |
---|---|
socket.AF_INET | IPv4(默认) |
socket.Af_INET6 | IPv6 |
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信 |
参数二: 类型
参数三: 协议
Socket类方法¶
Socket变成思想¶
TCP服务端
- 创建套接字,绑定套接字到本地IP与端口
- 开始监听连接
- 进入循环,不断接受客户端的连接请求
- 然后接收传来的数据,并发送给对方数据
- 传输完毕后,关闭套接字
TCP客户端
- 创建套接字,连接远端地址
- 连接后发送数据和接收数据
- 传输完毕后,关闭套接字
创建一个socket连接¶
s1.py为服务端
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import socket
# 创建一个socket对象
sk = socket.socket()
# 绑定允许连接的IP地址和端口
sk.bind(('127.0.0.1',6254))
# 服务端允许起来之后,限制客户端连接的数量,如果连接超过五个,第六个连接进来的时候直接断开第六个
sk.listen(5)
print("正在等待客户端连接...")
# 会一直阻塞,等待接收客户端的请求,如果有客户端连接会获取两个值,conn=创建的连接,address=客户端的IP和端口
coon,address = sk.accept()
# 输入客户端的连接和客户端的地址信息
print(address,coon)
c1.py
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import socket
# 创建一个socket对象
obj = socket.socket()
# 指定服务端的IP地址和端口
obj.connect(('127.0.0.1',6254,))
# 连接完成之后关闭连接
obj.close()

python-028-1
基于socket实现聊天机器人¶
第一份¶
- 创建一个socket
- 监听地址
- 等待客户端发送消息
- 回应
- 关闭
import socket
server = socket.socket()
server.bind(('localhost',60004))
server.listen()
print("等待小可爱上线...")
conn,addr = server.accept()
print("你的小可爱上线了...")
count = 0
while True:
message = conn.recv(1024)
print("recv: ",message.decode('utf-8'))
if not message:
print("你的小可爱断开连接了...")
break
conn.send(message.upper())
count += 1
if count > 10 :break
server.close()
- 创建一个socket
- 连接服务器
- 发送消息
- 接收来自服务端的回复
- 关闭
import socket
client = socket.socket()
client.connect(('localhost',60004))
while True:
message = input("输入q退出>> ").strip()
if message == 'q':
break
else:
client.send(message.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
client.close()
第二份¶
通过socket实现局域网内的聊天工具
service.py内容
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import socket
# 创建一个对象
sk = socket.socket()
# 绑定允许连接的IP地址和端口
sk.bind(('127.0.0.1',6054,))
# 服务端运行后,限制客户端连接的数量,如果超过五个连接,第六个连接来的时候直接断开第六个
sk.listen(5)
while True:
# 会一直阻塞,等待接收客户端的请求,如果有客户端连接会获取两个值,conn=创建的连接,address=客户端的IP和端口
conn,address = sk.accept()
# 当用户连接过来的时候就给用户发送一条信息,在python3里面需要把发送的内容转换为字节
conn.sendall(bytes("你好",encoding="utf-8"))
while True:
print("正在等待Client输入内容......")
# 接收客户端发送过来的内容
ret_bytes = conn.recv(1024)
# 转换成字符串类型
ret_str = str(ret_bytes,encoding="utf-8")
# 输出用户发送过来的内容
print(ret_str)
# 如果用户输入的是q
if ret_str == "q":
# 则退出循环,等待下个用户输入
break
# 给客户端发送内容
inp = input("Service请输入要发送的内容>>>")
conn.sendall(bytes(inp,encoding="utf-8"))
client.py的内容
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import socket
# 创建一个socket对象
obj = socket.socket()
# 制定服务端的IP地址和端口
obj.connect(('127.0.0.1',6054,))
# 阻塞,等待服务端发送内容,接受服务端发送过来的内容,最大接受1024字节
ret_bytes = obj.recv(1024)
# 因为服务端发送过来的是字节,所以我们需要把字节转换为字符串进行输出
ret_str = str(ret_bytes,encoding="utf-8")
# 输出内容
print(ret_str)
while True:
# 当进入连接的时候,提示让用户输入内容
inp = input("Client -> 请输入要发送的内容>>>")
# 如果输出q则退出
if inp == "q":
# 把q发送给服务端
obj.sendall(bytes(inp,encoding="utf-8"))
# 退出当前while
break
else:
# 否则就把用户输入的内容发送给用户
obj.sendall(bytes(inp,encoding="utf-8"))
# 等待服务端响应
print("正在等待Server输入内容......")
# 获取服务端发送过来的结果
ret = str(obj.recv(1024),encoding="utf-8")
# 输出结果
print(ret)
# 连接完成之后关闭连接
obj.close()

python-029-1
基于socket实现文件上传¶
socket server实现多并发¶
简单实现¶
server
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print("客户端 %s 连接成功" % self.client_address[0])
while True:
self.data = self.request.recv(1024).strip()
print("{} {} wrote:".format(self.client_address[0],self.client_address[1]))
print(self.data)
if not self.data:
print(self.client_address,"断开了")
break
self.request.send(self.data.upper())
if __name__ == '__main__':
HOST,PORT = '127.0.0.1',60006
server = socketserver.ThreadingTCPServer((HOST,PORT),MyServer)
print("等待客户端...")
server.serve_forever()
client
import socket
client = socket.socket()
client.connect(('127.0.0.1',60006))
while True:
message = input(">> ").strip()
client.send(message.encode())
up = client.recv(1024)
print(up.decode())
IO多路复用¶
线程与进程¶
协程¶
协程:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其它协程
greenlet¶
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
# -*- coding:utf-8 -*-
from greenlet import greenlet
def test1():
print(12)
gr2.switch()
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch()
print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
Gevent¶
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
import gevent
def func1():
print('\033[31;1m李闯在跟海涛搞...\033[0m')
gevent.sleep(2)
print('\033[31;1m李闯又回去跟继续跟海涛搞...\033[0m')
def func2():
print('\033[32;1m李闯切换到了跟海龙搞...\033[0m')
gevent.sleep(1)
print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m')
gevent.joinall([
gevent.spawn(func1),
gevent.spawn(func2),
#gevent.spawn(func3),
])
同步与异步的性能区别
import gevent
def task(pid):
"""
Some non-deterministic task
"""
gevent.sleep(0.5)
print('Task %s done' % pid)
def synchronous():
for i in range(1,10):
task(i)
def asynchronous():
threads = [gevent.spawn(task, i) for i in range(10)]
gevent.joinall(threads)
print('Synchronous:')
synchronous()
print('Asynchronous:')
asynchronous()
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。
import sys
import socket
import time
import gevent
from gevent import socket,monkey
monkey.patch_all()
def server(port):
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:", data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server(8001)
04-网络编程¶
modules¶
模块¶
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式.在python中,一个.py文件就称之为一个模块(Module)
模块分为三种:
- 自定义模块
- 内置模块
- 开源模块
PyPI
使用模块的好处¶
- 提高了代码的可维护性,可重用性.
- 避免函数名和变量名冲突,相同的名字的函数和变量完全可以分别存在不同的模块中,我们在编写模块时,不必考虑名字会与其他模块冲突.但要注意,尽量不要与内置函数名子冲突
包¶
同时为了避免模块名冲突,python引入了按目录来组织模块的方法,称为包(Package)
比如:
一个abc.py
的文件就是一个叫abc
的模块和一个xyz.py
的文件就是一个叫xyz
的模块.现在abc
和xyz
两个模块的名字与其他模块冲突,于是我们可以通过包来组织模块,为了避免冲突,方法就是选择一个顶层包名,比如mycompany
,如下:

python-package-1
引入包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突.现在abc.py
模块的名字就变成了mycompany.abc
.
注意:
每一个包目录下面都会有一个__init__.py
文件,这个文件是必须存在的,否则,python就把这个目录当成普通目录,而不是一个包,__init__.py
文件可以是空文件,也可以有python代码,因为__init__.py
本身就是一个模块,而它的模块名就是mycompany
类似的,可以有多及目录,组成多级层次的包结构.

python-package-2
web
目录下的utils.py
的模块名就是mycompany.web.utils
,两个utils.py
文件的模块名分别是mycompany.utils
和mycompany.web.utils
自己创建模块时要注意命名,不能和python自带的模块名称冲突.
mycompany.web
也是一个模块,其对应的文件为web目录下的__init__.py文件
导入模块¶
- import module
- from module.xx.xx import xx
- from module.xx.xx import xx as rename
- from module.xx.xx import *
导入模块就是告诉python解释器去解释哪个py
- 导入一个py文件,解释器解释该py文件
- 导入一个包,解释器解释该包下的
__init__.py
导入模块时,根据下面路径作为基准来进行,sys.path
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python35.zip', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages']
当我们试图加载一个模块时,Python会在上述路径下搜索对应的.py文件,如果找不到,就会报错:
>>> import mymodule
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named mymodule
如果sys.path路径列表没有想要的路径,可以通过sys.path.append(‘路径’)添加
>>> import sys
>>> import os
>>> pre_path = os.path.abspath('../')
>>> sys.path.append(pre_path)
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python35.zip', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages', '/Users']
示例代码:
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# 模块的注释文档,任何模块代码的第一个字符串都被视为模块的文档注释
' a test module'
# 使用__author__变量把作者写进去
__author__ = 'Michael'
# 导入模块
import sys
def test():
args = sys.argv
if len(args) == 1:
print("Hello,World")
elif len(args) == 2:
print('Hello, %s! ' % args[1])
else:
print("Too many arguments!")
if __name__ == '__main__':
test()
比如,使用sys
模块,第一步,导入模块
import sys
导入sys
模块后,就有sys
变量指向该模块,利用sys
这个变量,就可以访问sys
模块的所有功能.
sys
模块有一个argv
变量,用list存储了命令行的所有参数.argv
至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py
获得的sys.argv
就是[hello.py]
运行python3 hello.py Michael
获得的sys.argv
就是['hello.py','Michael']
最后两行代码:
if __name__ == '__main__':
test()
当我们在命令行运行hello
模块文件时,python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该hello
模块时,if
判断将失败,因此,这种if
测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试
.
在python交互环境,再导入hello
模块,导入时,没有打印内容,因为没有执行
test()
函数,调用hello.test()
时,才能打印出内容.

python-module-1
动态导入模块¶
import importlib
__import__('import_lib.metaclass') # 这是解释器自己内部使用的
# importlib.import_module('import_lib.metaclass') # 与上面这句效果一样, 官方建议用这个
作用域¶
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人用,有的函数和变量我们希望仅仅在模块内部使用.在python中,是通过_
前缀来实现的.
正常的函数和变量名是公开的(public),可以直接被引用,比如:abc
,xyz
等
类似__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello
模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名
类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用
之所以说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量.
private函数或变量不应该被别人引用.它们的用处:
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:
外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。
安装开源模块¶
安装方法有两种
- 一
- yum
- pip
- apt-get
- …
- 二
- 下载源码
- 解压源码
- 进入目录
- 编译源码
- 安装源码
使用源码安装,需要使用到gcc和python开发环境
需要先执行:
yum install gcc python-devel
或者
apt-get python-dev
安装成功后,模块会自动添加到sys.path中的某个目录中,比如
/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages
如果你正在使用Windows,确保安装时勾选了pip和Add python.exe to Path。
在命令提示符窗口下尝试运行pip,如果Windows提示未找到命令,可以重新运行安装程序添加pip。
注意:Mac或Linux上有可能并存Python 3.x和Python 2.x,因此对应的pip命令是pip3。
一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是:
pip install Pillow
耐心等待下载并安装后,就可以使用Pillow了。
有了Pillow,处理图片易如反掌。随便找个图片生成缩略图:
>>> from PIL import Image
>>> im = Image.open('test.png')
>>> print(im.format, im.size, im.mode)
PNG (400, 300) RGB
>>> im.thumbnail((200, 100))
>>> im.save('thumb.jpg', 'JPEG')
其他常用的第三方库还有MySQL的驱动:mysql-connector-python
,用于科学计算的NumPy库:numpy
,用于生成文本的模板工具Jinja2
,等等。
内置模块¶
用于提供对解释器相关的操作
sys.argv 命令行参数`列表`,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0)
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout.write('please:')
val = sys.stdin.readline()[:-1] # 从标准输入读入
subprocess 执行系统命令¶
可以执行shell命令的相关模块和函数有:
- os.system
- os.spawn*
- os.popen* # 废弃
- popen2.* # 废弃
- commands.* # 废弃,3.x中被移除
以上执行shell命令的相关的模块和函数的功能均在 subprocess 模块中实现,并提供了更丰富的功能。
执行命令,返回状态码
>>> import subprocess
>>> ret = subprocess.call(["ls","-l"],shell=False)
# shell = True,允许shell命令是字符串形式
>>> ret = subprocess.call(["ls -l"],shell=True)
执行命令,如果执行状态是0,则返回0,否则抛出异常
>>> subprocess.check_call(["ls","-l"])
>>> subprocess.check_call("exit 1",shell=True)
执行命令,如果状态码是0,则返回执行结果,否则抛出异常
>>> subprocess.check_output(["echo","Hello World!"])
b'Hello World!\n'
>>> subprocess.check_output("exit 1",shell=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py", line 316, in check_output
**kwargs).stdout
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py", line 398, in run
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
>>>
用于执行复杂的系统命令
参数:
- args:shell命令,可以是字符串或者序列类型(如:list,元组)
- bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
- stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
- preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
- close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。shell:同上
- cwd:用于设置子进程的当前目录
- env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
- universal_newlines:不同系统的换行符不同,True -> 同意使用 :raw-latex:`\n`
- startupinfo与createionflags只在windows下有效
- 将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等
执行普通命令
>>> import subprocess
>>> ret1 = subprocess.Popen(["mkdir","t1"])
>>> ret2 = subprocess.Popen("mkdir t2",shell=True)
终端输入的命令分为两种
- 输入即可得到输出,如ifconfing
- 输入进行某环境,依赖再输入,如python
shutil¶
高级的 文件,文件夹,压缩包处理模块
将文件内容拷贝到另一个文件中,可以部分内容
def copyfileobj(fsrc, fdst, length=16*1024):
"""copy data from file-like object fsrc to file-like object fdst"""
while 1:
buf = fsrc.read(length)
if not buf:
break
fdst.write(buf)
拷贝文件
def copyfile(src, dst):
"""Copy data from src to dst"""
if _samefile(src, dst):
raise Error("`%s` and `%s` are the same file" % (src, dst))
for fn in [src, dst]:
try:
st = os.stat(fn)
except OSError:
# File most likely does not exist
pass
else:
# XXX What about other special files? (sockets, devices...)
if stat.S_ISFIFO(st.st_mode):
raise SpecialFileError("`%s` is a named pipe" % fn)
with open(src, 'rb') as fsrc:
with open(dst, 'wb') as fdst:
copyfileobj(fsrc, fdst)
仅拷贝权限。内容、组、用户均不变
def copymode(src, dst):
"""Copy mode bits from src to dst"""
if hasattr(os, 'chmod'):
st = os.stat(src)
mode = stat.S_IMODE(st.st_mode)
os.chmod(dst, mode)
拷贝状态的信息,包括:mode bits, atime, mtime, flags
def copystat(src, dst):
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
st = os.stat(src)
mode = stat.S_IMODE(st.st_mode)
if hasattr(os, 'utime'):
os.utime(dst, (st.st_atime, st.st_mtime))
if hasattr(os, 'chmod'):
os.chmod(dst, mode)
if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
try:
os.chflags(dst, st.st_flags)
except OSError, why:
for err in 'EOPNOTSUPP', 'ENOTSUP':
if hasattr(errno, err) and why.errno == getattr(errno, err):
break
else:
raise
configparser 配置文件¶
用于对特定的配置进行操作,当前模块的名称在 python 3.x 版本中变更为 configparser
logging 日志¶
用于便捷记录日志且线程安全的模块
time & datetime 时间¶
时间相关的操作,时间有三种表示方式:
- 时间戳 1970年1月1日之后的秒,即:time.time()
- 格式化的字符串 2014-11-11 11:11, 即:time.strftime(‘%Y-%m-%d’)
- 结构化时间 元组包含了:年、日、星期等… time.struct_time 即:time.localtime()
re 正则¶
random 随机数¶
一些模块¶
Celery¶
Distributed Task Queue
Celery is a simple, flexible, and reliable distributed system to process vast amounts of messages, while providing operations with the tools required to maintain such a system.
It’s a task queue with focus on real-time processing, while also supporting task scheduling.
Celery has a large and diverse community of users and contributors, you should come join us on IRC or our mailing-list.
Celery is Open Source and licensed under the BSD License.
Pexpect¶
Pexpect allows easy control of interactive console applications.
https://github.com/pexpect/pexpect
Pexpect makes Python a better tool for controlling other applications.
Pexpect is a pure Python module for spawning child applications; controlling them; and responding to expected patterns in their output. Pexpect works like Don Libes’ Expect. Pexpect allows your script to spawn a child application and control it as if a human were typing commands.
subprocess¶
Subprocess management
`subprocess
<https://docs.python.org/3.5/library/subprocess.html#module-subprocess>`__
The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions:
inspect¶
__future__
¶
Python提供了__future__模块,把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性。
Excel¶
在处理excel数据时发现了xlwt的局限性–不能写入超过65535行、256列的数据(因为它只支持Excel 2003及之前的版本,在这些版本的Excel中行数和列数有此限制),这对于实际应用还是不够的。
openpyxl支持07/10/13版本Excel的, 功能很强大, 但是操作起来感觉没有xlwt方便.
- 读取Excel时,选择openpyxl和xlrd 都能满足需求.
- 写入少量数据且存为xls格式文件时, 用xlwt更方便.
- 写入大量数据(超过xls格式限制)或者必须存为xlsx格式文件时,使用openpyxl了.
读写Excel, 但是写操作会有一些问题. 用xlrd进行读取比较方便, 流程和平时都手操作Excel一样, 打开工作簿(Workbook), 选择工作表(sheets), 然后操作单元格(cell)
爬虫¶
The Python Standard Library¶
NumPy¶
PIL¶
PIL:Python Imaging Library
,
已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。
由于PIL仅支持到Python 2.7
,
加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫
Pillow
,
支持最新Python 3.x
,又加入了许多新特性,因此,我们可以直接安装使用Pillow。
Pillow¶
http://pillow.readthedocs.io/en/latest/
https://github.com/python-pillow/Pillow
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 26/05/2017 10:31 AM
# @Author : yang
from PIL import Image,ImageDraw,ImageFont,ImageFilter
import random
def rnd_char():
return chr(random.randint(65,90))
def rnd_color():
return (random.randint(64,255), random.randint(64,255), random.randint(64,255))
def rnd_color2():
return (random.randint(32,127), random.randint(32,127), random.randint(32,127))
# 240 * 60
width = 60 * 4
height = 60
image = Image.new('RGB',(width,height),(255,255,255))
# 创建Font对象
font = ImageFont.truetype('Arial.ttf',36)
# 创建Draw对象
draw = ImageDraw.Draw(image)
# 填充每个像素
for x in range(width):
for y in range(height):
draw.point((x,y),fill=rnd_color())
# 输出文字
for t in range(4):
draw.text((60 * t + 10,10),rnd_char(),font=font,fill=rnd_color2())
# 模糊
#image = image.filter(ImageFilter.BLUR)
image.save('code.jpg','jpeg')
collections¶
collections是python內建的一个集合模块,提供了许多有用的集合类
namedtuple¶
namedtuple
是一个函数,它用来创建一个自定义的tuple
对象,并且规定了tuple
元素的个数,并可以用属性而不是索引来引用tuple
的某个元素.
这样,我们用namedtuple
可以很方便地定义一种数据类型,它具备tuple的不变形,又可以根据属性来引用.
二维坐标
>>> from collections import namedtuple
>>> Point = namedtuple('Point',['x','y'])
>>> p = Point(1,2)
>>> p.x
1
>>> p.y
2
>>> isinstance(p,Point)
True
>>> isinstance(p,tuple)
True
类似的,可以用坐标和半径表示一个源,也可以用namedtuple
定义
Circle = namedtuple('Circle',['x','y','r'])
deque¶
用list
存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list
是线性存储,数据量大的时候,插入和删除效率很低
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈
>>> from collections import deque
>>> q = deque(['a','b','c'])
>>> q.append('x')
>>> q
deque(['a', 'b', 'c', 'x'])
>>> q.appendleft('what')
>>> q
deque(['what', 'a', 'b', 'c', 'x'])
>>> q.pop()
'x'
>>> q.popleft()
'what'
>>> q
deque(['a', 'b', 'c'])
deque除了实现list
的append()
和pop()
外,还支持appendleft()
和popleft()
,这样就可以非常高效地往头部添加或删除元素。
defaultdict¶
使用dict
时,如果引用的key不存在,就会抛出KeyError,如果希望key不存在时,返回一个默认值,就可以用defaultdict
>>> from collections import defaultdict
>>> d = defaultdict(lambda : 'N/A')
>>> d['key1'] = 'abc'
>>> d['key1']
'abc'
>>> d['key2']
'N/A'
默认值是调用函数返回的,而函数在创建``defaultdict``对象时传入
除了key不存在时返回默认值,其他的与dict一致
OrderedDict¶
使用dict
,key是无序的,在对dict
做迭代时,我们无法确定key的顺序
如果要保持key的顺序,可以使用OrderedDict
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
OrderedDict的Key会按照插入的顺序排列,不是Key本身排序
OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key:
from collections import OrderedDict
class LastUpdatedOrderedDict(OrderedDict):
def __init__(self, capacity):
super(LastUpdatedOrderedDict, self).__init__()
self._capacity = capacity
def __setitem__(self, key, value):
containsKey = 1 if key in self else 0
if len(self) - containsKey >= self._capacity:
last = self.popitem(last=False)
print('remove:', last)
if containsKey:
del self[key]
print('set:', (key, value))
else:
print('add:', (key, value))
OrderedDict.__setitem__(self, key, value)
Counter¶
Counter
是一个简单的计数器,例如,统计字符出现的个数
>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'pythoncxxxx':
... c[ch] = c[ch] + 1
...
>>> c
Counter({'x': 4, 'n': 1, 'p': 1, 'c': 1, 'o': 1, 'y': 1, 't': 1, 'h': 1})
Counter实际上也是dict的一个子类,上面的结果可以看出,字符’x’出现了4次,其他字符各出现了一次。
collections模块提供了一些有用的集合类,可以根据需要选用。¶
concurrent.futures¶
python 3
为我们提供的标准库,
提供了ThreadPoolExecutor
和ProcessPoolExecutor
两个类,实现了对threading
和multiprocessing
的更高级的抽象,对编写线程池/进程池提供了直接的支持。
concurrent.futures
基础模块是 executor
和 future
python 2.7 需要安装futures模块,使用命令pip install futures安装即可
https://docs.python.org/3/library/concurrent.futures.html
http://blog.csdn.net/dutsoft/article/details/54728706
http://www.cnblogs.com/skiler/p/7080179.html
类的方法¶
concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)
:wait等待fs里面所有的Future实例(由不同的Executors实例创建的)完成。返回两个命名元祖,第一个元祖名为done,存放完成的futures对象,第二个元祖名为not_done,存放未完成的futures。return_when参数必须是concurrent.futures里面定义的常量:FIRST_COMPLETED,FIRST_EXCEPTION,ALL_COMPLETED
concurrent.futures.as_completed(fs, timeout=None)
:返回一个迭代器,yield那些完成的futures对象。fs里面有重复的也只可能返回一次。任何futures
在调用as_completed()
调用之前完成首先被yield。
Executor Objects¶
一个抽象类, 提供方法来执行异步调用. 不应该直接使用, 可以通过具体子类使用.
ThreadPoolExecutor¶
ThreadPoolExecutor
是 Executor
的子类, 使用线程池执行异步调用.
示例:
下面会造成死锁
import time
def wait_on_b():
time.sleep(5)
print(b.result()) # b will never complete because it is waiting on a.
return 5
def wait_on_a():
time.sleep(5)
print(a.result()) # a will never complete because it is waiting on b.
return 6
executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
def wait_on_future():
f = executor.submit(pow, 5, 2)
# This will never complete because there is only one worker thread and
# it is executing this function.
print(f.result())
executor = ThreadPoolExecutor(max_workers=1)
executor.submit(wait_on_future)
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
ProcessPoolExecutor¶
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419]
def is_prime(n):
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
Future 对象¶
Future
类封装了一个可调用的异步执行,
`Future
<https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future>`__
对象通过
`Executor.submit()
<https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.submit>`__创建.
cancel
()Attempt to cancel the call. If the call is currently being executed and cannot be cancelled then the method will return
False
, otherwise the call will be cancelled and the method will returnTrue
.cancelled
()Return
True
if the call was successfully cancelled.running
()Return
True
if the call is currently being executed and cannot be cancelled.done
()Return
True
if the call was successfully cancelled or finished running.result
(timeout=None)Return the value returned by the call. If the call hasn’t yet completed then this method will wait up to timeout seconds. If the call hasn’t completed in timeout seconds, then a
`concurrent.futures.TimeoutError
<https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.TimeoutError>`__ will be raised. timeout can be an int or float. If timeout is not specified orNone
, there is no limit to the wait time.If the future is cancelled before completing then`CancelledError
<https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.CancelledError>`__ will be raised.If the call raised, this method will raise the same exception.exception
(timeout=None)Return the exception raised by the call. If the call hasn’t yet completed then this method will wait up to timeout seconds. If the call hasn’t completed in timeout seconds, then a
`concurrent.futures.TimeoutError
<https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.TimeoutError>`__ will be raised. timeout can be an int or float. If timeout is not specified orNone
, there is no limit to the wait time.If the future is cancelled before completing then`CancelledError
<https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.CancelledError>`__ will be raised.If the call completed without raising,None
is returned.add_done_callback
(fn)
还有一些方法用于单元测试和Executor
实现
contextlib¶
在Python中操作文件可以使用try...finally
写try...finally
非常繁琐。Python的with语句允许我们非常方便地使用资源,而不必担心资源没有关闭
with open('/path/to/file', 'r') as f:
f.read()
并不是只有open()函数返回的fp对象才能使用with语句。实际上,任何对象,只要正确实现了上下文管理,就可以用于with语句。
实现上下文管理是通过__enter__
和__exit__
这两个方法实现的。例如,下面的class实现了这两个方法
class Query:
def __init__(self,name):
self.name = name
def __enter__(self):
print('Begin')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print('Error')
else:
print('End')
def query(self):
print('Query info about %s ... ' % self.name)
with Query('Bob') as q:
q.query()
@contextmanager¶
编写__enter__
和__exit__
仍然很繁琐,因此Python的标准库contextlib
提供了更简单的写法,上面的代码可以改写如下:
from contextlib import contextmanager
class Query(object):
def __init__(self,name):
self.name = name
def query(self):
print('Query info about %s...' % self.name)
@contextmanager
def create_query(name):
print('Begin')
q = Query(name)
yield q
print('End')
with create_query('Bob') as q:
q.query()
csv¶
使用Python标准库,csv模块
使用Python生成csv表格,用Excel打开的时候,中文乱码¶
解决办法: 在文件开头加BOM
# 需要 import codecs
with open('user_info.csv', 'wb') as csvfile:
csvfile.write(codecs.BOM_UTF8)
示例
def write_csv(user_info):
"""
写csv
:param user_info: 所有用户信息,整个为一个字典,所有value分别是一个包含一个用户信息的字典
:return:
"""
# 防止乱码
with open('user_info.csv', 'wb') as csvfile:
csvfile.write(codecs.BOM_UTF8)
with open('user_info.csv', 'a+', encoding="utf-8") as csvfile:
fieldnames = ['Name','DoorName','CardNO','GroopName']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
rows = []
# writer.writerows([{'DoorName': '二门', 'CardNO': 30000, 'GroopName': '内部运营\\行政', 'Name': '王麻子'},{'DoorName': '二门', 'CardNO': 40000, 'GroopName': '内部运营\\行政', 'Name': '张三'}])
for user in user_info.values():
rows.append(user)
writer.writerows(rows)
datetime¶
datetime是Python处理日期和时间的标准库
获取当前日志和时间¶
>>> from datetime import datetime
>>> now = datetime.now()
>>> print(now)
2017-05-24 14:59:05.139597
>>> print(type(now))
<class 'datetime.datetime'>
datetime
是模块,datetime
模块还包含一个datetime
类,通过from datetime import datetime
导入的才是datetime
这个类,如果仅导入import datetime
,则必须引用全名datetime.datetime
datetime.now()
返回当前日期和时间,起类型是datetime
获取指定日志和时间¶
要指定某个日期和时间,我们直接用参数构造一个datetime
>>> from datetime import datetime
>>> dt = datetime(2017,5,17,5,20)
>>> print(dt)
2017-05-17 05:20:00
datetime转换为timestamp¶
在计算机中,时间实际上是用数字表示的.我们把1970年1月1日00:00:00
UTC+00:00时区的时刻成为epoch
time,记为0
(1970年以前的时间timestamp为负数),当前时间就是相对于``epoch time``的秒数,称为timestamp
你可以认为:
timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00
对应的北京时间是:
timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00
可见timestamp的值与时区毫无关系,因为timestamp一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的,这就是为什么计算机存储的当前时间是以timestamp表示的,因为全球各地的计算机在任意时刻的timestamp都是完全相同的(假定时间已校准)。
>>> from datetime import datetime
>>> dt = datetime(2017,5,24,3,5) # 用指定日期时间创建datetime
>>> print(dt)
2017-05-24 03:05:00
>>> dt.timestamp() # 把datetime转换为timestamp
1495566300.0
注意Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数。
某些编程语言(如Java和JavaScript)的timestamp使用整数表示毫秒数,这种情况下只需要把timestamp除以1000就得到Python的浮点表示方法。
timestamp转换为datetime¶
使用datetime
提供的fromtimestamp()
方法
>>> from datetime import datetime
>>> t = 1495566300.0
>>> print(datetime.fromtimestamp(t))
2017-05-24 03:05:00
注意到timestamp是一个浮点数,它没有时区的概念,而datetime是有时区的。上述转换是在timestamp和本地时间做转换。
本地时间是指当前操作系统设定的时区。例如北京时区是东8区,则本地时间:
2017-05-24 03:05:00
实际上就是UTC+8:00时区的时间:
2017-05-24 03:05:00 UTC+8:00
而此刻的格林威治标准时间与北京时间差了8小时,也就是UTC+0:00时区的时间应该是:
2017-05-23 19:05:00 UTC+0:00
timestamp也可以直接被转换到UTC标准时区的时间
>>> from datetime import datetime
>>> t = 1495566300.0
>>> print(datetime.fromtimestamp(t))
2017-05-24 03:05:00
>>> print(datetime.utcfromtimestamp(t))
2017-05-23 19:05:00
str转换为datetime¶
很多时候,用户输入的日期和时间是字符串,要处理日期和时间,首先必须把str转换为datetime.转换方法是通过datetime.strptime()
实现,需要一个日期和时间的格式化字符串.
>>> from datetime import datetime
>>> cday = datetime.strptime('2017-05-24 15:14','%Y-%m-%d %H:%M')
>>> print(cday)
2017-05-24 15:14:00
字符串%Y-%m-%d %H:%M:%S
规定了日期和时间部分的格式。详细的说明请参考。Python文档
datetime转换为str¶
如果已经有了datetime对象,要把它格式化为字符串显示给用户,就需要转换为str,转换方法是通过strftime()
实现的,同样需要一个日期和时间的格式化字符串
>>> from datetime import datetime
>>> now = datetime.now()
>>> print(now.strftime('%a,%b %d %H:%M'))
Wed,May 24 15:26
datetime加减¶
对日期和时间进行加减十几上就是把datetime往后或往前计算,得到新的datetime.加减可以直接用+
和-
运算符,不过需要导入timedelta
这个类
>>> from datetime import datetime,timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2017, 5, 24, 15, 28, 49, 136987)
>>> now + timedelta(hours=10)
datetime.datetime(2017, 5, 25, 1, 28, 49, 136987)
>>> now - timedelta(days = 1)
datetime.datetime(2017, 5, 23, 15, 28, 49, 136987)
>>> now + timedelta(days = 2,hours = 12)
datetime.datetime(2017, 5, 27, 3, 28, 49, 136987)
本地时间转换为UTC时间¶
本地时间是指系统设定时区的时间,例如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00的时间
一个datetime
类型有一个时区属性tzinfo
,但是默认为None
,所以无法区分这个datetime
是哪个时区,除非强行给datetime
设置一个时区
>>> from datetime import datetime
>>> from datetime import datetime,timedelta,timezone
>>> tz_utc_8 = timezone(timedelta(hours=8))
>>> now = datetime.now()
>>> now
datetime.datetime(2017, 5, 24, 15, 32, 32, 126421)
>>> dt = now.replace(tzinfo=tz_utc_8)
>>> dt
datetime.datetime(2017, 5, 24, 15, 32, 32, 126421, tzinfo=datetime.timezone(datetime.timedelta(0, 28800)))
时区转换¶
可以先通过utcnow()
拿到当前UTC时间,再转换为任意时区的时间
>>> utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
>>> print(utc_dt)
2017-05-24 07:38:49.792786+00:00
>>> bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
>>> print(bj_dt)
2017-05-24 15:38:49.792786+08:00
时区转换的关键在于,拿到一个datetime时,要获知其正确的时区,然后强制设置时区,作为基准时间。
利用带时区的datetime,通过astimezone()方法,可以转换到任意时区。
注:不是必须从UTC+0:00时区转换到其他时区,任何带时区的datetime都可以正确转换,例如上述bj_dt到tokyo_dt的转换。
小结¶
datetime
表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。
如果要存储datetime
,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关。
测试¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
假设你获取了用户输入的日期和时间如2015-1-21 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp:
"""
import re
from datetime import datetime, timezone, timedelta
def to_timestamp(dt_str, tz_str):
dt = datetime.strptime(dt_str,'%Y-%m-%d %H:%M:%S')
res = int(re.search("[+-]\d+", tz_str).group())
tz_zone = timezone(timedelta(hours=res))
return dt.replace(tzinfo=tz_zone).timestamp()
t1 = to_timestamp('2015-6-1 08:10:30', 'UTC+7:00')
assert t1 == 1433121030.0, t1
t2 = to_timestamp('2015-5-31 16:10:30', 'UTC-09:00')
assert t2 == 1433121030.0, t2
print('Pass')
docker-py模块¶
getpass¶
包含下面两个函数:
- getpass.getpass()
- getpass.getuser()
getpass.getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
提示用户输入一段密码,参数 prompt 用于提示用户开始输入,默认为’Password: ’。在 Unix 上,该提示符被写入到类文件对象流中。参数 stream 默认为控制终端 (/dev/tty) 或入过前者不可用时为 sys.stderr (该参数在 Windows 上无效)。
如果无回显输入不可用,getpass() 回退并向流 stream 中输出一个警告消息,从 sys.stdin 中读取并抛出异常 GetPassWarning。
适用于: Macintosh, Unix, Windows.
如果你在 IDLE 中调用getpass(),输入可能会在你启动 IDLE 的终端中而不是在 IDLE 窗口中完成
>>> import getpass
>>> p = getpass.getpass("xxx: ")
xxx:
>>> print(p)
123
exception getpass.GetPassWarning
Python内置异常 UserWarning 的子类,当密码输入可能被回显时抛出。
getuser() - Get the user name from the environment or password database.
返回用户的登录名,适用于:Unix, Windows
该函数依次检测环境变量 LOGNAME、USER、LNAME 和 USERNAME,返回其中第一个非空的值。如果这些变量都没有被设置,支持 pwd 模块的系统会返回密码数据库中的登录名,否则抛出异常。
hashlib¶
代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5算法
摘要算法简介
Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
举个例子,你写了一篇文章,内容是一个字符串'how to use python hashlib - by Michael'
,并附上这篇文章的摘要是'2d73d4f15c0db7f5ecb321b6a65e5d6d'
。如果有人篡改了你的文章,并发表为'how to use python hashlib - by Bob'
,你可以一下子指出Bob篡改了你的文章,因为根据'how to use python hashlib - by Bob'
计算出的摘要不同于原始文章的摘要。
可见,摘要算法就是通过摘要函数f()
对任意长度的数据data
计算出固定长度的摘要digest
,目的是为了发现原始数据是否被人篡改过。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值
如果数据量很大,可以分块多次调用update()
,最后计算的结果是一样的
>>> md1 = hashlib.md5()
>>> md1.update("what's your name".encode('utf-8'))
>>> md2 = hashlib.md5()
>>> md2.update("what's your name".encode('utf-8'))
>>> print(md1.hexdigest())
21d3ccbf3dd3e0d8c641bc574084fe46
>>> print(md2.hexdigest())
21d3ccbf3dd3e0d8c641bc574084fe46
>>> md2.update("x".encode('utf-8'))
>>> print(md2.hexdigest())
a340a72f8cdc0890e210770946577668
>>> md6 = hashlib.md5()
>>> md6.update("what's your namex".encode('utf-8'))
>>> print(md6.hexdigest())
a340a72f8cdc0890e210770946577668
SHA1¶
>>> sha1 = hashlib.sha1()
>>> sha1.update('xxxx'.encode('utf-8'))
>>> sha1.update('xxxx'.encode('utf-8'))
>>> print(sha1.hexdigest())
bcf22dfc6fb76b7366b1f1675baf2332a0e6a7ce
sha256,sha512..¶
import hashlib
# ######## md5 ########
hash = hashlib.md5()
hash.update('admin')
print hash.hexdigest()
# ######## sha1 ########
hash = hashlib.sha1()
hash.update('admin')
print hash.hexdigest()
# ######## sha256 ########
hash = hashlib.sha256()
hash.update('admin')
print hash.hexdigest()
# ######## sha384 ########
hash = hashlib.sha384()
hash.update('admin')
print hash.hexdigest()
# ######## sha512 ########
hash = hashlib.sha512()
hash.update('admin')
print hash.hexdigest()
以上加密算法虽然依然非常厉害,但时候存在缺陷,即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密。
import hashlib
# ######## md5 ########
hash = hashlib.md5('898oaFs09f')
hash.update('admin')
print hash.hexdigest()
python 还有一个 hmac 模块,它内部对我们创建 key 和 内容 再进行处理然后再加密
import hmac
h = hmac.new('hello')
h.update('world')
print h.hexdigest()
摘要算法应用¶
允许用户登录的网站可以存储用户口令的摘要,而不存储用户的明文口令.
当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。
在程序设计上对简单口令加强保护呢?
由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:
def calc_md5(password):
return get_md5(password + 'the-Salt')
但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?
如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
小结¶
摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
实例¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
根据用户输入的登录名和口令模拟用户注册,计算更安全的MD5:
然后,根据修改后的MD5算法实现用户登录的验证:
"""
import hashlib,getpass
db = {}
def get_md5(str):
md5 = hashlib.md5()
md5.update((str + 'xxxx').encode('utf-8'))
return md5.hexdigest()
def register(username,password):
db[username] = get_md5(password + username)
def login(username, password):
if username in db:
if get_md5(password + username) == db[username]:
print("success")
else:
print('failure')
else:
print('用户不存在')
def main():
username = input("Please input your name: ")
password = getpass.getpass()
register(username,password)
print(db)
username = input("Please input your name: ")
password = getpass.getpass()
login(username, password)
if __name__ == '__main__':
main()
itertools¶
count()¶
Python的內建模块itertools
提供了非常有用的用于操作迭代对象的函数.
>>> import itertools
>>> natuals = itertools.count(1)
>>> for n in natuals:
... print(n)
...
count()
会创建一个无限的迭代器,所以上述代码会打印出自然数序列.
cycle()¶
cycle()
会把传入的一个序列无限重复下去
>>> import itertools
>>> cs = itertools.cycle('ABC') # 注意字符串也是序列的一种
>>> for c in cs:
... print(c)
repeat()¶
repeat()
负责把一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数:
>>> import itertools
>>> ns = itertools.repeat('x',5)
>>> for n in ns:
... print(n)
...
x
x
x
x
x
>>>
无限序列只有在for迭代时才会无限地迭代下去,如果只是创建了一个迭代对象,它不会事先把无限个元素生成出来,事实上也不可能在内存中创建无限多个元素。
无限序列虽然可以无限迭代下去,但是通常我们会通过takewhile()
等函数根据条件判断来截取出一个有限的序列
>>> natuals = itertools.count(1)
>>> ns = itertools.takewhile(lambda x:x <=10,natuals)
>>> list(ns)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
chain()¶
chain()
可以把一组迭代对象串联起来,形成一个更大的迭代器
>>> for c in itertools.chain('abc','xyz'):
... print(c)
...
a
b
c
x
y
z
groupby()¶
groupby()
把迭代器中相邻的重复元素挑出来放在一起
>>> for key,group in itertools.groupby('aaaabbcdaa'):
... print(key,list(group))
...
a ['a', 'a', 'a', 'a']
b ['b', 'b']
c ['c']
d ['d']
a ['a', 'a']
实际上挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。如果我们要忽略大小写分组,就可以让元素’A’和’a’都返回相同的key
>>> for key, group in itertools.groupby('AaaBBbcCAAa', lambda c: c.upper()):
... print(key, list(group))
...
A ['A', 'a', 'a']
B ['B', 'B', 'b']
C ['c', 'C']
A ['A', 'A', 'a']
小结¶
itertools
模块提供的全部是处理迭代功能的函数,它们的返回值不是list
,而是Iterator
,只有用for循环迭代的时候才真正计算。
json 和 pickle**¶
用于序列化的两个模块
- json, 用于字符串和python数据类型间进行转换
- pickle, 用于python特有的类型 和 python的数据类型间进行转换
json模块提供了四个功能: dumps,dump,loads,load
pickle模块提供了四个功能: dumps,dump,loads,load
dumps是将dict转化成str格式,loads是将str转化成dict格式。
dump和load也是类似的功能,只是与文件操作结合起来了。
两个模块用法相同,区别在于pickle只可用于Python,可序列化Python特有数据类型
json¶
JSON类型 | Python类型 |
---|---|
{} | dict |
[] | list |
“string” | str |
1234.56 | int或float |
true/false | True/False |
null | None |
返回一个str
,内容就是标准的JSON.类似的dump()
方法可以直接把JSON写入一个file-like Object
>>> import json
>>> d = dict(name='Bob',age=20,score=80)
>>> json.dumps(d)
'{"name": "Bob", "age": 20, "score": 80}'
>>> data = {'k1':123,'k2':'hello'}
# json.dumps 将数据通过特殊的形式转换为所有程序语言都认识的字符串
j_str = json.dumps(data)
print(j_str)
# json.dump 将数据通过特殊的形式转换为所有程序语言都认识的字符串,并写入文件
with open('/User/xxx/result.json','w') as fp:
json.dump(data,fp)
要把JSON反序列化为Python对象,用loads()
或者对应的load()
方法,前者把JSON的字符串反序列化,后者从file-like Object
中读取字符串并反序列化
>>> json_str = '{"name": "Bob", "age": 20, "score": 80}'
>>> json.loads(json_str)
{'name': 'Bob', 'age': 20, 'score': 80}
由于JSON标准规定JSON编码是UTF-8,所以我们总是能正确地在Python的str
与JSON的字符串之间转换。
Python的dict
对象可以直接序列化为JSON的{}
,不过,很多时候,我们更喜欢用class
表示对象,比如定义Student
类,然后序列化
import json
class Student:
def __init__(self,name,age,score):
self.name = name
self.age = age
self.score = score
s = Student('Bob',20,80)
print(json.dumps(s))
上述代码直接运行,会报如下错误
TypeError: <__main__.Student object at 0x1021ded30> is not JSON serializable
原因是Student
对象不是一个可序列化为JSON的对象
dumps()
方法参考
https://docs.python.org/3/library/json.html#json.dumps
前面的代码不能将Student
类实例化为JSON,是因为默认情况下,dumps()
方法不知道如何将Student
实例变为一个JSON的{}
对象
可选参数default就是把任意一个对象变成一个可序列为JSON的对象,我们只需要为Student专门写一个转换函数,再把函数传进去即可:
import json
class Student:
def __init__(self,name,age,score):
self.name = name
self.age = age
self.score = score
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
s = Student('Bob',20,80)
print(json.dumps(s,default=student2dict))
这样,Student
实例首先被student2dict()
转为dict
,然后被序列化
不过,如果下次遇到其他类的实例,同样无法实例化为JSON,此时可以这样做,把任意class的实例变为dict
print(json.dumps(s,default=lambda obj:obj.__dict__))
因为通常class的实例都有一个__dict__
属性,它就是一个dict,用来存储实例变量。也有少数例外,比如定义了__slots__
的class。
同样的道理,如果我们要把JSON反序列化为一个Student对象实例,loads()方法首先转换出一个dict对象,然后,我们传入的object_hook函数负责把dict转换为Student实例:
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> print(json.loads(json_str, object_hook=dict2student))
<__main__.Student object at 0x10cd3c190>
打印出的是反序列化的Student
实例对象。
https://docs.python.org/3.6/library/json.html
文件内容
{
"1":["张三",150,120,100],
"2":["李四",90,99,95],
"3":["王五",60,66,68]
}
with open('student.txt', 'r', encoding='utf8') as f:
students_info = json.load(f, object_pairs_hook=OrderedDict)
students_info的顺序是跟文件中定义的顺序一样.
创建一个字符串变量 dict_str
>>> dict_str = '{"k1":"v1","k2":"v2"}'
>>> type(dict_str)
<class 'str'>
将字符串变量 dict_str
序列化成字典格式
>>> import json
>>> dict_json = json.loads(dict_str)
>>> type(dict_json)
<class 'dict'>
>>> dict_json
{'k2': 'v2', 'k1': 'v1'}
json模块的dumps()
和loads()
函数是定义得非常好的接口的典范。当我们使用时,只需要传入一个必须的参数。但是,当默认的序列化或反序列机制不满足我们的要求时,我们又可以传入更多的参数来定制序列化或反序列化的规则,既做到了接口简单易用,又做到了充分的扩展性和灵活性。
pickle¶
>>> import pickle
>>> data = {'k1':123,'k2':'hello'}
# pickle.dumps 将数据通过特殊的形式转换为只有python语言认识的字符串
>>> p_str = pickle.dumps(data)
>>> p_str
b'\x80\x03}q\x00(X\x02\x00\x00\x00k1q\x01K{X\x02\x00\x00\x00k2q\x02X\x05\x00\x00\x00helloq\x03u.'
# pickle.dump 将数据通过特殊的形式转换为只有python语言认识的字符串,并写入文件
with open('/User/xxxx/result.pk','w') as fp:
pickle.dump(data,fp)
if os.path.exists(os.path.split(__file__)[0] + '/user.db'):
pk_file = open('user.db','rb')
students = pickle.load(pk_file)
pk_file.close()
logging¶
https://docs.python.org/3/library/logging.html#module-logging
math¶
math.sqrt()¶
>>> math.sqrt(16)
4.0
math.ceil()¶
Return the ceiling of x as a float, the smallest integer value greater than or equal to x.
>>> math.ceil(1.1)
2
>>> math.ceil(2)
2
openpyxl¶
https://openpyxl.readthedocs.io/en/default/
wb = openpyxl.load_workbook(filename=file_path)
# 所有工作簿名字
# wsl = wb.sheetnames
# 通过名字获取工作簿
# ws1 = wb.get_sheet_by_name('试用期')
# 工作簿最大多少行, 最大多少列 max_column, max_row
# openpyxl 索引从 1 开始, 所以所有行索引为, (1, max_row+1)
# print(ws1.max_column)
# 打印某个单元格的值
# print(ws1.cell(row=1, column=1).value)
读取cell中的颜色¶
ws1 = wb.get_sheet_by_name('试用期')
c = ws1.cell(row=1,column=1)
fill = c.fill
front = c.font
print(c.value)
print(fill.start_color.rgb)
print(front.color.rgb)
服务器名称
FF00B050
FFFFFF00
写 excel¶
https://www.cnblogs.com/sun-haiyu/p/7096423.html
可以一次添加多行数据,从第一行空白行开始(下面都是空白行)写入。
# 添加一行
row = [1 ,2, 3, 4, 5]
sheet.append(row)
# 添加多行
rows = [
['Number', 'data1', 'data2'],
[2, 40, 30],
[3, 40, 25],
[4, 50, 30],
[5, 30, 10],
[6, 25, 5],
[7, 50, 10],
]
append按行写入, 按列写入只需要将矩阵转置
list(zip(*rows))
# out
[('Number', 2, 3, 4, 5, 6, 7),
('data1', 40, 40, 50, 30, 25, 50),
('data2', 30, 25, 30, 10, 5, 10)]
示例¶
def merge_sheet(wb):
"""
合并所有工作簿为一个 RecordCollection 对象
:param wb:
:return:
"""
records_list = []
for sheet_name in wb.sheetnames:
row_gen = single_sheet(wb=wb, sheet_name=sheet_name)
tmp_res = RecordCollection(row_gen)
tmp_res.all()
records_list.append(tmp_res)
# 合并工作簿为一个 RecordCollection 对象, 需要定义 __add__ 方法
results = reduce(lambda x, y: x + y, records_list)
return results
def single_sheet(wb, sheet_name):
"""
返回单张工作簿的所有数据
:param wb: workbook
:param sheet_name: 工作簿名字
:return: 返回单张工作簿的所有数据
"""
headers = {
'姓名': 'ConsumerName',
'卡号': 'CardNO',
'部门': 'GroupName',
'权限': 'DoorName'
}
sheet = wb.get_sheet_by_name(sheet_name)
# 从 1 开始, 如果不要表头, 从 2 行开始
sheet_header = []
for column in range(1, sheet.max_column + 1):
header = sheet.cell(row=1, column=column).value
if header in headers:
sheet_header.append(headers[header])
else:
raise ValueError('工作簿 <%s> 表头错误' % sheet)
# print(sheet_header)
user_gen = ([sheet.cell(row=row, column=column).value for column in range(1, sheet.max_column + 1)]
for row in range(2, sheet.max_row + 1))
return (Record(sheet_header, list(row)) for row in user_gen)
# return sheet_header,l
os¶
用于提供系统级别的操作
常用操作¶
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
os.getenv('PATH') 获取系统环境变量
os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
os.curdir 返回当前目录: ('.')
os.pardir 获取当前目录的父目录字符串名:('..')
os.makedirs('dirname1/dirname2') 可生成多层递归目录
os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove() 删除一个文件
os.rename("oldname","newname") 重命名文件/目录
os.stat('path/filename') 获取文件/目录信息
os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
os.pathsep 输出用于分割文件路径的字符串
os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command") 运行shell命令,直接显示
os.environ 获取系统环境变量
os.path.abspath(path) 返回path规范化的绝对路径
os.path.split(path) 将path分割成目录和文件名二元组返回
os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) 如果path是绝对路径,返回True
os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
实例¶
[ x for x in os.listdir('.') if os.path.isdir(x)]
.gz
结尾的文件¶>>> [ x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1] == '.gz' ]
['jdk-8u101-linux-x64.tar.gz']
import os
os.system(“ls ”)
# 调用之后,命令结果直接输出到屏幕
cmd_res = os.system("ls")
# cmd_res 的值,是os.system 执行的结果, 成功与否(0与非0)
如果要保存命令执行的结果, 使用 os.popen()
cmd_res = os.popen("ls").read()
os.popen("ls") 执行之后,结果存在一个内存地址,需要使用read方法去取
pymssql¶
先决条件¶
依赖 FreeTDS
- Python: Python 2.x: 2.7 or newer. Python 3.x: 3.3 or newer.
- FreeTDS: 0.82 or newer.
- Cython: 0.15 or newer.
- Microsoft SQL Server:
- 2005 or newer.
安装FreeTDS
# Ubuntu/Debian:
sudo apt-get install freetds-dev
# Mac OS X with Homebrew:
brew install freetds
安装¶
pip install pymssql
下载源码 .tar.gz
, 或者 .whl
,
PyPI
__pyx_r = DBVERSION_80;
¶clang -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/usr/local/include -I/usr/local/var/pyenv/versions/2.7.11/include/python2.7 -c _mssql.c -o build/temp.macosx-10.11-x86_64-2.7/_mssql.o -DMSDBLIB
_mssql.c:18783:15: error: use of undeclared identifier 'DBVERSION_80'
__pyx_r = DBVERSION_80;
^
1 error generated.
error: command 'clang' failed with exit status 1
原因是安装的freetds版本过高
解决办法:
# 1
brew unlink freetds; brew install homebrew/versions/freetds091
# 2
pip3 install git+https://github.com/pymssql/pymssql.git
示例¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pymssql
# 指定端口
server = "192.168.1.111:2179"
user = "USERNAME"
password = "PASSWORD"
conn = pymssql.connect(server, user, password, "AccessData")
cursor = conn.cursor()
cursor.execute("""
IF OBJECT_ID('persons', 'U') IS NOT NULL
DROP TABLE persons
CREATE TABLE persons (
id INT NOT NULL,
name VARCHAR(100),
salesrep VARCHAR(100),
PRIMARY KEY(id)
)
""")
cursor.executemany(
"INSERT INTO persons VALUES (%d, %s, %s)",
[(1, 'John Smith', 'John Doe'),
(2, 'Jane Doe', 'Joe Dog'),
(3, 'Mike T.', 'Sarah H.')])
# you must call commit() to persist your data if you don't set autocommit to True
conn.commit()
cursor.execute('SELECT * FROM persons WHERE salesrep=%s', 'John Doe')
row = cursor.fetchone()
while row:
print("ID=%d, Name=%s" % (row[0], row[1]))
row = cursor.fetchone()
conn.close()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pymssql
server = '192.168.1.111:2179'
user = "USERNAME"
password = "PASSWORD"
conn = pymssql.connect(server, user, password, "AccessData")
cursor = conn.cursor()
cursor.execute("""SELECT * FROM t_b_Group WHERE f_GroupName = '技术部\服务器'""")
row = cursor.fetchone()
print(row)
conn.close()
http://www.pymssql.org/en/stable/pymssql_examples.html#important-note-about-cursors
任何时候只能有一个游标, 和一个活跃的查询
每一次查询之后, 应该先存储数据, 再进行下一次查询
c1.execute('SELECT ...')
c1_list = c1.fetchall()
c2.execute('SELECT ...')
c2_list = c2.fetchall()
# use c1_list and c2_list here instead of fetching individually from
# c1 and c2
事务没有提交之前, 该连接对数据库的操作, 对其他连接来说是不可见的
PyMysql¶
简单测试¶
import pymysql
conn = pymysql.connect(host='127.0.0.1',port=3306,user='root',passwd='111111',db='mysql',charset='UTF8')
cur = conn.cursor()
cur.execute("select version()")
for i in cur:
print(i)
cur.close()
conn.close()
queue¶
https://docs.python.org/3/library/queue.html
``queue``模块定义了如下类, 以及异常.
- class
queue.Queue(maxsize=0)
FIFO
先进先出队列. maxsize
是一个整数, 限制队列最大值.
当队列大小达到最大值, 会导致阻塞插入. 当maxsize
小于或等于0,
队列大小无限.
- class
queue.LifoQueue(maxsize=0)
LIFO
后进先出队列. maxsize
作用同上.
- class
queue.PriorityQueue(maxsize=0)
priority queue
优先队列. maxsize
作用同上.
优先值最小的, 最先出队,
优先值通过sorted(list(entries))[0]
排序获得,
返回的第一个即为最小值.
典型的参数为一个元组(priority_number, data)
.
- exception
queue.Empty
当队列为空, 使用
`get()
<https://docs.python.org/3/library/queue.html#queue.Queue.get>`__
(或者
`get_nowait()
<https://docs.python.org/3/library/queue.html#queue.Queue.get_nowait>`__)方法,
不阻塞的时候会触发该异常, queue.Empty
.
如果队列为空, 使用get()
方法的时候, 会导致阻塞,
可以使用timeout
参数, 设定超时时间.
- exception
queue.Full
当队列已满, 使用
`put()
<https://docs.python.org/3/library/queue.html#queue.Queue.put>`__
(或
`put_nowait()
<https://docs.python.org/3/library/queue.html#queue.Queue.put_nowait>`__)方法,
不阻塞的时候会触发该异常, queue.Full
Queue对象 (```Queue`` <https://docs.python.org/3/library/queue.html#queue.Queue>`__, ```LifoQueue`` <https://docs.python.org/3/library/queue.html#queue.LifoQueue>`__, or ```PriorityQueue`` <https://docs.python.org/3/library/queue.html#queue.PriorityQueue>`__) 提供了如下公共方法.
队列方法¶
返回队列的近似大小. 注意, qsize() > 0
不能保证随后的 get()
不阻塞, qsize() < maxsize
也不能保证 put()
不阻塞.
如果队列为空, 返回True
, 否则返回False
. 如果
empty()
但会True
不能保证随后调用put()
不阻塞.
同样地, 如果empty()
返回 False
同样不能随后调用 get()
不阻塞.
如果队列满了, 返回 True
, 否则返回 False
. 如果 full()
返回
True
不能保证随后调用 get()
不阻塞. 同样地, 如果 full()
返回 False
不能保证随后调用 put()
不阻塞.
在队尾插入一个项目, item
为必需的, 为插入项目的值. 如果可选参数
block
为 true
并且 timeout
为 None
(默认值),
队列会一直阻塞, 知道有位置可用. 如果 timeout
是一个正数,
在放入项目的时候, 队列没有可用的空间, 它最多阻塞 timeout
传入的秒数,
之后会触发
`Full
<https://docs.python.org/3/library/queue.html#queue.Full>`__
异常. 反之 (block
为 False
), 如果有空间可用将立刻放入项目,
否则触发
`Full
<https://docs.python.org/3/library/queue.html#queue.Full>`__
异常 (这种情况下``timeout``参数会被忽略).
等价于 put(item, False)
.
从队头删除并返回一个项目, block
为是否阻塞,
timeout
为等待时间. 如果可选参数 block
is True
并且
timeout
为 None
(默认值), 阻塞知道有项目可用.
如果timeout
为正数, 在队列没有项目可用的时候,
它最多阻塞timeout
传入的秒数, 之后会触发
`Empty
<https://docs.python.org/3/library/queue.html#queue.Empty>`__
异常. 反之 (block
为 False
), 项目可用直接返回, 否则触发
`Empty
<https://docs.python.org/3/library/queue.html#queue.Empty>`__
异常 (这种情况下``timeout``参数被忽略).
相当于 get(False)
.
两个方法用于支持追踪入队任务是否被消费者线程完整地处理.
Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.
表明之前入队的任务完成了. 被用于队列消费者线程. 每个
`get()
<https://docs.python.org/3/library/queue.html#queue.Queue.get>`__
用于获取一个任务,
随后调用`task_done()
<https://docs.python.org/3/library/queue.html#queue.Queue.task_done>`__
告诉队列任务完成. 如果此时使用
`join()
<https://docs.python.org/3/library/queue.html#queue.Queue.join>`__
, 会导致线程阻塞, 当所有项目完成, 线程恢复, 不再阻塞
(意味着放入队列的每个项目都收到了
`task_done()
<https://docs.python.org/3/library/queue.html#queue.Queue.task_done>`__
调用).
Raises a
`ValueError
<https://docs.python.org/3/library/exceptions.html#ValueError>`__
if called more times than there were items placed in the queue.
q.join() 实际上意味着等到队列为空,再执行别的操作
阻塞, 直到队列里面的所有项目被获取并处理完成. 未完成的任务有一个计数器,
项目入队, 计数器增加. 当消费者线程调用
`task_done()
<https://docs.python.org/3/library/queue.html#queue.Queue.task_done>`__
表明该项目完成, 计数器减少. 当计数器下降至0,
`join()
<https://docs.python.org/3/library/queue.html#queue.Queue.join>`__不再阻塞.
实例¶
等待队列完成任务.
def worker():
while True:
item = q.get()
if item is None:
break
do_work(item)
q.task_done()
q = queue.Queue()
threads = []
for i in range(num_worker_threads):
t = threading.Thread(target=worker)
t.start()
threads.append(t)
for item in source():
q.put(item)
# block until all tasks are done
q.join()
# stop workers
for i in range(num_worker_threads):
q.put(None)
for t in threads:
t.join()
其他¶
- Class
`multiprocessing.Queue
<https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue>`__
queue
类, 用于多进程(而不是多线程)上下文.
`collections.deque
<https://docs.python.org/3/library/collections.html#collections.deque>`__
is an alternative implementation of unbounded queues with fast atomic
`append()
<https://docs.python.org/3/library/collections.html#collections.deque.append>`__
and
`popleft()
<https://docs.python.org/3/library/collections.html#collections.deque.popleft>`__
operations that do not require locking.
collections.deque
是无界队列的另一种实现, 具有不需要锁的快速原子操作
append()
和 popleft()
random¶
choice(seq) method of Random instance
Choose a random element from a non-empty sequence.
randint(a, b) method of Random instance
Return random integer in range [a, b], including both end points.
random(...) method of Random instance
random() -> x in the interval [0, 1).
randrange(start, stop=None, step=1, _int=<class 'int'>) method of Random instance
Choose a random item from range(start, stop[, step]).
This fixes the problem with randint() which includes the
endpoint; in Python this is usually not what you want.
sample(population, k) method of Random instance
Chooses k unique random elements from a population sequence or set.
uniform(a, b) method of Random instance
Get a random number in the range [a, b) or [a, b] depending on rounding.
shuffle(x, random=None) method of Random instance
Shuffle list x in place, and return None.
Optional argument random is a 0-argument function returning a
random float in [0.0, 1.0); if it is the default None, the
standard random.random will be used.
random.choice()¶
>>> random.choice('hello')
'o'
>>> random.choice('hello')
'e'
>>> random.choice([1,2,3])
2
>>> random.choice(['apple','xxx','oa'])
'apple'
>>> random.choice(['apple','xxx','oa'])
'oa'
random.randint()¶
>>> random.randint(0,100)
76
random.random()¶
>>> random.random()
0.0865458073401395
random.randrange()¶
>>> random.randrange(0,3,2)
0
>>> random.randrange(0,3,2)
2
>>> random.randrange(0,3,2)
2
random.sample()¶
>>> random.sample('hello',2)
['e', 'l']
random.uniform()¶
>>> random.uniform(1,3)
1.8014312865217799
>>> random.uniform(1,3)
2.137511066847893
random.shuffle()¶
洗牌
>>> items = [1,2,3,4,5,6,7,8,9]
>>> random.shuffle(items)
>>> items
[8, 5, 4, 7, 2, 9, 1, 6, 3]
正则表达式re模块¶
正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。得益于这一点,在提供了正则表达式的语言里,正则表达式的语法都是一样的,区别只在于不同的编程语言实现支持的语法数量不同;但不用担心,不被支持的语法通常是不常用的部分。如果已经在其他语言里使用过正则表达式,只需要简单看一看就可以上手了。
正则表达式概念¶
- 使用单个字符串来描述匹配一系列符合某个句法规则的字符串
- 是对字符串操作的一种逻辑公式
- 应用场景: 处理文本和数据
- 正则表达式是过程: 依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;否则匹配失败
字符匹配¶
匹配任意一个字符
# 导入模块
>>> import re
# 匹配字符串abc
>>> re.match('a.c','abc').group()
'abc'
数字与非数字
# 匹配任意一数字
>>> re.match('\d','1').group()
'1'
# 匹配任意一个非数字
>>> re.match('\D','a').group()
'a'
空白与非空白
>>> re.match('\s',' ').group()
' '
>>> re.match('\S','a').group()
'a'
>>> re.match('\S','1').group()
'1'
单词字符与非单词字符
单词字符即代表[a-zA-Z0-9]
>>> re.match('\w','a').group()
'a'
>>> re.match('\w','1').group()
'1'
# 匹配任意一个非单词字符
>>> re.match('\W',' ').group()
' '
次数匹配¶
字符 | 匹配 |
---|---|
* |
匹配前一个字符0次或无限次 |
+ |
匹配前一个字符1次或无限次 |
? |
匹配前一个字符0次或者1次 |
{m}/{m,n} |
匹配前一个字符m次或者m到n次 |
*?/+?/?? |
匹配模式变为懒惰模式(尽可能少的匹配字符) |
介绍
字符 | 匹配 |
---|---|
(prev)? |
0个或1个prev |
(prev)* |
0个或多个prev,尽可能多地匹配 |
(prev)*? |
0个或多个prev,尽可能少地匹配 |
(prev)+ |
1个或多个prev,尽可能多地匹配 |
(prev)+? |
1个或多个prev,尽可能少地匹配 |
(prev){m} |
m个连续的prev |
(prev){m,n} |
m到n个连续的prev,尽可能多地匹配 |
(prev){m,n}? |
m到n个连续的prev,尽可能少地匹配 |
[abc] |
a或b或c |
[^abc] |
非(a或b或c) |
匹配前一个字符0次或者无限次
>>> re.match('[A-Z][a-z]*','Aaa').group()
'Aaa'
>>> re.match('[A-Z][a-z]*','Aa').group()
'Aa'
>>> re.match('[A-Z][a-z]*','A').group()
'A'
匹配前一个字符至少1次或者无限次
>>> re.match('[A-Z][a-z]+','A').group()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'
>>> re.match('[A-Z][a-z]+','Aa').group()
'Aa'
>>> re.match('[A-Z][a-z]+','Aaaa').group()
'Aaaa'
匹配前一个字符0次或者1次
>>> re.match('[A-Z][a-z]?','A').group()
'A'
>>> re.match('[A-Z][a-z]?','Aaa').group()
'Aa'
匹配前一个字符m次或者m-n次
>>> re.match('\w{5}','as432dasdasd').group()
'as432'
>>> re.match('\w{6,10}','as432dasdasd').group()
'as432dasda'
懒惰匹配
>>> re.match(r'[0-9][a-z]*','1bc').group()
'1bc'
>>> re.match(r'[0-9][a-z]*?','1bc').group()
'1'
>>> re.match(r'[0-9][a-z]+?','1bc').group()
'1b'
>>> re.match(r'[0-9][a-z]??','1bc').group()
'1'
边界匹配¶
字符 | 匹配 |
---|---|
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
:raw-latex:`\A `:raw-latex:`Z` | 指定的字符串必须出现在开头/结尾 |
匹配字符串开头
# 必须以指定的字符串开头,结尾必须是@163.com
>>> re.match('^[\w]{4,6}@163.com$','fafafd@163.com').group()
'fafafd@163.com'
匹配字符串结尾
# 必须以.com结尾
>>> re.match('[\w]{1,20}\.com$','163.com').group()
'163.com'
指定字符串必须出现在开头/结尾
>>> re.match(r'\Awww[\w]*\me','wwwanshengme').group()
'wwwanshengme'
正则表达式分组匹配¶
>>> re.match('yang|ccc','yang').group()
'yang'
>>> re.match('yang|ccc','ccc').group()
'ccc'
(ab)括号中表达式作为一个整体
>>> re.match(r"[\w]{4,6}@(163|126).com","fdafa@126.com").group()
'fdafa@126.com'
>>> re.match(r"[\w]{4,6}@(163|126).com","fdafa@163.com").group()
'fdafa@163.com'
(?P)分组起一个别名
>>> re.search("(?P<zimu>abc)(?P<shuzi>123)","abc123").groups()
('abc', '123')'
引用别名为name的分组匹配字符串 有问题…待处理
>>> res.group("shuzi")
'123'
>>> res.group("zimu")
'abc'
re模块常用的方法¶
语法格式
match(pattern,string,flags=0)
释义:
Try to apply the pattern at the start of the string,returning a match object,or None if no match was found.
从头开始匹配, 不管是否使用^
都是从字符串开始进行匹配
# 从头开始匹配,匹配成功则返回匹配的对象
>>> re.match("abc","abc123efa").group()
'abc'
# 从头开始匹配,如果没有匹配到对应的字符串就报错
>>> re.match("\d","abc123efa").group()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'
>>>
match与search的区别
>>> import re
# 从字符串开始进行匹配
>>> re.match("b.+", "abc123abc")
# search 匹配最先匹配到的内容, 同时, 只匹配最先匹配到的内容
>>> re.search("b.+", "abc123abc")
<_sre.SRE_Match object; span=(1, 9), match='bc123abc'>
search(pattern,string,flags=0)
Scan through string looking for a match to the pattern,returning a match object,or None if no match was found.
匹配最先匹配到的内容, 同时, 只匹配最先匹配到的内容
# 匹配整个字符串,匹配到第一个的时候就返回匹配到的对象
>>> re.search("\d","abc123daf").group()
'1'
findall(pattern,string,flags=0)
Return a list of all non-overlapping matches in the string.
将所有能匹配到的内容都匹配出来, 返回一个列表存储匹配到的内容
# 把匹配到的字符串以列表的形式返回
>>> re.findall("\d","fda123fda")
['1', '2', '3']
findall没有group方法
>>> re.search("(?P<id>[0-9]+)", "abcd1234daf@34")
<_sre.SRE_Match object; span=(4, 8), match='1234'>
>>> re.search("(?P<id>[0-9]+)", "abcd1234daf@34").group()
'1234'
>>> re.search("(?P<id>[0-9]+)", "abcd1234daf@34").groupdict()
{'id': '1234'}
split(pattern,string,maxsplit=0)
Split the source string by the occurrences of the pattern,returning a list containing the resulting substrings.
# 指定以数字进行分割,返回的是一个列表对象
>>> re.split("\d+","abc1234=+-*/56")
['abc', '=+-*/', '']
# 以多个字符进行分割
>>> re.split("[\d,]","a,b12")
['a', 'b', '', '']
>>> re.split('[0-9]', 'abc12de3f4g5y')
['abc', '', 'de', 'f', 'g', 'y']
>>> re.split('[0-9]+', 'abc12de3f4g5y')
['abc', 'de', 'f', 'g', 'y']
sub(pattern,repl,string,count=0)
Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it’s passed the match object and must return a replacement string to be used.
# 把abc替换成def
>>> re.sub("abc","def","abc123abc")
'def123def'
# 只替换查找到的第一个字符串
>>> re.sub("abc","def","abc123abc",count=1)
'def123abc'
>>> re.sub('[0-9]+', '|', 'abc12de3f456gf')
'abc|de|f|gf'
>>> re.sub('[0-9]+', '|', 'abc12de3f456gf', count=2)
'abc|de|f456gf'
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
- 编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
- 用编译后的正则表达式去匹配字符串。
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:
>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')
编译后生成Regular Expression对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。
匹配模式¶
仅需知道如下几个模式
- re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法, 下同)
- M(MULTILINE): 多行模式, 改变
^
,$
的行为 - S(DOTALL): 点任意匹配模式, 改变
.
的行为
>>> re.search('[a-z]+', 'abcdA', flags=re.I)
<_sre.SRE_Match object; span=(0, 5), match='abcdA'>
>>> re.search(r'^a', '\nabc\nrrr', flags=re.M)
<_sre.SRE_Match object; span=(1, 2), match='a'>
>>> re.search(r'^a', '\nabc\nrrr')
实例¶
string方法包含了一百个可打印的ASCII字符,大小写字母,数字,空格以及标点符号
>>> import string
>>> printable = string.printable
>>> printable
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
>>> import re
# 定义的字符串
>>> source = '''I wish I may,I wish I might
... have a dish of fish tonight.'''
# 在字符串中检索wish
>>> re.findall('wish',source)
['wish', 'wish']
# 在字符串中检索任意wish或fish
>>> re.findall('wish|fish',source)
['wish', 'wish', 'fish']
# 从字符串开头开始匹配wish
>>> re.findall('^wish',source)
[]
# 从字符串开头匹配I wish
>>> re.findall('^I wish',source)
['I wish']
# 匹配以fish结尾
>>> re.findall('fish$',source)
[]
>>> re.findall('fish tonight.$',source)
['fish tonight.']
# 匹配wish或者fish
>>> re.findall('[wf]ish',source)
['wish', 'wish', 'fish']
# 匹配 w s h 任意组合
>>> re.findall('[wsh]+',source)
['w', 'sh', 'w', 'sh', 'h', 'h', 'sh', 'sh', 'h']
# 匹配ght并且后面跟着一个非单词字符
>>> re.findall('ght\W',source)
['ght.']
# 匹配后面为 " wish"的I
>>> re.findall('I (?=wish)',source)
['I ', 'I ']
>>> re.findall('(?<=I) (wish|might)',source)
['wish', 'wish', 'might']
>>> re.match('a','Abc',re.I).group()
'A'
>>> import re
>>> pa = re.compile(r'yangjin')
>>> pa.match("yangjin")
<_sre.SRE_Match object; span=(0, 7), match='yangjin'>
>>> ma = pa.match("yangjin")
>>> ma
<_sre.SRE_Match object; span=(0, 7), match='yangjin'>
# 匹配到的值村道group内
>>> ma.group()
'yangjin'
# 返回字符串的所有位置
>>> ma.span()
(0, 7)
# 匹配的字符串会被放到string中
>>> ma.string
'yangjin'
# 实例放在re中
>>> ma.re
re.compile('yangjin')
requests模块¶
https://github.com/kennethreitz/requests
http://docs.python-requests.org/en/master/
安装requests¶
通过pip安装
pip3 install requests
或者下载源码安装
git clone https://github.com/kennethreitz/requests.git
cd requests
python setup.py install
发送请求与传递参数¶
>>> import requests
>>> r = requests.get(url='http://www.baidu.com')
>>> print(r.status_code) # 返回状态吗
200
>>> r = requests.get(url='http://dict.baidu.com/s',params={'wd':'python'})
>>> print(r.url)
http://dict.baidu.com/s?wd=python
>>> print(r.text) # 打印解码后返回的数据
其他方法
很简单吧!不但GET方法简单,其他方法都是统一的接口样式哦!
requests.get('127.0.0.1') #GET请求
requests.post(“http://xxx.org/post”) #POST请求
requests.put(“http://xxx.org/put”) #PUT请求
requests.delete(“http://xxx.org/delete”) #DELETE请求
requests.head(“http://xxx.org/get”) #HEAD请求
requests.options(“http://xxx.org/get”) #OPTIONS请求
上面这些HTTP方法,对于WEB一般只支持GET和POST,有一些还支持HEAD方法
POST发送JSON数据
import requests
import json
r = requests.post('https://api.github.com/some/endpoint', data=json.dumps({'some': 'data'}))
print(r.json())
定制header
import requests
import json
data = {'some': 'data'}
headers = {'content-type': 'application/json',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0'}
r = requests.post('https://api.github.com/some/endpoint', data=data, headers=headers)
print(r.text)
response对象¶
使用requests方法后,会返回一个response对象,其存储了服务器响应的内容,如r.text,r.status_code
获取文本方式的响应实例:当你访问r.text的时候,会使用期响应的文本编码进行解码,并且你可以修改其编码让r.text使用自定义的编码进行解码.
shelve¶
shelve模块是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的Python数据格式
import shelve
import datetime
d = shelve.open('shelve_test') # 打开一个文件
class Test(object):
def __init__(self,n):
self.n = n
t = Test(123)
t2 = Test(123456)
name = ['xxx','ooo','test']
info = {'age':18, 'job': 'it'}
d['name'] = name # 持久化列表
d['t1'] = t # 持久化类
d['t2'] = t2
d['info'] = info # 持久化字典
d['date] = datetime.datetime.now()
d.close()
会在当前目录生成一个shelve_test.db
struct¶
Python提供一个 struct
模块来解决bytes
和其他二进制数据类型的转换.
struct
的pack
函数把任意数据类型变成bytes
>>> import struct
>>> struct.pack('>I',10240099)
b'\x00\x9c@c'
pack
的第一个参数是处理指令,'>I'
的意思是:
>
表示字节顺序是big-endian,也就是网络序,I
表示4字节无符号整数。
后面的参数个数要和处理指令一致。
unpack
把 bytes
变成相应的数据类型:
>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)
根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数。
所以,尽管Python不适合编写底层操作字节流的代码,但在对性能要求不高的地方,利用struct就方便多了。
struct模块定义的数据类型可以参考Python官方文档: https://docs.python.org/3/library/struct.html#format-characters
Windows的位图文件(.bmp)是一种非常简单的文件格式,我们来用struct分析一下。
首先找一个bmp文件,没有的话用“画图”画一个。
读入前30个字节来分析:
>>> s = b'\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'
BMP格式采用小端方式存储数据,文件头的结构按顺序如下:
两个字节:’BM’表示Windows位图,’BA’表示OS/2位图; 一个4字节整数:表示位图大小; 一个4字节整数:保留位,始终为0; 一个4字节整数:实际图像的偏移量; 一个4字节整数:Header的字节数; 一个4字节整数:图像宽度; 一个4字节整数:图像高度; 一个2字节整数:始终为1; 一个2字节整数:颜色数。
>>> struct.unpack('<ccIIIIIIHH', s)
(b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)
请编写一个bmpinfo.py,可以检查任意文件是否是位图文件,如果是,打印出图片大小和颜色数。¶
tablib¶
示例¶
data为数据列表,里面是一行行内容
tablib.Dataset(*data, headers=headers, title=sheet_name)
# tmp_data 为一个字典, 字典的 value 是dataset
book = tablib.Databook(tuple(tmp_data.values()))
with open(filename, 'wb') as f:
f.write(book.xlsx)
# 添加排序
book = tablib.Databook(tuple(tmp_data[key] for key in sorted(tmp_data.keys())))
with open(filename, 'wb') as f:
f.write(book.xlsx)
threading¶
几个事实
- Python 默认参数创建线程后,不管主线程是否执行完毕,都会等待子线程执行完毕才一起退出,有无join结果一样
- 如果创建线程,并且设置了daemon为true,即thread.setDaemon(True), 则主线程执行完毕后自动退出,不会等待子线程的执行结果。而且随着主线程退出,子线程也消亡。
- join方法的作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,子线程还没有结束,则主线程强制结束子线程。
- 如果线程daemon属性为False, 则join里的timeout参数无效。主线程会一直等待子线程结束。
- 如果线程daemon属性为True, 则join里的timeout参数是有效的, 主线程会等待timeout时间后,结束子线程。此处有一个坑,即如果同时有N个子线程join(timeout),那么实际上主线程会等待的超时时间最长为 N * timeout, 因为每个子线程的超时开始时刻是上一个子线程超时结束的时刻。
time¶
在Python中,通常有几种方式表示时间
- 时间戳
- 格式化的时间字符串
- 元组(共九个元素)
time.strftime()¶
>>> x = time.localtime()
>>> x
time.struct_time(tm_year=2017, tm_mon=6, tm_mday=2, tm_hour=10, tm_min=51, tm_sec=34, tm_wday=4, tm_yday=153, tm_isdst=0)
# time.strftime("%Y-%m-%d %H:%M:%S",struct_time) 将struct_time 转成格式化的字符串
>>> time.strftime("%Y-%m-%d %H:%M:%S",x)
'2017-06-02 10:51:34'
time.strptime()¶
# time.strptime('2017-06-02 10:51:34',"%Y-%m-%d %H:%M:%S") 将格式化的字符串转成struct_time
>>> time.strptime('2017-06-02 10:51:34',"%Y-%m-%d %H:%M:%S")
time.struct_time(tm_year=2017, tm_mon=6, tm_mday=2, tm_hour=10, tm_min=51, tm_sec=34, tm_wday=4, tm_yday=153, tm_isdst=-1)
>>>
xlrd & xlwt 处理Excel¶
使用第三方库xlrd和xlwt,分别用于excel读和写
安装
pip3 install xlrd
pip3 install xlwt
写表格示例¶
import xlwt
def set_style(name,height,bold=False):
"""
设置单元格样式
:param name: 字体名字
:param height: 字体大小
:param bold: 是否加粗
:return: 返回样式
"""
style = xlwt.XFStyle()
font = xlwt.Font()
font.name = name
font.bold = bold
font.color_index = 4
font.height = height
style.font = font
return style
def write_excel():
"""
写表格
:return:
"""
f = xlwt.Workbook() # 创建工作簿
sheet1 = f.add_sheet('dataaaa',cell_overwrite_ok=True)
row0 = ['姓名','卡号','部门']
# 生成第一行
for i in range(0,len(row0)):
sheet1.write(0,i,row0[i],set_style('宋体',200,True)) # 200对应的是10号字体,如果设置太小,可能看上去像空Excel,实际上是有内容的
f.save('test1.xls')
if __name__ == '__main__':
write_excel()
xml¶
import xml.etree.ElementTree as ET
help(ET)
fromstring = XML(text, parser=None)
Parse XML document from string constant.
This function can be used to embed "XML Literals" in Python code.
*text* is a string containing XML data, *parser* is an
optional parser instance, defaulting to the standard XMLParser.
Returns an Element instance.
parse(source, parser=None)
Parse XML document into element tree.
*source* is a filename or file object containing XML data,
*parser* is an optional parser instance defaulting to XMLParser.
Return an ElementTree instance.
xml.etree.ElementTree¶
import xml.etree.ElementTree as ET
data = r'''<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>cn.xxx.xlsx</groupId>
<artifactId>xlsx</artifactId>
<versioning>
<release>1.1.3</release>
<versions>
<version>1.0</version>
<version>1.1</version>
<version>1.1.1</version>
<version>1.1.2</version>
<version>1.1.3</version>
</versions>
<lastUpdated>20170120083155</lastUpdated>
</versioning>
</metadata>
'''
root = ET.fromstring(data)
for node in root.iter('version'):
print(node,node.text)
r = ET.parse('xmltest.xml')
for node1 in root.iter('version'):
print(node1,node1.text)
xml.parsers.expat(难用)¶
import requests
from xml.parsers.expat import ParserCreate
class AppHandle(object):
def __init__(self):
self.now_tag = ''
self.version = []
def start_element(self, tag, attrs):
self.now_tag = tag
def version_data(self, version):
if self.now_tag == 'version':
if version.strip() != '':
self.version.append(version)
def end_element(self,name):
pass
data = r'''<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>cn.xxx.xlsx</groupId>
<artifactId>xlsx</artifactId>
<versioning>
<release>1.1.3</release>
<versions>
<version>1.0</version>
<version>1.1</version>
<version>1.1.1</version>
<version>1.1.2</version>
<version>1.1.3</version>
</versions>
<lastUpdated>20170120083155</lastUpdated>
</versioning>
</metadata>
'''
handler = AppHandle()
parser = ParserCreate()
parser.StartElementHandler = handler.start_element
parser.EndElementHandler = handler.end_element
parser.CharacterDataHandler = handler.version_data
parser.Parse(data)
print(handler.version)
scripts¶
发送邮件¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : yang
# @version : 0.2
import smtplib
import time
import os
from email.mime.text import MIMEText
from email.utils import formataddr
from email.mime.multipart import MIMEMultipart
class MyEmail(object):
def __init__(self, mail_sender=None, pwd=None, content=None, file_path=None, subject=None,
to_list=None, cc_list=None, bcc_list=None):
"""
:param mail_sender: 发件人
:param pwd: 发件人密码
:param content: 邮件内容
:param file_path: 文件列表
:param subject: 主题
:param to_list: 收件人列表
:param cc_list: 抄送人列表
:param bcc_list: 按送人列表
"""
self.mail_sender = mail_sender
self.password = pwd
self.to_list = to_list if to_list else []
self.cc_list = cc_list if cc_list else []
self.bcc_list = bcc_list if bcc_list else []
self.file_path = file_path if file_path else []
self.subject = subject
# self.filename = os.path.basename(self.file_path)
self.content = content
self.msg = self.generate_mail()
def generate_mail(self):
"""
生成邮件内容
:return:
"""
msg = MIMEMultipart()
msg['Subject'] = self.subject
# 发件人,收件人,主题等等
msg['From'] = self.mail_sender
# 收件人
if self.to_list:
msg['To'] = ";".join(self.to_list)
# 抄送
if self.cc_list:
msg['CC'] = ";".join(self.cc_list)
# 暗送
if self.bcc_list:
msg['bcc'] = ";".join(self.bcc_list)
# 邮件正文
if self.content:
body = MIMEText(self.content)
msg.attach(body)
# 附件
if isinstance(self.file_path, list):
for file in self.file_path:
filename = os.path.basename(file)
att = MIMEText(open(file, 'rb').read(), 'base64', 'utf-8')
att["Content-Type"] = 'application/octet-stream'
# print(self.filename)
att.add_header('Content-Disposition', 'attachment', filename=('gbk', '', filename))
msg.attach(att)
return msg
def send_email(self,smtp_server="smtp.exmail.qq.com", ssl=True, port=465):
"""
发送邮件
:param smtp_server: smtp服务器
:param ssl: 默认使用ssl
:param port: 端口
:return:
"""
try:
if ssl:
server = smtplib.SMTP_SSL(smtp_server, port)
else:
server = smtplib.SMTP(smtp_server, port)
server.login(self.mail_sender, self.password)
server.sendmail(self.mail_sender, self.to_list + self.cc_list + self.bcc_list, self.msg.as_string())
server.quit()
print('success')
except Exception as e:
print(e)
if __name__ == '__main__':
DATABASE_BRAVE = {'smtp_server': 'smtp.163.com', 'port': 25, 'username': 'brxxx@163.com', 'password': 'xxxxxxx00'}
username = DATABASE_BRAVE['username']
password = DATABASE_BRAVE['password']
subject = 'xxx-' + time.strftime('%Y-%m-%d-%H%M%S')
content = '邮件内容 xxx'
file_path = ["/Users/xxx/Pictures/哈哈.jpg",]
to_list = ['493535459@qq.com',]
# cc_list = ['xxx@qq.com']
# bcc_list = ['xxx@qq.com',]
my_email = MyEmail(
mail_sender=username, pwd=password,
content=content,
file_path=file_path, subject=subject,
to_list=to_list,
# cc_list=cc_list,
# bcc_list=bcc_list
)
my_email.send_email(smtp_server=DATABASE_BRAVE['smtp_server'], port=DATABASE_BRAVE['port'], ssl=True)
tips¶
高阶函数¶
高阶函数¶
filter¶
python內建的filter()
用于过滤序列
和map()
类似,filter()
也接收一个函数和一个序列.
filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素.
例如,在一个list中,删掉偶数,保留奇数
>>> L = [1,2,3,4,5,6,7,8,9,10]
>>> list(filter(lambda x:x % 2 == 1,L))
[1, 3, 5, 7, 9]
把一个序列中的空字符串删掉
>>> list(filter(lambda x:x and x.strip() ,["A"," ","",None,"C",1]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
AttributeError: 'int' object has no attribute 'strip'
>>> list(filter(lambda x:x and x.strip() ,["A"," ","",None,"C","1"]))
['A', 'C', '1']
>>> list(filter(lambda x:x and str(x).strip() ,["A"," ","",None,"C",1]))
['A', 'C', 1]
filter()
函数返回的是一个
Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获取所有结果并返回list
>>> list(filter(lambda x:str(x) == str(x)[::-1],range(1,1000)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999]
>>>
map/reduce¶
reduce(...)
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
>>> L1 = ['adam', 'LISA', 'barT']
>>> list(map(lambda s:s[0].upper()+s[1:].lower() ,L1))
['Adam', 'Lisa', 'Bart']
>>> from functools import reduce
>>> reduce(lambda x,y : x * y,[3,5,7,9])
945
把字符串’123.456’转换成浮点数123.456:
>>> def char2num(s):
... return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,".":"."}[s]
...
>>> s = "1234.56700"
reduce(lambda x,y:x*10+y,map(char2num,s.split(".")[0]))+reduce(lambda x,y:x/10 + y,map(char2num,s.split(".")[1][::-1]))/10
def str2float(s):
digit = {'1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '0': 0, '.': '.'}
l = list(map(lambda c: digit[c], s))
n = l.index('.')
l.remove('.')
return reduce(lambda x, y: 10*x+y, l)/(10**(len(l)-n))
sorted¶
排序算法
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:
list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。
我们给sorted传入key函数,即可实现忽略大小写的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
>>> L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
#按分数排序
>>> sorted(L,key=lambda x : x[1])
[('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]
>>> sorted(L,key=lambda x : x[1],reverse=True)
[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
# 按名字排序
>>> sorted(L,key=lambda x : x[0])
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
>>> sorted(L)
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。
tips¶
python实用脚本¶
连接oralce, 取出数据(数据和字段), 写入excel¶
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import cx_Oracle as db
import os
import xlwt
import sys
import datetime
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
def queryOracle(sql):
username = "USxxx"
passwd = "xxx"
host = "192.166.xx.xx"
port = "1521"
sid = "xx"
dsn = db.makedsn(host, port, sid)
con = db.connect(username, passwd, dsn)
cur = con.cursor()
cur.execute(sql)
columnsname = [tuple[0] for tuple in cur.description] #取出SQL的字段名称:做一个列表解析,之后返回
results = cur.fetchall()
cur.close()
con.close()
return (columnsname,results)
def set_style(name,height,bold=False):
"""
设置单元格样式
:param name: 字体名字
:param height: 字体大小
:param bold: 是否加粗
:return: 返回样式
"""
style = xlwt.XFStyle()
font = xlwt.Font()
font.name = name
font.bold = bold
font.color_index = 4
font.height = height
style.font = font
return style
def write_excel(sql,name):
"""
写表格
:return:
"""
now_time = datetime.datetime.now()
yes_time = now_time + datetime.timedelta(days=-1)
yes_time=(yes_time.strftime('%Y%m%d'))+"new"
y_name=yes_time+name
#print(name)
f = xlwt.Workbook() # 创建工作簿
sheet1 = f.add_sheet(y_name,cell_overwrite_ok=True) ##第二参数用于确认同一个cell单元是否可以重设值。
#results=queryOracle(sql)
columnsname,results=queryOracle(sql)
# 生成第一行(字段名)
for i in range(len(columnsname)):
sheet1.write(0,i,columnsname[i],set_style('宋体',200,True)) # 200对应的是10号字体,如果设置太小,可能看上去像空Excel,实际上是有内容的
#写入数据
#if len(results)>0:print(results)
for count,row in enumerate(results):
for i in range(len(row)):
sheet1.write(count+1,i,row[i],set_style('宋体',200,True))
#f.save('test1.xls')
if os.path.exists(yes_time) is not True:
os.makedirs(yes_time)
path=os.path.join(os.getcwd(),yes_time,name)
#path=os.path.join('/usr/local/Scripts',yes_time,name)
f.save('%s.xls'%path)
#sys.exit()
if __name__=="__main__":
#sql = "select sysdate from dual"
#sql = "SELECT * from IF_ADVICE"
sql_1='''select gr.group_name as "groupName(路段)", count(1) as "orderNum(总数量)" from if_parking_order po inner JOIN if_order o on o.order_id=PO.order_id inner JOIN if_berth b on o.berth_id=b.berth_id inner JOIN IF_BERTH_GROUP gr ON b.GROUP_ID = gr.GROUP_ID where 1=1 and o.status in ('waitPay','yetPay','refund') and O.OUT_TIME >= (SELECT TRUNC (SYSDATE - 1) + 0 / 24 FROM dual ) AND O.OUT_TIME < (SELECT TRUNC (SYSDATE) + 0 / 24 FROM dual ) AND TO_CHAR(OUT_TIME, 'hh24:mi:ss') BETWEEN '07:00:00' AND '19:00:00' GROUP BY gr.group_id,gr.group_name ORDER BY gr.group_name'''
# write_excel(sql_1,'总订单')
#日间路段
sql_dic = {
sql_1:'日间路段-总订单',
}
for sql in sql_dic:
write_excel(sql,sql_dic[sql])
tips¶
some tips¶
isinstance()
判断¶isinstance(a,list)
判断某个属性是否是方法的最简单办法就是利用callable:
[f for f in dir('') if callable(getattr('',f))]
将列表L里面的字符转换为小写
>>> L = ['Hello', 'World', 18, 'Apple', None]
>>> [ s.lower() for s in L if isinstance(s,str)]
['hello', 'world', 'apple']
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
代码
def triangles():
L = [1]
while True:
yield L
L = [1] + [ L[i] + L[i+1] for i in range(len(L) - 1)] + [1]
n = 0
for t in triangles():
print(t)
n = n + 1
if n == 10:
break
import os
path = os.environ["PATH"]
print("当前PATH变量:",path)
my = '/root/bin'
# 注意环境变量分隔符
os.environ["PATH"] = os.environ["PATH"]+';'+my
print("设置之后的环境变量:",os.environ["PATH"])
import time
import sys
for i in range(50):
sys.stdout.write("#")
sys.stdout.flush()
time.sleep(0.1)
使用Python优雅的实现switch¶
https://www.zhihu.com/question/50498770?sort=created
优雅实现¶
运行效率不会变快, 但是代码容易维护
_registered_actions = {}
def action(name):
def decorator(f):
_registered_actions[name] = f
return f
return decorator
@action("getInfo")
def get_info(data):
...
@action("changeName")
def change_name(data):
...
def do_action(action_name, data):
try:
f = _registered_actions[action_name]
except KeyError:
return json.dumps(...)
else:
f(data)
...
2¶
在一个函数里用dict的{key:func}这种方式尚可,但用lambda 可读性就很惆怅了。。
另外这个dict建议放在类定义里,
首先在类定义里加上
allow_methods={"method1","method2"} #构造一个集合
具体调用的时候
if func_name in self.allow_methods:
func = getattr(self,"on_"+func_name)
func(...)
函数式编程¶
来自廖雪峰Python教程后的评论,做一个备份
这里的讨论仅仅涉及将函数作为参数,不涉及函数作为返回值的情况。¶
使用大家之前讨论的一个例子:
求1到100的和
def sum1(start, end):
total = 0
for x in range(start, end + 1):
total += x
return total
求1到100每项平方加1的和
def sum2(start, end):
total = 0
for x in range(start, end + 1):
total += x * x + 1
return total
观察上面的sum1函数和sum2函数,发现非常的相似,都是迭代地加某一个数,然后保存在变量total里,等到函数执行完成后返回该值。唯一不同的地方就是针对被求和的每一项的处理不同:sum1函数中就是每一项x本身,而在sum2函数中,每一项x则是经过x * x + 1或者x ** 2 + 1这样的操作后再参与相加的,而这样的操作可能是多种多样的,比如我要求1到100每个数的立方和,求1到100之间所有素数的和等等,而这个操作应该是独立于我们求和这个概念的:不管我们把每一项变成什么鬼样子,最终的目的就是要把他们全部累积起来。
所以我们可以把针对每一项的具体操作定义为函数,然后将其作为参数传入求和函数sum中
def sum(start, end, handle):
total = 0
for x in range(start, end + 1):
total += handle(x)
return total
其中的handle几句是处理每一项的具体函数,要计算什么素的和,就直接定义处理该数的handle函数即可
现在我们重新来求解上面两个问题:
求1到100的和
# 定义处理的handle函数
# 函数名你可以随便取名
def identity(n):
return n
print(sum(1, 100, identity))
求1到100每项平方加1的和
def square_plus_one(n):
return n ** 2 + 1
print(sum(1, 100, square_plus_one))
如果我们需要计算1到100的立方的和,同理可得
def cube(n):
return n ** 3
print(sum(1, 100, cube))
如果我们需要计算1到100内所有素数的和呢,也是一样的,只是可能我们的handle函数有些复杂而已
# 处理函数is_prime用于判断一个正整数是否为素数
# 如果是素数的话,则返回该数
# 否则则返回0
def is_prime(n):
def smallest_divisor(n):
return find_divisor(n, 2)
def find_divisor(n, test_divisor):
if pow(test_divisor, 2) > n:
return n
elif n % test_divisor == 0:
return test_divisor
else:
return find_divisor(n, test_divisor + 1)
if 1 == n:
retrun 0
elif smallest_divisor(n) == n:
return n
else:
return 0
print(sum(1, 100, is_prime))
其实,如果我们的脑洞在大一点,或者说抽象思维在强一点,就不难发现,其实阶乘和求和其实是类似的概念,区别仅仅在于:
- 将加号更换为乘号
- 接收累积的值的变量total的初始值为0变成了1
那么按照上面的sum求和函数,我们可以很容易的定义求积函数product
def product(start, end, handle):
total = 1
for x in range(start, end + 1):
total *= handle(x)
return total
使用上面定义的product函数,以及上面我们定义的identity函数:
def identity(n):
return n
我们很容易实现阶乘函数factroial函数:
def factroial(n):
return product(1, n, identity)
同样的,如果把上面另外两种求和的情况改为求积的情况,也是非常容易的:仅需要将sum函数更换为product函数即可,其他的都不用改变!
其实,如果我们脑洞再够大一点的话,还能抽象出求和和求积表达的其实是一种更一般的概念:累积,简单的理解就是把一堆东西不断堆在一起,越变越大,不断膨胀的一种情况,求和和求积仅仅是两种特殊的累积方式罢了。
如果有了这样的想法,那我们其实是可以将求和和求积抽象为更高级更一般的概念:累积
老方法,我们还是通过观察比较sum函数和product函数的相同点和不同点,看看能得到啥:
sum函数
def sum(start, end, handle):
total = 0
for x in range(start, end + 1):
total += handle(x)
return total
product函数
def product(start, end, handle):
total = 1
for x in range(start, end + 1):
total *= handle(x)
return total
通过观察比较,其实也不难发现大部分地方都是相同的,不同的地方也就两个地方:
- 变量total的初始值不同
- 累积的方式不同,一个是用加号进行累加,另一个是用乘号进行累乘
然后呢,还是套路,就像上面我们在抽象1到100的和与1到100的平方加1的和来定义sum函数一样,将不同之处定义为函数,然后将这些函数作为参数传入即可。
因此我们来定义accmuluate函数来表达累积这个概念:
def accumulate(start, end, handle, init_value, combine):
def symbol(a, b, combine):
if '+' == combine:
return a + b
elif '*' == combine:
return a * b
else:
pass # 错误处理:
total = init_value
for x in range(start, end + 1):
total = symbol(total, handle(x), combine)
return total
有了这样一个更一般的函数,那我们来重新定义sum函数和product函数就轻松了许多:
def sum(start, end, handle):
return accumulate(start, end, handle, 0, '+')
def product(start, end, handle):
return accumulate(start, end, handle, 1, '*')
最后将完整的代码贴在这里,并计算1到100的和以及1到6的乘积:
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
def accumulate(start, end, handle, init_value, combine):
def symbol(a, b, combine):
if '+' == combine:
return a + b
elif '*' == combine:
return a * b
else:
pass # 错误处理
total = init_value
for x in range(start, end + 1):
total = symbol(total, handle(x), combine)
return total
def sum(start, end, handle):
return accumulate(start, end, handle, 0, '+')
def product(start, end, handle):
return accumulate(start, end, handle, 1, '*')
def identity(n):
return n
print(sum(1, 100, identity))
print(product(1, 6, identity))
运行结果:
root@kali:~/py3venv/scripts# python accumulate.py
5050
720
这里仅仅是我学习函数式编程时的一点点思考,参考的资料是SICP第一章第三节第一小节的内容,有兴趣的同学可以看看,但是Python可能有更简单更强大的方法来处理此类问题,但是编程思想的学习和探究应该是极其重要的。
简化¶
通过使用Python强大的map/reduce函数,上述代码将会大大的简化,这里只是定义Sum函数,Product函数以及他们的更一般抽象的Accumulate函数
# 为了不与Python提供的sum内置函数混淆
# 将我们自己定义的求和函数命名为Sum
def Sum(start, end, handle):
return reduce(lambda x, y: x + y, \
map(lambda x: handle(x), \
[x for x in range(start, end + 1)]))
def Product(start, end, handle):
return reduce(lambda x, y: x * y, \
map(lambda x: handle(x), \
[x for x in range(start, end + 1)]))
从上述Sum函数与Product函数我们可以抽象出Accumulate函数:
def Accumulate(start, end, handle, combine):
def symbol(a, b, combine):
if '+' == combine:
return a + b
elif '*' == combine:
return a * b
else:
pass
return reduce(lambda x, y: symbol(x, y, combine), \
map(lambda x: handle(x), \
[x for x in range(start, end + 1)]))
而通过Accumulate函数,我们又能容易地定义Sum函数和Product函数:
def Sum(start, end, handle):
return accumulate(start, end, handle, '+')
def Product(start, end, handle):
return accumulate(start, end, handle, '*')
最后我们来测试一下计算1到100的和以及1到6的乘积来验证上述函数是否正确
# 定义handle函数
def identity(n):
return n
测试结果:
>>> print(Sum(1, 100, identity))
5050
>>> print(Product(1, 6, identity))
720
字典,列表排序¶
字典¶
items = dict.items()
items.sort()
for key,value in items:
print(key, value) # print(key,dict[key])
- print(key, dict[key] for key in sorted(dict.keys()))
- 把dictionary中的元素分离出来放到一个list中,对list排序,从而间接实现对dictionary的排序。这个“元素”可以是key,value或者item。
- 用lambda表达式来排序,更灵活
sorted(dict.items(), lambda x, y: cmp(x[1], y[1]))
#降序
sorted(dict.items(), lambda x, y: cmp(x[1], y[1]), reverse=True)
遍历list和dict¶
List遍历¶
最常用最简单的遍历list的方法
a = ["a", "b", "c", "d"]
for i in a:
print i
但是, 如果我需要拿到list的 index
, 很多人可能会这样写
a = ["a", "b", "c", "d"]
for i in xrange(len(a)):
print i, a[i]
其实, python提供了一个方法 enumerate
, 用法如下
a = ["a", "b", "c", "d"]
for i, el in enumerate(a):
print i, el
上面两种方式的结果相同
0 a
1 b
2 c
3 d
这是一种更加方便便捷的方式, 虽然少写不了几个字符, 从代码可读性等方面来考量的话, 还是清晰很多的.
代码应该让人一目了然, 目的明确, 如果多种方式可以实现相同的功能, 那么我们应该选择一种大家更加容易理解的, enumerate就是这样的方式.
enumerate(iterable[, start]) -> iterator for index, value of iterable
第二个参数在很多时候也是很有用的, 比如我不希望从0开始, 希望从1开始
a = ["a", "b", "c", "d"]
for i, el in enumerate(a, 1):
print i, el
输出如下
1 a
2 b
3 c
4 d
如果你使用range的话, 会蹩脚很多.
Dict遍历¶
dict最简单的遍历方式
遍历字典key
d = {'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> for k in d:
... print(k)
...
c
a
d
b
>>> for k in d:
... print(k,d[k])
...
c 3
a 1
d 4
b 2
>>> d.keys()
dict_keys(['c', 'a', 'd', 'b'])
遍历字典值
>>> for k in d.values():
... print(k)
...
3
1
4
2
遍历字典的项
>>> for item in d.items():
... print(item)
...
('c', 3)
('a', 1)
('d', 4)
('b', 2)
遍历key,value
dict本身提供了items()方法, 可以做到k,v对遍历.数据量大的时候不要使用,会把dict先转成list
d = {'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> for k,v in d.items():
... print(k,v)
...
c 3
a 1
d 4
b 2
dict还有其他几个方法
错误,调试和测试¶
错误记录¶
在mac上装一个tensorflow做一些小的实验。用virtualenv的方式避免了很多麻烦。
但是在运行的时候发现报错:
RuntimeError: Python is not installed as a framework. The Mac OS X backend will not be able to function correctly if Python is not installed as a framework. See the Python documentation for more information on installing Python as a framework on Mac OS X. Please either reinstall Python as a framework, or try one of the other backends. If you are using (Ana)Conda please install python.app and replace the use of 'python' with 'pythonw'. See 'Working with Matplotlib on OSX' in the Matplotlib FAQ for more information.
解决方法
vim ~/.matplotlib/matplotlibrc
然后输入以下内容
backend: TkAgg
调试¶
logging才是最终武器
用print()
来辅助查看的地方,都可以使用断言(assert)来替代
def foo(s):
n = int(s)
assert n != 0, 'n is zero'
return 10/n
def main(arg):
foo(arg)
main('0')
assert
的意思是,n != 0
应该是True
,否则,根据程序运行的逻辑,后面的代码肯定会出错.
如果断言失败,assert
语句本身就会抛出AssertionError
Traceback (most recent call last):
...
assert n != 0, 'n is zero'
AssertionError: n is zero
不过程序中如果充斥着assert
,和print()
相比也好不到哪去.不过,启动Python解释器时可以使用-O
参数来关闭assert
关闭后,你可以把所有的assert
语句当做pass
来看
和assert
比,logging
不会抛出错误,而且会输出到文件
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' %n)
print(10 / n)
输出
INFO:root:n = 0
Traceback (most recent call last):
File "err.py", line 8, in <module>
print(10 / n)
ZeroDivisionError: division by zero
这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。
python3 -m pdb err.py
pdb.set_trace()
这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点
StyleGuide¶
Pythonic¶
链式比较¶
a = 3
b = 1
1 <= b <= a < 10 #True
真值测试¶
name = 'Tim'
langs = ['AS3', 'Lua', 'C']
info = {'name': 'Tim', 'sex': 'Male', 'age':23 }
if name and langs and info:
print('All True!') #All True!
真值
真 | 假 |
---|---|
True | False |
任意非空字符串 | 空的字符串 '' |
任意非0数字 | 数字0 |
任意非空容器 | 空的容器 [] () {} set() |
其他任意非False | None |
真假值表中还有一类判断,如果是用户自定义类,还需要对该类的实例的__nonzero__
和__len__
的返回值进行判断。
字符串反转¶
def reverse_str( s ):
return s[::-1]
字符串列表连接¶
strList = ["Python", "is", "good"]
res = ' '.join(strList) #Python is good
列表求和,最大值,最小值,乘积¶
numList = [1,2,3,4,5]
sum = sum(numList) #sum = 15
maxNum = max(numList) #maxNum = 5
minNum = min(numList) #minNum = 1
from operator import mul
prod = reduce(mul, numList, 1) #prod = 120 默认值传1以防空列表报错
列表推导¶
列表推导是C、C++、Java里面没有的语法,但是,是Python里面使用非常广泛,是特别推荐的用法。
l = [x*x for x in range(10) if x % 3 == 0]
#l = [0, 9, 36, 81]
与列表推导对应的,还有集合推导和字典推导。我们来演示一下。
>>> [ i*i for i in range(30, 41) if i% 2 == 0 ]
[900, 1024, 1156, 1296, 1444, 1600]
集合:1~20所有奇数的平方的集合
>>> { i*i for i in range(1, 21) if i % 2 != 0 }
{1, 225, 289, 9, 169, 361, 81, 49, 121, 25}
字典:30~40 所有奇数的平方
>>> { i:i*i for i in range(30, 40) if i% 2 != 0 }
{33: 1089, 35: 1225, 37: 1369, 39: 1521, 31: 961}
enumerate¶
array = [1, 2, 3, 4, 5]
for i, e in enumerate(array,0):
print i, e
#0 1
#1 2
#2 3
#3 4
#4 5
使用zip创建键值对¶
keys = ['Name', 'Sex', 'Age']
values = ['Tim', 'Male', 23]
dic = dict(zip(keys, values))
#{'Age': 23, 'Name': 'Tim', 'Sex': 'Male'}
Python练习¶
Python练习¶
Python 练习册,每天一个小程序¶
说明¶
- Python 练习册,每天一个小程序。注:将 Python 换成其他语言,大多数题目也适用
- 不会出现诸如「打印九九乘法表」、「打印水仙花」之类的题目
- 点此链接,会看到部分题目的代码,仅供参考
- 本文本文由@史江歌(shijiangge@gmail.com QQ:499065469)根据互联网资料收集整理而成,感谢互联网,感谢各位的分享。鸣谢!本文会不断更新。
Talk is cheap. Show me the code.–Linus Torvalds
第 0000 题¶
将你的 QQ 头像(或者微博头像)右上角加上红色的数字,类似于微信未读信息数量那种提示效果。
类似于图中效果

头像
from PIL import Image, ImageDraw, ImageFont
def draw_num(img):
draw = ImageDraw.Draw(img)
w, h = img.size
Font = ImageFont.truetype('BeauRivageOne.ttf', size=60)
draw.text((0.9* w, 0), '4', fill='red', font=Font)
img.save('haha.jpg', 'jpeg')
im = Image.open('pic-0000.jpg')
print(im.size)
draw_num(im)
第 0001 题¶
做为 Apple Store App 独立开发者,你要搞限时促销,为你的应用``生成激活码``(或者优惠券),使用 Python 如何生成 200 个激活码(或者优惠券)?
import uuid
def generate_code(count):
code_list = []
for i in range(count):
code = str(uuid.uuid4()).replace('-', '').upper()
if code not in code_list:
code_list.append(code)
return code_list
if __name__ == '__main__':
code_list = generate_code(200)
print('\n'.join(code_list))
第 0002 题¶
将 0001 题生成的 200 个激活码(或者优惠券)保存到MySQL关系型数据库中
import uuid
import pymysql
def generate_code(count):
code_list = []
for i in range(count):
code = str(uuid.uuid4()).replace('-', '').upper()
if code not in code_list:
code_list.append(code)
return code_list
def add_to_mysql(codes):
db = pymysql.connect(host='127.0.0.1', user='yang', passwd='111111', db='xxx')
cursor = db.cursor()
cursor.execute(r'''CREATE TABLE IF NOT EXISTS tb_code(
id INT NOT NULL AUTO_INCREMENT,
code VARCHAR(32) NOT NULL,
PRIMARY KEY(id) )''')
for code in codes:
cursor.execute('insert into tb_code(code) values(%s)', (code))
cursor.connection.commit()
db.close()
if __name__ == '__main__':
code_list = generate_code(200)
# print('\n'.join(code_list))
add_to_mysql(code_list)
第 0003 题¶
将 0001 题生成的 200 个激活码(或者优惠券)保存到 Redis 非关系型数据库中。
import uuid
import redis
def generate_code(count):
code_list = []
for i in range(count):
code = str(uuid.uuid4()).replace('-', '').upper()
if code not in code_list:
code_list.append(code)
return code_list
def insert_into_redis(codes):
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
counter = 0
for code in codes:
r.set('code-%s' % counter, code)
counter += 1
print(r.get('code-0'))
if __name__ == '__main__':
code_list = generate_code(200)
# print('\n'.join(code_list))
insert_into_redis(code_list)
第 0004 题¶
任一个英文的纯文本文件,统计其中的单词出现的个数。
简单版
import collections
import re
file_name = "The Old Man and the Sea.txt"
c = collections.Counter()
with open(file_name, 'r') as f:
c.update(re.findall(r'\b[a-zA-Z\']+\b', f.read()))
# c.update(re.findall(r'\b[a-zA-Z]+\b', f.read()))
with open("WordCount.txt", 'w') as wf:
for word in c.most_common():
wf.write(word[0]+','+str(word[1])+'\n')
第 0005 题¶
你有一个目录,装了很多照片,把它们的尺寸变成都不大于 iPhone5 分辨率的大小。
'''
你有一个目录,装了很多照片,把它们的尺寸变成都不大于 iPhone5 分辨率的大小。
'''
import os
from PIL import Image
DIR_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pic')
def re_size(dirPath, size_w, size_h):
f_list = os.listdir(dirPath)
print(f_list)
for i in f_list:
if os.path.splitext(i)[1] == '.jpg':
img = Image.open(os.path.join(dirPath, i))
w, h = img.size
if w < size_w and h < size_h:
continue
img.thumbnail((size_w, size_h))
img.save(os.path.join(dirPath, "thumbnail-%s" % i))
re_size(DIR_PATH, 1100, 800)
第 0006 题¶
你有一个目录,放了你一个月的日记,都是 txt,为了避免分词的问题,假设内容都是英文,请统计出你认为每篇日记最重要的词。
'''
你有一个目录,放了你一个月的日记,都是 txt,为了避免分词的问题,假设内容都是英文,请统计出你认为每篇日记最重要的词。
'''
import collections
import re
file_name = "The Old Man and the Sea.txt"
c = collections.Counter()
with open(file_name, 'r') as f:
c.update(re.findall(r'\b[a-zA-Z\']+\b', f.read()))
# c.update(re.findall(r'\b[a-zA-Z]+\b', f.read()))
l = filter(lambda x: len(x[0]) > 2 and x[0] != 'the' and x[0] != 'her' and x[0] != 'his', c.most_common())
print(list(l))
第 0007 题¶
有个目录,里面是你自己写过的程序,统计一下你写过多少行代码。包括空行和注释,但是要分别列出来。
第 0008 题¶
一个HTML文件,找出里面的正文。
'''
一个HTML文件,找出里面的正文。
'''
from bs4 import BeautifulSoup
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
soup = BeautifulSoup(html_doc, "html.parser")
print(soup.get_text())
第 0009 题¶
一个HTML文件,找出里面的链接。
'''
一个HTML文件,找出里面的链接。
'''
from bs4 import BeautifulSoup
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
soup = BeautifulSoup(html_doc, "html.parser")
for link in soup.find_all('a'):
print(link.get('href'))
第 0011 题¶
敏感词文本文件 filtered_words.txt,里面的内容为以下内容,当用户输入敏感词语时,则打印出 Freedom,否则打印出 Human Rights。
北京
程序员
公务员
领导
牛比
牛逼
你娘
你妈
love
sex
jiangge
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = "Ysara"
'''
敏感词文本文件 filtered_words.txt,里面的内容为以下内容,当用户输入敏感词语时,则打印出 Freedom,否则打印出 Human Rights。
'''
import re
from functools import reduce
with open('filtered_words.txt', 'r', encoding='utf8') as f:
# filtered_words = list(map(lambda x: x.strip(), f.readlines()))
filtered_pattern = reduce(lambda x, y: x.strip()+'|'+y, f.readlines())
print(filtered_pattern)
while True:
i = input("请输入: ")
if re.search(filtered_pattern, i):
print('Freedom')
else:
print('Human Rights')
第 0012 题¶
敏感词文本文件 filtered_words.txt,里面的内容 和
0011题一样,当用户输入敏感词语,则用 星号 *
替换,例如当用户输入「北京是个好城市」
,则变成
「**是个好城市」
。
'''
敏感词文本文件 filtered_words.txt,里面的内容 和 0011题一样,当用户输入敏感词语,则用 星号 * 替换,例如当用户输入「北京是个好城市」,则变成「**是个好城市」。
'''
import re
from functools import reduce
with open('filtered_words.txt', 'r', encoding='utf8') as f:
filtered_pattern = reduce(lambda x, y: x.strip()+'|'+y, f.readlines())
print(filtered_pattern)
while True:
i = input("请输入: ")
c = re.sub(filtered_pattern, '**', i)
print(c)
第 0014 , 0015, 0016 题¶
纯文本文件 student.txt为学生信息, 里面的内容(包括花括号)如下所示:
{
"1":["张三",150,120,100],
"2":["李四",90,99,95],
"3":["王五",60,66,68]
}
请将上述内容写到 student.xls 文件中,如下图所示:

student.xls
纯文本文件 city.txt为城市信息, 里面的内容(包括花括号)如下所示:
{
"1" : "上海",
"2" : "北京",
"3" : "成都"
}
请将上述内容写到 city.xls
文件中,如下图所示:

city.xls
纯文本文件 numbers.txt, 里面的内容(包括方括号)如下所示:
[
[1, 82, 65535],
[20, 90, 13],
[26, 809, 1024]
]

numbers.xls
- 阅读资料 腾讯游戏开发 XML 和 Excel 内容相互转换
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = "Ysara"
'''
0014
纯文本文件 student.txt为学生信息, 里面的内容(包括花括号)如下所示:
{
"1":["张三",150,120,100],
"2":["李四",90,99,95],
"3":["王五",60,66,68]
}
----
0015
纯文本文件 city.txt为城市信息, 里面的内容(包括花括号)如下所示:
{
"1" : "上海",
"2" : "北京",
"3" : "成都"
}
----
0016
纯文本文件 numbers.txt, 里面的内容(包括方括号)如下所示:
[
[1, 82, 65535],
[20, 90, 13],
[26, 809, 1024]
]
'''
import json
from collections import OrderedDict
from openpyxl import Workbook
with open('student.txt', 'r', encoding='utf8') as f:
students_info = json.load(f, object_pairs_hook=OrderedDict)
wb = Workbook()
sheet = wb.active
# 0014
sheet.title = "student"
for i in students_info:
sheet.append([i] + students_info[i])
# 0015
with open('city.txt', 'r', encoding='utf8') as f:
city = OrderedDict(json.load(f, object_pairs_hook=OrderedDict))
sheet_city = wb.create_sheet('city', index=1)
for item in city.items():
# ('1', '上海')
sheet_city.append(item)
# 0016
with open('numbers.txt', 'r', encoding='utf8') as f:
numbers = json.load(f)
sheet_num = wb.create_sheet('numbers', index=2)
for num in numbers:
sheet_num.append(num)
wb.save(r'student-city-num.xlsx')
print(students_info)
print(city)
print(numbers)
第 0017 题¶
将 第 0014 题中的 student.xls 文件中的内容写到 student.xml 文件中,如
下所示:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<students>
<!--
学生信息表
"id" : [名字, 数学, 语文, 英文]
-->
{
"1" : ["张三", 150, 120, 100],
"2" : ["李四", 90, 99, 95],
"3" : ["王五", 60, 66, 68]
}
</students>
</root>
第 0018 题¶
将 第 0015 题中的 city.xls 文件中的内容写到 city.xml 文件中,如下所示:
<?xmlversion="1.0" encoding="UTF-8"?>
<root>
<citys>
<!--
城市信息
-->
{
"1" : "上海",
"2" : "北京",
"3" : "成都"
}
</citys>
</root>
第 0019 题¶
将 第 0016 题中的 numbers.xls 文件中的内容写到 numbers.xml 文件中,如下
所示:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<numbers>
<!--数字信息-->
[
[1, 82, 65535],
[20, 90, 13],
[26, 809, 1024]
]
</numbers>
</root>
第 0020 题¶
登陆中国联通网上营业厅 后选择「自助服务」 –> 「详单查询」,然后选择你要查询的时间段,点击「查询」按钮,查询结果页面的最下方,点击「导出」,就会生成类似于 2014年10月01日~2014年10月31日通话详单.xls 文件。写代码,对每月通话时间做个统计。
第 0021 题¶
通常,登陆某个网站或者 APP,需要使用用户名和密码。密码是如何加密后存储起来的呢?请使用 Python 对密码加密。
第 0022 题¶
iPhone 6、iPhone 6 Plus 早已上市开卖。请查看你写得 第 0005 题的代码是否可以复用。
第 0025 题¶
使用 Python 实现:对着电脑吼一声,自动打开浏览器中的默认网站。
例如,对着笔记本电脑吼一声“百度”,浏览器自动打开百度首页。
关键字:Speech to Text
参考思路: 1:获取电脑录音–>WAV文件 python record wav
2:录音文件–>文本
STT: Speech to Text
STT API Google API
3:文本–>电脑命令
第 0026 题¶
Python练习¶
http://www.runoob.com/python/python-100-examples.html
有四个数字: 1,2,3,4,能组成多少个互不相同且无重复数字的三位数?各是多少¶
分析: 列出所有可能,去掉不满足条件的
#!/usr/bin/env python
# -*- coding: utf-8 -*-
count = 0
for i in range(1,5):
for j in range(1,5):
for k in range(1,5):
if (i != k ) and i !=j and j != k:
print("%s%s%s" % (i,j,k))
count += 1
print("总数量: ", count)
企业发放的奖金根据利润提成,具体如下¶
利润(I)低于或等于10万元时,奖金可提10%;利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%;20万到40万之间时,高于20万元的部分,可提成5%;40万到60万之间时高于40万元的部分,可提成3%;60万到100万之间时,高于60万元的部分,可提成1.5%,高于100万元时,超过100万元的部分按1%提成,从键盘输入当月利润I,求应发放奖金总数?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
i = int(input("请输入利润: "))
profit = [1000000,600000,400000,200000,100000,0]
rat = [0.01,0.15,0.03,0.05,0.075,0.1]
r = 0
for n in range(0,6):
if i > profit[n]:
r += (i - profit[n]) * rat[n]
i = profit[n]
print("奖金: ",r)
题目:在10000以内的数字找一个正整数,它加上100和加上268后都是一个完全平方数,请问该数是多少?¶
数学上,平方数,或称完全平方数,是指可以写成某个整数的平方的数,即其平方根为整数的数。 例如,9 = 3 × 3,它是一个平方数。 平方数也称正方形数,若n 为平方数,将 n 个点排成矩形,可以排成一个正方形。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import math
for i in range(1,10000):
#if math.sqrt(i + 100) % 1 == math.sqrt(i + 268) % 1:
if math.ceil(math.sqrt(i + 100)) == math.sqrt(i + 100) and math.ceil(math.sqrt(i + 268)) == math.sqrt(i + 268):
print(i)
输入某年某月某日,判断这一天是这一年的第几天?¶
- 年份能被4整除;
- 年份若是 100 的整数倍的话需被400整除,否则是平年。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
year = int(input("year: "))
month = int(input("month: "))
day = int(input("day: "))
sum = day
days = [31,28,31,30,31,30,31,31,30,31,30,31]
for i in range(0,month - 1):
sum += days[i]
leap = 0
if (year % 400 == 0) or (year % 4 == 0 and year % 100 != 0):
leap = 1
if (leap == 1) and (month > 2):
sum += 1
print("%s 是闰年" % (year))
print("It is the %sth day of %s/%s/%s" % (sum,year,month,day))
输入三个整数x,y,z,请把这三个数由小到大输出。¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
l = []
for i in range(3):
x = int(input("请输入一个整数: "))
l.append(x)
l.sort()
print(l)
斐波那契数列¶
斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……。
递归,输出第n个数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def fib(n):
if n == 0 or n == 1:
return n
else :
return fib(n-2) + fib(n-1)
print(fib(10))
输出一个数列
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def fib(n):
if n == 0 :
return [0]
if n == 1:
return [0,1]
fibs = [0,1]
for i in range(2,n):
fibs.append(fibs[i-1] + fibs[i-2])
return fibs
print(fib(10))
L = [1, 1]
for i in range(10):
# L.append(L[-2] + L[-1])
L.append(sum(L[i:]))
print(L)
将一个列表的数据复制到另一个列表中。¶
使用列表[:]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
a = [1,2,3]
b = a[:]
print(b)
>>> a=[1,2,3]
>>> b=a.copy()
>>> b
[1, 2, 3]
输出 9*9 乘法口诀表。¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
for i in range(1,10):
print()
for j in range(1,i+1):
print(i,"*",j,"=",i*j,"\t",end="")
暂停一秒输出。¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
mydict = {"var1" : 1 , "var2" : 2}
for key,value in dict.items(mydict):
print(key,value)
time.sleep(1)
暂停一秒输出,并格式化当前时间。¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
time.sleep(1)
print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?¶
判断101-200之间有多少个素数,并输出所有素数。¶
程序分析:判断素数的方法:只能被1和自身整除
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#!/usr/bin/env python
# -*- coding: utf-8 -*-
l = []
for i in range(101,201):
count = 0
for j in range(2,i+1):
if i % j == 0:
count += 1
if count == 1:
l.append(i)
print(l)
print("素数的数量是: ",len(l))
打印出所有的“水仙花数”¶
所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个“水仙花数”,因为153=1的三次方+5的三次方+3的三次方。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
for i in range(100,1000):
a = i // 100
b = i // 10 % 10
c = i % 10
if (a ** 3 + b ** 3 + c ** 3 == i):
print(i)
将一个正整数分解质因数。例如:输入90,打印出90=233*5。¶
Python练习¶
生成激活码¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import uuid
def generate_code(count):
code_list = []
for i in range(count):
code = str(uuid.uuid4()).replace('-','').upper()
if code not in code_list:
code_list.append(code)
return code_list
if __name__ == '__main__':
code_list = generate_code(200)
for code in code_list:
print(code)
闭包¶
s = [lambda x: x + i for i in range(10)]
print(s[0](10))
print(s[1](10))
print(s[2](10))
print(s[3](10))
def create_multipliers():
multipliers = []
for i in range(5):
# 两种情况,结果与期望完全不一样
# def multiplier(x):
def multiplier(x, i=i):
return i * x
multipliers.append(multiplier)
return multipliers
for mu in create_multipliers():
print(mu(2))
解决 两种方法
def create_multipliers():
return [lambda x, i=i : i * x for i in range(5)]
from functools import partial
from operator import mul
def create_multipliers():
return [partial(mul, i) for i in range(5)]
字典练习¶
要求key通过字典序升序排列(注意key可能是字符串)。¶
给你一字典a,如a = {1:“a”,2:“b”,3:“c”},输出字典a的key,以’,‘连接,如‘1,2,3’。要求key通过字典序升序排列(注意key可能是字符串)。
例如:a = {1:“a”,2:“b”,3:“c”}, 则输出:1,2,3
# Python join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。
# str.join(sequence)
# 参数:sequence -- 要连接的元素序列
# 返回值:返回通过指定字符连接序列中元素后生成的新字符串。
# 实例:以下实例展示了join()的使用方法:
>>> print(','.join([str(key) for key in a.keys()]))
1,2,3
# map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。
# 不改变原有list,返回一个新list
>>> print(','.join(map(str,a.keys())))
1,2,3
a = {1:"a",2:"b",3:"c"}
#Python 3 ,a.keys返回的是一个可迭代的视图对象,称之为 dict_keys,不能使用join
#>>> a.keys()
#dict_keys([1, 2, 3])
b=a.keys()
m=[]
for i in b:
m.append(str(i))
print(','.join(m))
字符串逆序输出¶
a = “12345”
>>> a[::-1]
'54321'
>>> a = "12345"
>>> l = list(a)
>>> l
['1', '2', '3', '4', '5']
>>> l.reverse()
>>> l
['5', '4', '3', '2', '1']
>>> ''.join(l)
'54321'
一个文本有100行内容, 现分10行, 写到10个文件¶
生成一个100行内容的的文件, 做文件拆分, 写到10个文件, 每个文件10行
#!/usr/bin/env python3
# _*_ coding: utf-8 _*_
for n in range(0,100,10):
with open('file') as reader, open(str(n // 10)+".txt", 'w') as writer:
for index, line in enumerate(reader):
if index >=n and index < n+10:
writer.write(line)
生成器试题¶
实现tail命令功能¶
import time
def tail(filename):
f = open(filename)
f.seek(0, 2) #从文件末尾算起
while True:
line = f.readline() # 读取文件中新的文本行
if not line:
time.sleep(0.1)
continue
yield line
tail_g = tail('tmp')
for line in tail_g:
print(line)
使用echo写内容到文件测试
send¶
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
结果
10.0
20.0
15.0
结果为何¶
def demo():
for i in range(4):
yield i
g=demo()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))
打印结果为
[0, 1, 2, 3]
[]
g1=(i for i in g)
# g1 为生成器 ,此时为空,所以g2 为空
g2=(i for i in g1)
结果为何? (二)¶
def add(n, i):
return n+i
def test():
for i in range(4):
yield i
g=test()
for n in [1,10]:
g=(add(n,i) for i in g)
print(list(g))
结果
[20, 21, 22, 23]
原因
n = 10
i = 10
n + i = 20
n = 10
i = 11
n + i = 21
...
web框架¶
Django¶
Django¶
- 安装python
- 安装Django
- 创建工程
在t_django目录执行命令
➜ t_django django-admin startproject mysite # mysite为工程名
➜ t_django cd mysite
➜ mysite tree
.
├── manage.py # 管理Django程序
└── mysite
├── __init__.py
├── settings.py # 配置文件
├── urls.py # url对应关系
└── wsgi.py # 遵循WSGI规范,发布的话使用 uwsgi+Nginx
运行
python manage.py runserver 0.0.0.0:8000

python-django-01
只要遵循这个规范,就可以用来创建socket
➜ python3 -V
Python 3.5.3
使用 pip
进行安装
# 默认安装最近稳定版本
pip3 install django
指定安装django
版本
pip install django==1.9
进入python
解释器,导入django
模块来进行校验是否正确安装
在导入的时候没有报错就表示已经安装成功,否则需要重新安装
➜ ~ python3
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.get_version()
'1.10.6'
django
为我们提供了一个 django-admin
命令,方便我们通过命令行创建
django
项目,使用 django-admin --help
查看该命令的帮助信息.
常用参数:
参数 | 描述 |
---|---|
startproject | 创建一个完整的项目 |
startapp | 创建一个app |
runserver | 运行django为我们提供的http服务 |
shell | 进入带django环境的shell |
makemigrations | 生成数据库命令 |
migrate | 执行生成好的数据库命令 |
使用startproject
创建项目
➜ ~ django-admin startproject yang
➜ ~ cd yang
➜ ls
manage.py yang
manage.py
文件是一个命令行工具,允许你以多种方式与该Django
项目进行交互,输入
python mange.py help
可以看到它为我们提供的指令,比如如下是常用的
指令 | 描述 |
---|---|
createsuperuser | 创建一个django后台的超级管理员 |
changepassword | 修改超级管理员的密码 |
django-admin —help
查看帮助
- 项目全局配置文件:
yang/settings.py
- 路由配置文件:
yang/urls.py
使用如下命令
python3 manage.py startapp cmdb # app名字
# 可以通过app将业务分开
➜ python3 manage.py startapp cmdb
➜ tree
.
├── cmdb # 刚创建的app
│ ├── __init__.py
│ ├── admin.py # Django提供的后台管理
│ ├── apps.py # 配置当前app
│ ├── migrations # 记录数据库表结构修改
│ │ └── __init__.py
│ ├── models.py # ORM,写指定的类,通过命令就可以创建数据库结构
│ ├── tests.py # 单元测试
│ └── views.py # 业务代码
├── manage.py
└── yang
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-35.pyc
│ └── settings.cpython-35.pyc
├── settings.py
├── urls.py
└── wsgi.py
4 directories, 14 files
django
内部是有一个內建的轻量的web开发服务器,在开发期间你完全可以使用內建的服务器,避免安装Nginx
或者Apache
等
➜ python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
March 14, 2017 - 07:37:08
Django version 1.10.6, using settings 'yang.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

python-django-01
默认监听本地 8000
端口, 修改监听端口:
python manage.py runserver 8080
指定 IP
地址,修改监听地址.
python manage.py runserver 0.0.0.0:8080
后台添加用户,前台展示用户
继续上面创建好的项目yang
之上创建一个app
:users
python3 manage.py startapp users
除此之外,需要把app
注册到我们的项目中,可以在yang/setting.py
中找到INSTALLED_APPS
字典,把刚创建的app名字添加进去
INSTALLED_APPS = [
......
'users',
]
我们需要用到html
,所以我们也需要配置模板路径文件,先创建一个存放模板文件的路径.
mkdir templates
继续编辑settings.py
,找到TEMPLATES
,把DIRS
修改如下
'DIRS': [os.path.join(BASE_DIR, 'templates')],
在yang/urls.py
中添加一条路由配置
from django.conf.urls import url
from django.contrib import admin
# 导入app下面的视图函数users
from users.views import users
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 指定路由对应的函数
url(r'^users/$',users),
]
users/views.py
视图函数内容如下:
➜ yang cat users/views.py
from django.shortcuts import render
# 导入模型中的UserInfo表
from .models import UserInfo
# Create your views here.
def users(request):
# 获取所有的用户
all_user = UserInfo.objects.all()
# 把用户信息和前端文件一起发送到浏览器
return render(request,'users.html',{'all_user': all_user})
➜ yang
templates/users.html
内容如下
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<!-- 循环传过来的所有用户,显示其用户名 -->
{% for user in all_user %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>
</body>
</html>
users/models.py
配置文件
from django.db import models
__all__ = [
'UserInfo'
]
# Create your models here.
class UserInfo(models.Model):
name = models.CharField(max_length=30,verbose_name='用户名')
email = models.EmailField(verbose_name='用户邮箱')
user/admin.py
配置文件
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(UserInfo)
最后生成数据库
➜ yang python3 manage.py makemigrations
Migrations for 'users':
users/migrations/0001_initial.py:
- Create model UserInfo
➜ yang python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, users
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
Applying users.0001_initial... OK
创建超级管理员用户
用户名 yang
密码 yang111111
➜ yang python3 manage.py createsuperuser
Username (leave blank to use 'yang'): yang
# 邮箱地址,可以为空
Email address:
# 密码
Password:
Password (again):
Superuser created successfully.
打开http://127.0.0.1:8000/admin/
登录后台,输入我们刚才创建好的用户和密码

python-django-02
找到刚刚添加的app
,添加一个或多个用户

python-django-03

python-django-04

python-django-05
继续打开http://127.0.0.1:8000/users/就能够看到刚才添加的用户了,你可以试着再添加一个用户然后刷新页面,看看是否会显示出来你刚刚新添加的用户。
新建项目,可直接选择Django,输入项目名称,模板文件夹名字,应用名字,创建即可

python-django-08
相关配置
可以在此处直接进行运行Django(免去使用命令行运行的麻烦),相关配置也可以在此处进入,如配置端口等

python-django-09

python-django-10
使用PyCharm直接创建mysite项目
mysite/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 配置静态目录
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cmdb',
]
mysite/urls.py
from django.conf.urls import url
from django.contrib import admin
from cmdb import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
url(r'^login/', views.login),
url(r'^home/', views.home),
]
templates/home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body {
margin:0 auto;
}
#i1 {
height: 48px;
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="i1"></div>
<div>
<form action="/home/" method="post">
<input type="text" name="username" placeholder="用户名">
<input type="text" name="email" placeholder="邮箱">
<input type="text" name="gender" placeholder="性别">
<input type="submit" value="添加">
</form>
</div>
<div>
<table>
{% for row in user_list %}
<tr>
<td>{{ row.username }}</td>
<td>{{ row.email }}</td>
<td>{{ row.gender }}</td>
</tr>
{% endfor %}
</table>
</div>
</body>
</html>
templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
label {
width: 80px;
text-align: right;
display: inline-block;
}
.sp1 {
color: red;
}
</style>
</head>
<body>
<form action="/login/" method="post">
<p>
<label for="username">用户名: </label>
<input id="username" name="user" type="text">
</p>
<p>
<label for="password">密 码: </label>
<input id="password" name="pwd" type="password">
<input type="submit" value="提交">
<!-- 使用下面这个标签巧妙的显示错误信息 -->
<span class="sp1">{{ error_msg }}</span>
</p>
</form>
</body>
</html>
cmdb/views.py
from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import redirect
# Create your views here.
def index(request):
return HttpResponse('<h1>Index cmdb</h1>')
def login(request):
# 包含用户提交的所有信息
print(request)
# <class 'django.core.handlers.wsgi.WSGIRequest'>
print(type(request))
# 获取用户提交方法
print(request.method)
error_msg = ""
if request.method == "POST":
# 获取用户通过POST请求提交过来的数据
# 如果不存在会返回一个None
user = request.POST.get("user",None)
pwd = request.POST.get("pwd",None)
print(user,pwd)
# 使用这种方法,如果key不存在,会报错,所以推荐使用get方法获取
# user = request.POST['user']
# pwd = request.POST['pwd']
if user == 'qwe' and pwd == '123':
# redirect 跳转
return redirect('https://www.baidu.com')
else:
error_msg = "用户名或密码错误"
return render(request, 'login.html', {'error_msg': error_msg})
USER_LIST = [
{'username': 'xxx', 'email': 'xxx@xx.com', 'gender': '男'}
]
for i in range(20):
temp = {'username': 'xxx' + str(i), 'email': 'xxx@xx.com', 'gender': '男'}
USER_LIST.append(temp)
def home(request):
if request.method == "POST":
# 接收用户提交的数据,添加到USER_LIST并显示到页面
u = request.POST.get('username')
e = request.POST.get('email')
g = request.POST.get('gender')
temp = {'username': u, 'email': e, 'gender': g}
USER_LIST.append(temp)
return render(request, 'home.html',{'user_list': USER_LIST})
# 如果模板在templates目录下的某个子目录下,则只需要加上路径就行了,例如 test下
# return render(request, 'test/home.html',{'user_list': USER_LIST})
创建Django工程
- django-admin startproject 工程名
创建app
- cd 工程名
- python manage.py startapp cmdb
静态文件
project.settings.py
STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), )
模板路径
'DIRS': [os.path.join(BASE_DIR, 'templates')]
settings
- 暂时注释CSRF
定义路由规则
- url.py “login” —> 函数名
定义视图函数
app下views.py
def func(request): # request.method # request.GET.get('',None) # return HttpResponse("字符串") # return render(request,"HTML模板路径") # return redirect('/只能填URL')
Django路由与视图¶
路由说白了就是视图(函数)的对应关系,一个路由对应一个视图,如上面文章所提到的,当打开/users/
路径的时候会让users
这个函数来进行逻辑处理,把处理的结果再返回到前端
路由的配置文件入口在 settings.py
文件中已经定义.
ROOT_URLCONF = 'yang.urls'
# 访问地址必须是http://127.0.0.1:8000/hello/
url(r'^hello/$', views.hello),
# http://127.0.0.1:8000/detail-2-2.html
# url(r'^detail-(\d+)-(\d+).html', views.detail),
# 括号内匹配到的内容会当做参数传给detail函数
def detail(request, nid, uid):
print(nid, uid)
return HttpResponse(nid)
# 分组匹配
# url(r'^detail-(?P<uid>\d+)-(?P<nid>\d+).html', views.detail),
# 使用 *args, **kwargs 接受所有参数
def detail(request, *args, **kwargs):
print(nid, uid)
return HttpResponse(nid)
在函数内需要接受year,month,day参数
url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.date),
# 使用分组匹配就不用考虑视图函数的参数位置了
date
视图必须接收以下参数
def date(request, year, month, day):
对URL路由关系进行命名,以后可以通过此名字获取URL(通过别名获取URL,就算URL调整也不会影响之前所有的别名)
可用于前端的from表单验证,如下实例,URLs地址的时候,因为from表单提交的地址使用了别名,所以会自动替换
# urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/$', views.index, name='bieming'),
]
# views.py
from django.shortcuts import render,HttpResponse
def index(request):
if request.method=='POST':
username=request.POST.get('username')
password=request.POST.get('password')
if username=='as' and password=='123':
return HttpResponse("登陆成功")
return render(request, 'index.html')
# index.html
<form action="{% url 'bieming' %}" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="submit">
</form>
url(r'^home/', views.Home.as_view()),
include分发,有利于解耦
# 当访问二级路由是cmdb的时候转发给app01.urls处理
url(r'^cmdb/$', include('app01.urls')),
可以使用incloud
把很多个路由进行拆封,然后把不同的业务放到不同的urls中,首先我们创建项目及应用
# 创建DjangoProjects项目
django-admin.py startproject DjangoProjects
cd DjangoProjects
# 在项目内创建app1和app2应用
python3 manage.py startapp app1
python3 manage.py startapp app2
项目的urls.py文件内容
# DjangoProjects/DjangoProjects/urls.py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
# 当路由匹配到一级路径为app1时,就把这个URL交给app1.urls再次进行匹配
url(r'^app1/', include('app1.urls')),
url(r'^app2/', include('app2.urls')),
]
应用urls.py和view.py文件内容
# DjangoProjects/app1/urls.py
from django.conf.urls import url,include
from django.contrib import admin
from app1 import views
urlpatterns = [
url(r'^hello/$', views.hello),
]
# DjangoProjects/app1/views.py
from django.shortcuts import render,HttpResponse
def hello(request):
return HttpResponse("Hello App1")
# DjangoProjects/app2/urls.py
from django.conf.urls import url
from django.contrib import admin
from app2 import views
urlpatterns = [
url(r'^hello/$', views.hello),
]
# DjangoProjects/app2/views.py
from django.shortcuts import render,HttpResponse
def hello(request):
return HttpResponse("Hello App2")
- 当访问 http://127.0.0.1:8000/app1/hello/ 时返回内容
Hello App1
- 当访问 http://127.0.0.1:8000/app2/hello/ 时返回内容
Hello App2
http请求: HttpRequest对象
http响应: HttpResponse对象
HttpRequest对象属性
属性 | 描述 |
---|---|
request.path | 请求页面的路径,不包括域名 |
request.path_info | 可用于跳转到当前页面 |
request.get_full_path() | 获取带参数的路径 |
request.method | 页面的请求方式 |
request.GET | GET请求方式的数据 |
request.POST | POST请求方式的数据 |
HttpResponse对象属性
属性 | 描述 |
---|---|
render(request, ‘index.html’) | 返回一个模板页面 |
render_to_response( ‘index.html’) | 返回一个模板页面 |
redirect(‘/login’) | 页面跳转 |
HttpResponseRedirect(‘/login’) | 页面跳转 |
HttpResponse(‘yang’) | 给页面返回一个字符串 |
➜ django_xxx git:(master) ✗ django-admin startproject mysite
➜ django_xxx git:(master) ✗ cd mysite
➜ mysite git:(master) ✗ tree
.
├── manage.py
└── mysite
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 5 files
新建app,名称为learn,会自动生成一个learn目录
➜ mysite git:(master) ✗ python3 manage.py startapp learn
➜ mysite git:(master) ✗ tree
.
├── learn
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py # Django 1.9.x 以上会在Django 1.8基础上多出此文件
│ ├── migrations # Django 1.8.x 以上会生成此目录
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── mysite
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-35.pyc
│ └── settings.cpython-35.pyc
├── settings.py
├── urls.py
└── wsgi.py
4 directories, 14 files
➜ mysite git:(master) ✗ ls
learn manage.py mysite
➜ mysite git:(master) ✗ vim mysite/settings.py
# 修改内容如下
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'learn',
]
➜ mysite git:(master) ✗ vim learn/views.py
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse
def index(request):
return HttpResponse('Hello World')
即规定 访问什么网址对应什么内容
Django 1.8.x以后,官方要求以如下方式导入,再使用
from learn.views import index # 导入
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', index), # 使用index
]
python manage.py runserver
打开 http://127.0.0.1:8000/
即可看到效果,页面会显示 Hello World
如需修改监听IP,及端口
python manage.py runserver 0.0.0.0:8000
通过url传值,做加减法
url 通过 /add?a=4&b=5
方式传值
➜ mysite git:(master) ✗ ls
db.sqlite3 learn manage.py mysite
➜ mysite git:(master) ✗ python3 manage.py startapp calc
➜ mysite git:(master) ✗ tree calc
calc
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
1 directory, 7 files
将 calc
这个app加入到 mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
...
'calc',
]
修改 calc/views.py
➜ mysite git:(master) ✗ vim calc/views.py
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse
def add(request):
a = request.GET['a']
b = request.GET['b']
c = int(a) + int(b)
return HttpResponse(str(c))
request.GET 类似于一个字典,更好的办法是用 request.GET.get(‘a’, 0) 当没有传递 a 的时候默认 a 为 0
修改 mysite/urls.py
➜ mysite git:(master) ✗ vim mysite/urls.py
from django.conf.urls import url
from django.contrib import admin
from learn.views import index
from calc.views import add
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', index),
url(r'^add/$',add,name='add'),
]
运行
➜ mysite git:(master) ✗ python3 manage.py runserver
访问
http://127.0.0.1:8000/add/?a=4&b=5
会显示 a + b
的结果
试试 http://127.0.0.1:8000/add/?a=157&b=5
url 通过 /add/3/4/
传值
修改 calc/views.py
vim calc/views.py
def add2(request,a,b):
c = int(a) + int(b)
return HttpResponse(str(c))
修改 mysite/urls.py
url(r'^add/(\d+)/(\d+)/$',add2,name='add2'),
访问 http://127.0.0.1:8000/add/100/200/
新地址自动跳转到旧地址的一种方式:
from django.http import HttpResponseRedirect
# from django.core.urlresolvers import reverse # django 1.4.x - django 1.10.x
from django.urls import reverse
def old_add2_redirect(request,a,b):
return HttpResponseRedirect(reverse('add2',args=(a,b)))
urls.py
url(r'^new_add/(\d+)/(\d+)/$',add2,name='add2'),
url(r'^add/(\d+)/(\d+)/$',old_add2_redirect),
Django模板¶
模板是一个文本,用于分离文档的表现形式和内容,模板定义了占位符以及各种用于规范文档该如何显示的各部分基本逻辑(模板标签).模板通常用于产生HTML,但是Django的模板也能产生任何给予文本格式的文档.
在Python代码中使用Django模板的最基本方式如下:
- 可以用原始的模板代码字符串创建一个
Template
对象,Django同样支持用指定模板文件路径的方式来创建Template
对象; - 调用模板对象的
render
方法,并且传入一套变量context
.它将返回一个基于模板的展现字符串,模板中的变量和标签会被context
值替代
一旦你创建一个Template
对象,你可以用context
来传递数据给它.一个context是一系列变量和它们值得集合.
context
在Django里表现为Context
类,在django.template
模块里,她的构造函数带有一个可选的参数:
一个字典映射变量和它们的值.调用Template
对象的render()
方法并传递context
来填充模板
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'xxx'})
>>> t.render(c)
'My name is xxx.'
这就是Django模板系统的基本规则: 写模板,创建 Template
对象,创建
Context
,调用 render()
方法.
>>> from django.template import Template,Context
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
'xxx is 43 years old.'
句点可以用来引用对象的 方法
. 例如,每个Python字符串都有 upper()
和 isdigit()
方法,你在模板中可以使用同样的句点语法来调用它们
>>> from django.template import Template,Context
>>> t = Template('{{ var }} upper: {{ var.upper }} isdigit: {{ var.isdigit }}')
>>> t.render(Context({'var': '123'}))
'123 upper: 123 isdigit: True'
>>> t.render(Context({'var': 'hello'}))
'hello upper: HELLO isdigit: False'
句点也可以用与访问列表索引,例如:
>>> from django.template import Template,Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apple','bananas','carrote']})
>>> t.render(c)
'Item 2 is carrote.'
>>>
当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:
- 字典类型查找(比如
foo["bar"]
) - 属性查找(比如
foo.bar
) - 方法调用(比如
foo.bar()
) - 列表类型索引查找(比如
foo[bar]
)
…
# 首先你要导入os模块
'DIRS': [os.path.join(BASE_DIR, 'templates')],
from django.shortcuts import render_to_response
# def current_datetime(request):
# now = datetime.datetime.now()
# return render_to_response('current_datetime.html',{'current_datetime': now})
很多时候,就像在这个范例中那样,你发现自己一直在计算某个变量,保存结果到变量中(比如前面代码中的now),然后将这些变量发送给模板。尤其喜欢偷懒的程序员应该注意到了,不断地为临时变量和临时模板命名有那么一点点多余。不仅多余,而且需要额外的输入。
如果你是个喜欢偷懒的程序员并想让代码看起来更加简明,可以利用 Python
的内建函数locals()
。它返回的字典对所有局部变量的名称与值进行映射。因此,前面的视图可以重写成下面这个样子
def current_datetime(request):
current_datetime = datetime.datetime.now()
return render_to_response('current_datetime.html',locals())
在此,我们没有像之前那样手工指定context
字典,而是传入了locals()
的值,它囊括了函数执行到该时间点时所定义的一切变量。因此,我们将now
变量重命名为current_date
,因为那才是模板所预期的变量名称。在本例中,locals()
并没有带来多大的改进,但是如果有多个模板变量要界定而你又想偷懒,这种技术可以减少一些键盘输入。
使用locals()
时要注意是它将包括所有的局部变量,它们可能比你想让模板访问的要多。在前例中,locals()
还包含了request
。对此如何取舍取决你的应用程序。
{% raw %}
{{ k1|lower }} # 将所有字母都变为小写
{{ k1|first|upper }} # 将首字母变为大写
{{ k1|truncatewords:"30" }} # 取变量k1的前30个字符
{{ item.createTime|date:"Y-m-d H:i:s" }} # 将时间转为对应格式显示
{% endraw %}
在内置的方法满足不了我们的需求的时候,就需要自己定义属于自己的方法了,自定义方法分别分为
filter
和 simple_tag
① 传参:
filter默认最多只支持2个参数:可以用{{ k1|f1:"s1, s2, s3" }}这种形式将参数传递个函数,由函数去split拆分
simple_tag支持多个参数:{% f1 s1 s2 s3 s4 %} 有多少就写多少
② 模板语言if条件:
filter:
{% if k1|f1 %} # 函数的结果作为if语句的条件
<h1>True</h1>
{% else %}
<h1>False</h1>
simple_tag: 不支持模板语言if条件
a、在app中创建templatetags目录,目录名必须为templatetags b、在目录templatetags中创建一个.py文件,例如 s1.py c、html模板顶部通过{% load s1 %}导入py文件 d、settings.py中注册app
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django import template
register = template.Library() # 这一句必须这样写
@register.filter
def f1(value):
return value + "666"
@register.filter
def f2(value, arg):
return value + "666" + arg
@register.simple_tag
def f3(value, s1, s2, s3, s4):
return value + "666" + s1 + s2 + s3 + s4
{% raw %}
{% load s1 %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>{{ title }}</h1>
{# 使用filter方式调用自定义方法 #}
<!-- 将k1当做参数传递给f1函数进行处理 处理方式 f1(k1) -->
<p>{{ k1|f1 }}</p>
<!-- 将k1当做参数传递给f2函数进行处理,接受2个参数 处理方式 f2(k1, "xxx") -->
<p>{{ k1|f2:"xxx" }}</p>
{# 使用simple_tag方式调用自定义方法 #}
<!-- 将k1当做参数传递给f3函数进行处理,接收多个参数 处理方式 f3(k1, "s1", "s2", "s3", "s4") -->
<p>{% f3 k1 "s1" "s2" "s3" "s4" %}</p>
</body>
</html>
{% endraw %}
Django模型¶
把数据存取逻辑、业务逻辑和表现逻辑组合在一起的概念有时被称为软件架构的Model-View-Controller(MVC)
模式。在这个模式中,Model代表数据存取层,View代表的是系统中选择显示什么和怎么显示的部分,Controller指的是系统中根据用户输入并视需要访问模型,以决定使用哪个视图的那部分。
Django紧紧地遵循这种MVC
模式,可以称得上是一种MVC
框架。
以下是Django中M、V
和C
各自的含义:
M
: 数据存取部分,由django数据库层处理;V
: 选择显示哪些数据要显示以及怎样显示的部分,由视图和模板处理;C
: 根据用户输入委派视图的部分,由Django框架根据URLconf设置,对给定URL调用适当的Python函数;
C
由框架自行处理,而Django里更关注的是模型(Model)
、模板(Template)
和视图(Views)
,Django也被称为MTV
框架,在MTV开发模式中:
M
代表模型(Model),即数据存取层,该层处理与数据相关的所有事务:如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等;T
代表模板(Template),即表现层,该层处理与表现相关的决定:如何在页面或其他类型文档中进行显示;V
代表视图(View),即业务逻辑层,该层包含存取模型及调取恰当模板的相关逻辑,你可以把它看作模型与模板之间的桥梁;
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'yang',
'USER': 'root',
'PASSWORD': '111111',
'HOST': '127.0.0.1',
'PORT': '3306'
}
}
以下是一个MySQL数据库的配置属性:
字段 | 描述 |
---|---|
ENGINE | 使用的数据库引擎 |
NAME | 数据库名称 |
USER | 那个用户连接数据库 |
PASSWORD | 用户的密码 |
HOST | 数据库服务器 |
PORT | 端口 |
ENGINE字段所支持的内置数据库
- django.db.backends.postgresql
- django.db.backends.mysql
- django.db.backends.sqlite3
- django.db.backends.oracle
无论你选择使用那个数据库都必须安装此数据库的驱动,即python操作数据库的介质,在这里你需要注意的是python3.x并不支持使用MySQLdb模块,但是你可以通过pymysql来替代mysqldb,首先你需要安装pymysql模块:
pip3 install pymysql
然后在项目的__init__.py
文件加入以下两行配置:
import pymysql
pymysql.install_as_MySQLdb()
当数据库配置完成之后,我们可以使用python manage.py shell进入项目测试,输入下面这些指令来测试你的数据库配置:
➜ yangxxx git:(master) ✗ python3 manage.py shell
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.db import connection
>>> cursor = connection.cursor()
没有报错,则说明数据库配置正确.
让我们来创建一个Django app,一个包含模型,视图和Django代码,并且形式为独立Python包的完整Django应用。
Project和app之间的不同就是一个是配置另一个是代码:
- 一个Project包含很多个Django app以及对它们的配置;
- 技术上,Project的作用是提供配置文件,比方说哪里定义数据库连接信息, 安装的app列表,TEMPLATE等等;
- 一个app是一套Django功能的集合,通常包括模型和视图,按Python的包结构的方式存在;
- 例如,Django本身内建有一些app,例如注释系统和自动管理界面,app的一个关键点是它们是很容易移植到其他Project和被多个Project复用。
如果你使用了Django的数据库层(模型),你必须创建一个Django app,模型必须存放在apps中,因此,为了开始建造我们的模型,我们必须创建一个新的app:
➜ yangxxx git:(master) ✗ python3 manage.py startapp darker
➜ yangxxx git:(master) ✗ tree darker
darker
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
1 directory, 7 files
首先创建三张表
- 学生表(student) , 拥有字段: id/sname/gender
- 课程表(course) , 拥有字段: id/cname
- 成绩表(score) , 拥有字段: id/student_id/course_id
打开app的models.py
目录输入以下代码:
from django.db import models
# Create your models here.
class student(models.Model):
# 自增主键,如果没有定义,Django会自动帮我们创建一个
id = models.AutoField
sname = models.CharField(max_length=12)
gender = models.CharField(max_length=2)
class course(models.Model):
# 自增主键,如果没有定义,Django会自动帮我们创建一个
id = models.AutoField
cname = models.CharField(max_length=12)
class score(models.Model):
# 自增主键,如果没有定义,Django会自动帮我们创建一个
id = models.AutoField
# 设置外键关联
student_id = models.ForeignKey(student)
course_id = models.ForeignKey(course)
每个数据模型都是django.db.models.Model
的子类,它的父类Model
包含了所有必要的和数据库交互的方法,并提供了一个简洁漂亮的定义数据库字段的语法,每个模型相当于单个数据库表,每个属性也是这个表中的一个字段,属性名就是字段名.
要通过django在数据库中创建这些表,首先我们需要在项目中激活这些模型,将darker app添加到配置文件的已安装应用列表中即可完成此步骤;
编辑settings.py
文件,找到INSTALLED_APPS
设置,INSTALLED_APPS
告诉Django项目哪些app
处于激活状态:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'darker',
]
通过下面的指令来创建数据表
# 检查是否正确
➜ yangxxx git:(master) ✗ python3 manage.py check
System check identified no issues (0 silenced).
# 在数据库中生成表
➜ yangxxx git:(master) ✗ python3 manage.py makemigrations
Migrations for 'darker':
darker/migrations/0001_initial.py
- Create model course
- Create model score
- Create model student
- Add field student_id to score
# 生成数据
➜ yangxxx git:(master) ✗ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, darker, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying darker.0001_initial... OK
Applying sessions.0001_initial... OK
django1.7
之前的版本都是:
python manage.py syncdb
django1.7
及之后的版本做了修改,把1步拆成了2步,变成
python manage.py makemigrations
python manage.py migrate
查看创建的数据表
mysql> use yang;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------------------+
| Tables_in_yang |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| darker_course |
| darker_score |
| darker_student |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
13 rows in set (0.01 sec)
运行 python3 manager.py shell
并使用Django提供的高级Python API
➜ yangxxx git:(master) ✗ python3 manage.py shell
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
# 导入student模型类,通过这个类我们可以与student数据表进行交互
>>> from darker.models import student
# 创建一个student类的实例并设置字段sname,gender
>>> s1 = student(sname='yang',gender='m')
# 调用该对象的save()方法,将对象保存到数据库中,Django会在后台执行一条INSERT语句
>>> s1.save()
>>> s2 = student(sname='s2',gender='w')
>>> s2.save()
# 使用student.objects属性从数据库取出student表的记录集,这个属性又许多方法,student.objects.all()方法获取数据库中student类的所有对象,实际上Django执行了一条SQL SELECT语句
>>> student_list = student.objects.all()
>>> student_list
<QuerySet [<student: student object>, <student: student object>, <student: student object>]>
只需要在上面三个表类中添加一个方法 __str__
,如下:
from django.db import models
# Create your models here.
class student(models.Model):
id = models.AutoField
sname = models.CharField(max_length=12)
gender = models.CharField(max_length=2)
def __str__(self):
return self.sname
class course(models.Model):
id = models.AutoField
cname = models.CharField(max_length=12)
def __str__(self):
return self.cname
class score(models.Model):
id = models.AutoField
student_id = models.ForeignKey(student)
course_id = models.ForeignKey(course)
重新进入shell
➜ yangxxx git:(master) ✗ python3 manage.py shell
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from darker.models import student
>>> student_list = student.objects.all()
# 此次获取的是字符串而不是对象了
>>> student_list
<QuerySet [<student: yang>, <student: yang>, <student: s2>]>
# 插入数据
>>> s1 = student(sname='qaz',gender='m')
# 将数据保存到数据库
>>> s1.save()
# 获取刚插入的数据的ID
>>> s1.id
4
# 修改数据内容
>>> s1.gender='w'
>>> s1.save()
# 相当于执行了命令:
# UPDATE darker_student SET gender = 'w' WHERE id=4;
下面的指令是从数据库中获取所有的数据
>>> student.objects.all()
<QuerySet [<student: yang>, <student: yang>, <student: s2>, <student: qaz>]>
Django在选择所有数据时并没有使用 SELECT *
,而是显式列出了所有字段,
SELECT *
会更慢,而且最重要的是列出所有字段遵循了Python的一个信条:
明言胜于暗示
数据过滤
使用filter()
方法对数据过滤
>>> student.objects.filter(sname='qaz',gender='w')
<QuerySet [<student: qaz>]>
在sname
和contains
之间有双下划线,gender部分会被Django翻译成LIKE语句
>>> student.objects.filter(sname__contains='y')
<QuerySet [<student: yang>, <student: yang>]>
翻译成下面的SQL语句:
SELECT id,sname,gender FROM darker_student WHERE name LIKE '%y%'
获取单个对象
>>> student.objects.get(sname='qaz')
<student: qaz>
数据排序
使用order_by()
这个方法进行排序
>>> student.objects.order_by("sname")
<QuerySet [<student: qaz>, <student: s2>, <student: yang>, <student: yang>]>
>>>
如果需要以多个字段为标准进行排序(第二个字段会在第一个字段的值相同的情况下被使用到),使用多个参数就可以了,如下:
>>> student.objects.order_by("sname","gender")
<QuerySet [<student: qaz>, <student: s2>, <student: yang>, <student: yang>]>
指定逆序排序
>>> student.objects.order_by('-sname')
<QuerySet [<student: yang>, <student: yang>, <student: s2>, <student: qaz>]>
设置数据的默认排序
如果你设置了这个选项,那么除非你检索时特意额外地使用了order_by()
,否则,当你使用Django的数据库API去检索时,student对象的相关返回值默认地都会按sname
字段排序.
class student(models.Model):
id = models.AutoField
sname = models.CharField(max_length=12)
gender = models.CharField(max_length=2)
def __str__(self):
return self.sname
class Meta:
ordering = ['sname']
连锁查询
>>> from darker.models import student
>>> student.objects.filter(id='2').order_by('-sname')
<QuerySet [<student: yang>]>
限制返回的数据
>>> student.objects.order_by('sname')[0]
<student: qaz>
>>> student.objects.order_by('sname')[1]
<student: s2>
类似的,你可以用python的列表切片来获取数据
>>> student.objects.order_by('sname')[0:2]
<QuerySet [<student: qaz>, <student: s2>]>
虽然不支持负索引,但是我们可以使用其他的方法,比如稍微修改order_by()
语句来实现
>>> student.objects.order_by('-sname')[0]
<student: yang>
更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()
方法
>>> student.objects.filter(id=2).update(sname='Hello')
1
# update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录,比如将所有实例的性别都改为女
>>> student.objects.all().update(gender='w')
4
update()
方法会返回一个整型数值,表示受影响的记录条数
删除数据库中的对象只需要调用该对象的delete()
方法
删除指定数据
>>> student.objects.all().filter(sname='yang').delete()
(1, {'darker.score': 0, 'darker.student': 1})
>>> student.objects.all()
<QuerySet [<student: Hello>, <student: qaz>, <student: s2>]>
删除所有数据
>>> student.objects.all().delete()
(3, {'darker.score': 0, 'darker.student': 3})
>>> student.objects.all()
<QuerySet []>
方法 | 描述 |
---|---|
models.ForeignKey(其他表) |
一对多 |
models.ManyToManyField(其他表) |
多对多 |
models.OneToOneField(其他表) |
一对一 |
django.db.utils.InternalError: (1366, "Incorrect string value: '\\xE7\\x94\\xB7' for column 'gender' at row 1")
原因,| yang | CREATE DATABASE `yang` /*!40100 DEFAULT CHARACTER SET latin1 */ |
在 app
的models.py文件内添加一下内容用户创建一对多关系表
class UserType(models.Model):
nid = models.AutoField(primary_key=True)
caption = models.CharField(max_length=32)
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=16)
user_type = models.ForeignKey('UserType')
把app的名字添加到项目的settings.py
配置文件的INSTALLED_APPS中,然后在数据库中生成表
➜ yangxxx git:(master) ✗ python3 manage.py makemigrations
Migrations for 'darker':
darker/migrations/0002_auto_20170617_0902.py
- Create model UserInfo
- Create model UserType
- Change Meta options on student
- Add field user_type to userinfo
➜ yangxxx git:(master) ✗ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, darker, sessions
Running migrations:
Applying darker.0002_auto_20170617_0902... OK
➜ yangxxx git:(master) ✗ python3 manage.py shell
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 08:49:46)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from darker import models
# 通过create方式进行数据的添加
>>> models.UserType.objects.create(caption='superadmin')
<UserType: UserType object>
# 通过save保存的方式添加数据
>>> obj = models.UserType(caption='admin')
>>> obj.save()
# 通过字典的方式进行数据添加
>>> UserInfoDict = {'username': 'yang','password': 'helloword','user_type': models.UserType.objects.get(nid=1)}
# 通过**UserInfoDict把数据以字典的方式传给create
>>> models.UserInfo.objects.create(**UserInfoDict)
<UserInfo: UserInfo object>
>>> UserInfoDict = {'username': 'qwe','password': 'xxxxxxxx','user_type': models.UserType.objects.get(nid=2)}
>>> models.UserInfo.objects.create(**UserInfoDict)
<UserInfo: UserInfo object>
# 如果知道user_type_id代表多少,可以直接写数字
>>> UserInfoDict = {'username': 'yang','password': 'helloworld','user_type_id': 2}
>>> models.UserInfo.objects.create(**UserInfoDict)
<UserInfo: UserInfo object>
修改数据
# 指定条件更新
>>> models.UserInfo.objects.filter(password='helloword').update(password='hw')
1
# 获取id=1的这条数据对象
>>> obj = models.UserInfo.objects.get(id=1)
# 修改username字段
>>> obj.username = 'as'
# 保存操作
>>> obj.save()
删除数据
>>> models.UserInfo.objects.filter(username='as').delete()
(1, {'darker.UserInfo': 1})
查询数据
# 查询单条数据,不存在则报错
>>> models.UserInfo.objects.get(id=2)
<UserInfo: UserInfo object>
>>> models.UserInfo.objects.get(id=5)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/django/db/models/query.py", line 380, in get
self.model._meta.object_name
darker.models.DoesNotExist: UserInfo matching query does not exist.
# 获取全部数据
>>> models.UserInfo.objects.all()
<QuerySet [<UserInfo: UserInfo object>, <UserInfo: UserInfo object>]>
# 获取指定条件的数据
>>> models.UserInfo.objects.filter(username='qwe')
<QuerySet [<UserInfo: UserInfo object>]>
查询出来的结果都是QuerySet对象
query方法是用来查看查询语句的,及Django生成的SQL
>>> ret = models.UserType.objects.all()
>>> print(ret.query)
SELECT `darker_usertype`.`nid`, `darker_usertype`.`caption` FROM `darker_usertype`
values与values_list
>>> ret = models.UserType.objects.all().values('nid')
# 返回的列表,列表里面套字典
>>> print(type(ret),ret)
<class 'django.db.models.query.QuerySet'> <QuerySet [{'nid': 1}, {'nid': 2}]>
>>> ret = models.UserType.objects.all().values_list('nid')
# 返回一个列表,列表里面套集合
>>> print(type(ret),ret)
<class 'django.db.models.query.QuerySet'> <QuerySet [(1,), (2,)]>
双下划线连表操作
>>> ret = models.UserInfo.objects.all().values('username','user_type__caption')
>>> print(ret.query)
SELECT `darker_userinfo`.`username`, `darker_usertype`.`caption` FROM `darker_userinfo` INNER JOIN `darker_usertype` ON (`darker_userinfo`.`user_type_id` = `darker_usertype`.`nid`)
>>> ret = models.UserInfo.objects.all()
>>> for item in ret:
... print(item,item.id,item.user_type.nid,item.user_type.caption,item.user_type_id)
...
UserInfo object 2 2 admin 2
UserInfo object 3 2 admin 2
获取用户类型是超级管理员的所有用户
正向查找
通过双下划线连表查询
>>> ret = models.UserInfo.objects.filter(user_type__caption = 'admin').values('username','user_type__caption')
>>> print(ret,type(ret),ret.query)
<QuerySet [{'username': 'qwe', 'user_type__caption': 'admin'}, {'username': 'yang', 'user_type__caption': 'admin'}]> <class 'django.db.models.query.QuerySet'> SELECT `darker_userinfo`.`username`, `darker_usertype`.`caption` FROM `darker_userinfo` INNER JOIN `darker_usertype` ON (`darker_userinfo`.`user_type_id` = `darker_usertype`.`nid`) WHERE `darker_usertype`.`caption` = admin
反向查找
先查找UserType表中数据,再把这个数据和UserInfo表中进行过滤
>>> obj = models.UserType.objects.filter(caption= 'admin').first()
>>> print(obj.nid,obj.caption)
2 admin
>>> print(obj.userinfo_set.all())
<QuerySet [<UserInfo: UserInfo object>, <UserInfo: UserInfo object>]>
把UserType表里的所有字段和userinfo表进行一个匹配,如果有匹配到就显示出来
>>> ret = models.UserType.objects.all().values('nid','caption','userinfo__username')
>>> print(ret)
<QuerySet [{'userinfo__username': 'qwe', 'nid': 2, 'caption': 'admin'}, {'userinfo__username': 'yang', 'nid': 2, 'caption': 'admin'}, {'userinfo__username': None, 'nid': 1, 'caption': 'superadmin'}]>
两种创建多对多表的方式
手动指定第三张表进行创建
class HostGroup(models.Model):
hgid = models.AutoField(primary_key=True)
host_id = models.ForeignKey('Host')
group_id = models.ForeignKey('Group')
class Host(models.Model):
hid = models.AutoField(primary_key=True)
hostname = models.CharField(max_length=32)
ip = models.CharField(max_length=32)
class Group(models.Model):
gid = models.AutoField(primary_key=True)
name = models.CharField(max_length=16)
h2g = models.ManyToManyField('Host',through='HostGroup')
➜ yangxxx git:(master) ✗ python3 manage.py makemigrations
Migrations for 'darker':
darker/migrations/0003_auto_20170619_0137.py
- Create model Group
- Create model Host
- Create model HostGroup
- Add field h2g to group
➜ yangxxx git:(master) ✗ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, darker, sessions
Running migrations:
Applying darker.0003_auto_20170619_0137... OK
django帮我们创建第三张表
创建一下表关系用于测试多对多
class Host(models.Model):
hid = models.AutoField(primary_key=True)
hostname = models.CharField(max_length=32)
ip = models.CharField(max_length=32)
class Group(models.Model):
gid = models.AutoField(primary_key=True)
name = models.CharField(max_length=16)
# 任意一个字段,会自动生成第三张表,且第三张表会自动的添加联合唯一索引,Unique
h2g = models.ManyToManyField('Host')
插入数据测试
# Host插入数据
>>> from darker import models
>>> models.Host.objects.create(hostname='localhost',ip='192.168.1.1')
<Host: Host object>
>>> models.Host.objects.create(hostname='linux-node1',ip='192.168.1.2')
<Host: Host object>
>>> models.Host.objects.create(hostname='linux-node2',ip='192.168.1.3')
<Host: Host object>
>>> models.Host.objects.create(hostname='web-node1',ip='192.168.1.4')
<Host: Host object>
# Group插入数据
>>> models.Group.objects.create(name='markte dep')
<Group: Group object>
>>> models.Group.objects.create(name='sales dep')
<Group: Group object>
>>> models.Group.objects.create(name='tec dep')
<Group: Group object>
单个添加数据
>>> obj = models.Group.objects.get(name='tec dep')
>>> obj.gid, obj.name
(4, 'tec dep')
# 获取第三张表的内容
>>> obj.h2g.all()
<QuerySet []>
>>> h1 = models.Host.objects.get(hid=2)
>>> h1.ip
'192.168.1.2'
>>> obj.h2g.add(h1)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 929, in add
(opts.app_label, opts.object_name)
AttributeError: Cannot use add() on a ManyToManyField which specifies an intermediary model. Use darker.HostGroup's Manager instead.
User.objects.create()
每保存一条就执行一次SQL
bulk_create()
是执行一条SQL存入多条数据,做会快很多!能用列表解析代替
for 循环就使用列表解析
如果导入数据重复时,只需要使用
User.objects.get_or_create(title=title,content=content)
python_code/xxx/django_xxx/menjin/create_records.py
...
user_card_dic = {}
for tup in rows:
if tup[2] in user_card_dic.keys():
user_card_dic[tup[2]]["DoorName"] += "," + tup[0]
else:
tmp_dict = {}
tmp_dict["DoorName"] = tup[0]
tmp_dict["ConsumerName"] = tup[1]
tmp_dict["CardNO"] = tup[2]
tmp_dict["ConsumerID"] = tup[3]
tmp_dict["GroopName"] = tup[4]
user_card_dic[tup[2]] = tmp_dict
#user_list.append(tmp_dict)
#print(user_card_dic)
user_object_list = []
for user in user_card_dic.values():
# user # {'ConsumerID': 700, 'ConsumerName': '张三', 'GroopName': '内部运营\\行政', 'DoorName': 'xxF-办公区', 'CardNO': 102999}
print(user)
user_object_list.append(User(**user))
print(user_object_list)
print(len(user_object_list))
#print(user_list)
# User.objects.bulk_create(user_object_list)
Django技巧¶
if request.user.is_authenticated():
# Do something for authenticated users.
else:
# Do something for anonymous users.
Django admin 项目 https://djangopackages.org/grids/g/admin-interface/
参考 http://blog.csdn.net/hshl1214/article/details/45676409
http://blog.csdn.net/clh604/article/details/9365961
权限控制 http://blog.csdn.net/xtmyd/article/details/53813091
- 创建项目下
templates/admin
目录, 并设置 - 拷贝
site-packages/django/contrib/admin/templates/admin
目录下对应模板文件到如上目录 - 修改
templates/admin
下对应模板文件, 重新加载即可看到效果
如果需要添加admin
为前缀的url,
只需要配置urls.py
即可(放在系统匹配url规则之前).
urlpatterns = [
url(r'^admin/export_to_xlsx/$', userinfo_views.export_to_xlsx),
url(r'^admin/update_records/$', userinfo_views.update_records),
url(r'^admin/import_from_xlsx/$', userinfo_views.import_from_xlsx),
url(r'^admin/', admin.site.urls),
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
使用 request.FILES.get('xlsx')
获取文件对象
‘xlsx’,为html
中name
属性
直接使用obj.name
获取的名字, 可能不安全,
比如存在../../../
这样的路径, 所以需要处理
def import_from_xlsx(request):
if request.method == "POST":
path = "uploads"
if not os.path.exists(path):
os.makedirs(path)
try:
obj = request.FILES.get('xlsx')
name = os.path.splitext(obj.name)[0]
postfix = os.path.splitext(obj.name)[1]
file_name = os.path.join(path, "{}-{}{}".format(name, time.strftime("%Y%m%d%H%M%S"), postfix))
print(file_name)
# 写文件, 其他地方自行修改
f = open(file_name, 'wb')
for chunk in obj.chunks():
f.write(chunk)
f.close()
manager = Manager()
user_list, l = manager.check_from_xlsx(file_name)
return render(request, 'admin/check_user.html', {'user_list': user_list,'length':l, 'file_name':file_name })
except Exception as e:
raise e
elif request.method == "GET":
file_name = request.GET.get('file_name')
if file_name is not None:
print("get")
manager = Manager()
manager.auth_from_xlsx(file_name)
return redirect('/admin/update_records')
else:
raise ValueError('文件错误')
<div class="row">
<div class="col-md-8 col-md-offset-2">
<br>
<P>第一种方法,直接把链接地址指向要下载的静态文件,在页面中点击该链接,可以直接打开该文件,在链接上点击右键,选择“另存为”可以保存该文件到本地硬盘。
此方法只能实现静态文件的下载,不能实现动态文件的下载。</P>
<a href="{% url 'media' 'uploads/11.png' %}">11.png</a>
<br>
<br>
<p>第二种方法,将链接指向相应的view函数,在view函数中实现下载功能,可以实现静态和动态文件的下载。</p>
<a href="{% url 'course:download_file' %}">11.png</a>
<br>
<br>
<br>
<p>第三种方法,与第二种方法类似,利用按钮的响应函数实现文件下载功能。</p>
<label> 11.png</label><button onclick="window.location.href='{% url 'course:download_file' %}'">Download</button>
</div>
</div>
from django.http import StreamingHttpResponse
from django.utils.http import urlquote
def export_to_xlsx(request):
def file_iterator(file_name, chunk_size=512):
# 读取 excel 需要使用 rb
with open(file_name, 'rb') as f:
while True:
c = f.read(chunk_size)
if c:
yield c
else:
break
dirpath = '/Users/yjj'
xlsx_filename = 'xxx-' + time.strftime('%Y-%m-%d-%H%M%S') + '.xlsx'
xlsx_path = os.path.join(dirpath, xlsx_filename)
response = StreamingHttpResponse(file_iterator(xlsx_path))
response['Content-Type'] = 'application/octet-stream'
# 中文文件名, 使用 urlquote
response['Content-Disposition'] = "attachment; filename={0}".format(urlquote(xlsx_filename))
return response
如果是直接使用Django的runserver --insecure
python manage.py runserver 0.0.0.0:8000 --insecure
使用Nginx, Apache等提供静态文件
Django实例2¶
➜ PycharmProjects django-admin startproject django_project2
➜ PycharmProjects cd django_project2
➜ django_project2 python3 manage.py startapp app01
django_project2/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 注册app01
'app01',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'static'),
)
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login),
url(r'^home/', views.Home.as_view()),
]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 上传文件需要增加enctype="multipart/form-data" 属性 -->
<form action="/login/" method="post" enctype="multipart/form-data">
<p>
篮球: <input type="checkbox" name="favor" value="1">
足球: <input type="checkbox" name="favor" value="2">
台球: <input type="checkbox" name="favor" value="3">
乒乓球: <input type="checkbox" name="favor" value="4">
</p>
<p>
<select name="city" multiple>
<option value="sh">上海</option>
<option value="bj">北京</option>
<option value="tj">天津</option>
</select>
</p>
<input type="file" name="xxx">
<input type="submit" value="提交">
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/home/" method="post">
<input type="text" name="zxc">
<input type="submit">
</form>
<span>method: {{ request.method }}</span>
</body>
</html>
from django.shortcuts import render
from django.shortcuts import redirect
from django.shortcuts import HttpResponse
import os
# Create your views here.
from django.views import View
from django.core.files.uploadedfile import InMemoryUploadedFile
def index(request):
return HttpResponse('<h1>Index</h1>')
def login(request):
if request.method == "GET":
return render(request, 'login.html')
elif request.method == "POST":
# 获取的是一个列表,适用于复选框,select
v = request.POST.getlist("favor")
ct = request.POST.getlist("city")
print(v)
print(ct)
obj = request.FILES.get("xxx")
if obj:
# <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
# from django.core.files.uploadedfile import InMemoryUploadedFile
# 可以通过上述对象,查看obj详细信息
# obj.name 为文件名
print(obj, type(obj), obj.name)
# upload/文件名
file_path = os.path.join('upload', obj.name)
# 如果目录不存在,则创建
if not os.path.exists(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
# 打开一个文件,将用户上传的内容写入文件,并关闭文件
f = open(file_path, mode='wb')
for i in obj.chunks():
f.write(i)
f.close()
return redirect('/login/')
else:
return redirect('index')
# views里面也可以使用类
class Home(View):
# 需要from django.views import View
def dispatch(self, request, *args, **kwargs):
# 每一次执行对应方法的时候,会在执行之前打印"before",执行之后打印"after"
print('before')
# 调用父类中的dispatch
result = super(Home,self).dispatch(request, *args, **kwargs)
print('after')
return result
def get(self, request):
# return HttpResponse('<h1> class Home get</h1>')
print(request.method)
return render(request, 'home.html')
def post(self, request):
print(request.method)
return render(request, 'home.html')
访问index,显示用户,点击用户名,显示详细用户信息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ user_dict.k1 }}
<ul>
{% for k,row in user_dict.items %}
<li><a href="/detail/?nid={{ k }}">{{ row.name }}</a></li>
<!--
<li><a href="/detail/?nid={{ k }}">{{ row.name }}</a></li>
-->
{% endfor %}
</ul>
<ul>
{% for k in user_dict.keys %}
<li>{{ k }}</li>
{% endfor %}
</ul>
<ul>
{% for val in user_dict.values %}
<li>{{ val }}</li>
{% endfor %}
</ul>
<ul>
{% for k,row in user_dict.items %}
<li>{{ k }}-{{ row }}</li>
{% endfor %}
</ul>
</body>
</html>
添加如下内容
USER_DICT = {
'1': {'name': 'root1', 'email': 'root@163.com'},
'2': {'name': 'root2', 'email': 'root@163.com'},
'3': {'name': 'root3', 'email': 'root@163.com'},
'4': {'name': 'root4', 'email': 'root@163.com'},
'5': {'name': 'root5', 'email': 'root@163.com'},
}
def index(request):
return render(request, 'index.html', {'user_dict': USER_DICT})
# 第一版
def detail(request):
nid = request.GET.get('nid')
return HttpResponse(USER_DICT[nid].items())
url(r'^index/', views.index),
url(r'^detail/', views.detail),
# 对应 urls
# url(r'^detail-(\d+).html', views.detail),
# 第二版,需要传一个参数,url匹配的时候(括号里面的内容会当做参数餐给对应的试图函数)
def detail(request, nid):
nid = nid
print(nid)
return HttpResponse(USER_DICT[nid].items())
# url(r'^detail-(\d+)-(\d+).html', views.detail),
如果用这种方式传参,试图函数接收参数时,顺序不能错,否则就会有问题
# 分组匹配,对应参数传给对应名字的形参
url(r'^detail-(?P<uid>\d+)-(?P<nid>\d+).html', views.detail),
# 视图函数使用 *args, **kwargs 接收所有参数
def detail(request, *args, **kwargs):
# 访问 http://127.0.0.1:8112/detail-3-2.html
# 使用对应url出现的结果
# url(r'^detail-(\d+)-(\d+).html', views.detail),
# ('3', '2')
print(args)
# url(r'^detail-(?P<uid>\d+)-(?P<nid>\d+).html', views.detail),
# {'uid': '3', 'nid': '2'}
print(kwargs)
return HttpResponse(" ")
在第一部分已经在settings.py
中注册过app01
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Django默认使用MySQLdb模块链接Mysql
主动修改为pymysql
操作数据库
# 安装
pip3 install pymysql
# 然后在项目的`__init__.py`文件加入以下两行配置:
```shell
import pymysql
pymysql.install_as_MySQLdb()
```
settings.py
配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'yang',
'USER': 'root',
'PASSWORD': '111xxx',
'HOST': '127.0.0.1',
'PORT': '3306'
}
}
创建类
from django.db import models
# Create your models here.
class UserInfo(models.Model):
# Django会自动创建一个id列,自增,主键
# 用户名,及密码,字符串类型,指定最大长度
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
执行命令,会根据app
下的models.py
自动创建数据库表
python3 manage.py makemigrations
python3 manage.py migrate
urls
url(r'^orm/', views.orm),
views
from app01 import models
def orm(requeset):
# 增加操作进行测试
return HttpResponse('orm')
from app01 import models
def orm(requeset):
# 增加操作进行测试
# 创建
# 方法一
models.UserInfo.objects.create(username='root',password='123')
# 方法二
obj = models.UserInfo(username='xxx',password='qwe')
obj.save()
return HttpResponse('orm')

python-django-models-01
继续修改视图函数
def orm(requeset):
# 增加操作进行测试
# 创建
# 方法一
# models.UserInfo.objects.create(username='root',password='123')
# 方法二
# obj = models.UserInfo(username='xxx',password='qwe')
# obj.save()
# 方法三,传入字典
dic = {'username': 'abc', 'password': '666'}
models.UserInfo.objects.create(**dic)
return HttpResponse('orm')
def orm(requeset):
# 增加操作进行测试
# 创建
# 方法一
# models.UserInfo.objects.create(username='root',password='123')
# 方法二
# obj = models.UserInfo(username='xxx',password='qwe')
# obj.save()
# 方法三,传入字典
# dic = {'username': 'abc', 'password': '666'}
# models.UserInfo.objects.create(**dic)
# 查
# 查询所有,返回的是一个QuerySet
result = models.UserInfo.objects.all()
# <QuerySet [<UserInfo: UserInfo object>, <UserInfo: UserInfo object>, <UserInfo: UserInfo object>]>
print(result)
for row in result:
print(row.id, row.username, row.password)
# 过滤
result = models.UserInfo.objects.filter(username='root')
# 多个条件
result = models.UserInfo.objects.filter(username='root', password='123')
# 删
# models.UserInfo.objects.filter(username='root').delete()
# 改
# models.UserInfo.objects.filter(username='root').update(password='asd')
return HttpResponse('orm')
def orm(request):
if request.method == "GET":
return render(request, 'orm.html')
elif request.method == "POST":
u = request.POST.get('username')
p = request.POST.get('password')
# 使用first(),如果有则返回一个对象,如果没有则返回一个None
obj = models.UserInfo.objects.filter(username=u, password=p).first()
# 获取obj之后,可以在页面显示用户信息
if obj:
return redirect('/index/')
else:
return render(request, 'orm.html')
return HttpResponse('orm')
orm.html
<body>
<form action="/orm/" method="post">
<p>
用户名: <input type="text" name="username">
</p>
<p>
密码: <input type="password" name="password">
</p>
<input type="submit" value="提交">
</form>
</body>
from django.conf.urls import include
urlpatterns = [
# 添加如下语句,使用路由分发
url(r'^cmdb/', include('app01.urls')),
]
文件若不存在,则创建
from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.conf.urls import include
urlpatterns = [
url(r'^user_info/', views.user_info),
url(r'^userdetail-(?P<nid>\d+)/', views.user_detail),
url(r'^userdel-(?P<nid>\d+)/', views.user_del),
url(r'^useredit-(?P<nid>\d+)/', views.user_edit),
]
def user_info(request):
if request.method == "GET":
user_list = models.UserInfo.objects.all()
# print(user_list.query)
# SELECT "app01_userinfo"."id", "app01_userinfo"."username", "app01_userinfo"."password" FROM "app01_userinfo"
# QuerySet [obj, obj, ]
return render(request, 'user_info.html', {'user_list': user_list})
elif request.method == "POST":
u = request.POST.get('user')
p = request.POST.get('pwd')
models.UserInfo.objects.create(username=u, password=p)
return redirect('/cmdb/user_info/')
def user_detail(request, nid):
# 取单条数据,如果不存在,会直接报错
# obj = models.UserInfo.objects.get(id=nid)
obj = models.UserInfo.objects.filter(id=nid).first()
return render(request, 'user_detail.html', {'obj': obj})
def user_del(request, nid):
models.UserInfo.objects.filter(id=nid).delete()
return redirect('/cmdb/user_info')
def user_edit(request, nid):
if request.method == "GET":
obj = models.UserInfo.objects.filter(id=nid).first()
return render(request, 'user_edit.html', {'obj': obj})
elif request.method == "POST":
nid = request.POST.get('id')
u = request.POST.get('username')
p = request.POST.get('password')
models.UserInfo.objects.filter(id=nid).update(username=u, password=p)
return redirect('/cmdb/user_info/')
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/common.css">
</head>
<body>
<div class="pg-header">
xxxxxxxx
</div>
<div>
<div class="menu-header">
<p><a class="menu" href="/cmdb/user_info/">用户管理</a></p>
<p><a class="menu" href="/cmdb/user_group/">用户组管理</a></p>
</div>
</div>
{% block content-wrapper %}
{% endblock %}
</body>
</html>
{% extends 'base.html' %}
{% load staticfiles %}
{% block content-wrapper %}
<div class="pg-content">
<h3>添加用户</h3>
<form action="/cmdb/user_info/" method="post">
<input type="text" name="user">
<input type="password" name="pwd">
<input type="submit" value="添加">
</form>
<h3>用户列表</h3>
<ul>
{% for row in user_list %}
<li>
<a href="/cmdb/userdetail-{{ row.id }}/">{{ row.username }}</a> |
<a href="/cmdb/useredit-{{ row.id }}/">编辑</a> |
<a href="/cmdb/userdel-{{ row.id }}/">删除</a>
</li>
{% endfor %}
</ul>
</div>
{% endblock %}
{% extends 'base.html' %}
{% load staticfiles %}
{% block content-wrapper %}
<div class="pg-content">
<h1>用户详细信息</h1>
<h5>{{ obj.id }}</h5>
<h5>{{ obj.name }}</h5>
<h5>{{ obj.password }}</h5>
</div>
{% endblock %}
{% extends 'base.html' %}
{% load staticfiles %}
{% block content-wrapper %}
<div class="pg-content">
<h1>编辑用户</h1>
<form action="/cmdb/useredit-{{ obj.id }}/" method="post">
<input style="display: none;" type="text" name="id" value="{{ obj.id }}">
<input type="text" name="username" value="{{ obj.username }}">
<input type="password" name="password" value="{{ obj.password }}">
<input type="submit" value="提交">
</form>
</div>
{% endblock %}
本实例相关知识¶
- form表单需要做特殊设置,
enctype="multipart/form-data"
- 服务端接收文件并保存
<!-- 上传文件需要增加enctype="multipart/form-data" 属性 -->
<form action="/login/" method="post" enctype="multipart/form-data">
<p>
篮球: <input type="checkbox" name="favor" value="1">
足球: <input type="checkbox" name="favor" value="2">
台球: <input type="checkbox" name="favor" value="3">
乒乓球: <input type="checkbox" name="favor" value="4">
</p>
<input type="file" name="xxx">
<input type="submit" value="提交">
</form>
def login(request):
if request.method == "GET":
return render(request, 'login.html')
elif request.method == "POST":
obj = request.FILES.get("xxx")
if obj:
# <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
# from django.core.files.uploadedfile import InMemoryUploadedFile
# 可以通过上述对象,查看obj详细信息
# obj.name 为文件名
print(obj, type(obj), obj.name)
# upload/文件名
file_path = os.path.join('upload', obj.name)
# 如果目录不存在,则创建
if not os.path.exists(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
# 打开一个文件,将用户上传的内容写入文件,并关闭文件
f = open(file_path, mode='wb')
for i in obj.chunks():
f.write(i)
f.close()
return redirect('/login/')
else:
return redirect('index')
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', views.login),
url(r'^home/', views.Home.as_view()),
]
详细信息看父类View
from django.views import View
# views里面也可以使用类
class Home(View):
# 需要from django.views import View
def get(self, request):
# return HttpResponse('<h1> class Home get</h1>')
print(request.method)
return render(request, 'home.html')
def post(self, request):
print(request.method)
return render(request, 'home.html')
class View(object):
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 编写对应方法,对应的请求则会触发对应的方法
# Django会先拿到请求,执行dispatch方法,通过getattr方法获取对应方法
# 再执行对应方法
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
我们也可以自己定义dispatch
class Home(View):
# 需要from django.views import View
def dispatch(self, request, *args, **kwargs):
# 每一次执行对应方法的时候,会在执行之前打印"before",执行之后打印"after"
print('before')
# 调用父类中的dispatch
result = super(Home,self).dispatch(request, *args, **kwargs)
print('after')
return result
def get(self, request):
# return HttpResponse('<h1> class Home get</h1>')
print(request.method)
return render(request, 'home.html')
def post(self, request):
print(request.method)
return render(request, 'home.html')
from app01 import models
def orm(requeset):
# 增加操作进行测试
# 创建
# 方法一
# models.UserInfo.objects.create(username='root',password='123')
# 方法二
# obj = models.UserInfo(username='xxx',password='qwe')
# obj.save()
# 方法三,传入字典
dic = {'username': 'abc', 'password': '666'}
models.UserInfo.objects.create(**dic)
return HttpResponse('orm')
# 查
# 查询所有,返回的是一个QuerySet
result = models.UserInfo.objects.all()
# <QuerySet [<UserInfo: UserInfo object>, <UserInfo: UserInfo object>, <UserInfo: UserInfo object>]>
# 过滤
result = models.UserInfo.objects.filter(username='root')
# 多个条件
result = models.UserInfo.objects.filter(username='root', password='123')
models.UserInfo.objects.filter(username='root').delete()
models.UserInfo.objects.filter(username='root').update(password='asd')
obj = models.UserInfo.objects.filter(username=u, password=p).first()
Django用户认证¶
要实现这样的需求其实很简单:
- 使用django自带的装饰器
@login_required
。 - 在相应的view方法的前面添加
@login_required
- 并在settings.py中配置
LOGIN_URL
参数 - 修改login.html中的表单action参数
python3 manage.py createsuperuser
<form class="form-signin" action="/accounts/login/" method="post">{% csrf_token %}
...
</form>
Django相关命令¶
新建app 前提: 处于项目目录
一个项目可以有多个app,通用的app也可以在多个项目中使用.
app的名字需要为合法的Python包名
Django 1.7.1及以上使用如下命令
如果是之前的版本,Django无法自动更改表结构,迁移数据,不过可以使用第三方工具south
创建更改文件
将更改应用到数据库
启动 web server
# 更改监听端口
python manage.py runserver 9999
# 监听所有 ip
python manage.py runserver 0.0.0.0:8000
# 如果是外网或者局域网电脑上可以用其它电脑查看web server
# 访问对应的 ip加端口,比如 http://172.16.20.2:8000
清空数据库,仅留空表
创建超级管理员,按照提示输入用户名和对应密码
修改 用户密码
python manage.py changepassword username
python manage.py dumpdata appname > appname.json
python manage.py loaddata appname.json
Django项目环境终端
数据库命令行
Django 会自动进入在settings.py中设置的数据库,如果是 MySQL 或 postgreSQL,会要求输入数据库用户密码。
在这个终端可以执行数据库的SQL语句。
Django认证系统¶
https://docs.djangoproject.com/en/2.0/topics/auth/
Django提供了一个用户身份验证系统, 它处理用户帐户、组权限和基于cookie的用户会话。
Django配置详解¶
- 配置文件
settings.py
中,两个配置参数跟时间与时区有关TIME_ZONE
USE_TZ
- 如果
USE_TZ
为True
- Django会使用系统默认设置的时区,即
America/Chicago
,此时的TIME_ZONE
不管有没有设置都不起作用。
- Django会使用系统默认设置的时区,即
- 如果
USE_TZ
为False
TIME_ZONE
为None
,Django会使用默认的America/Chicago
时间。TIME_ZONE
设置为其它时区的话,则还要分情况- 如果是Windows系统,则
TIME_ZONE
设置是没用的,Django会使用本机的时间。 - 如果为其他系统,则使用该时区的时间,如设置
USE_TZ = False
,TIME_ZONE = 'Asia/Shanghai'
, 则使用上海的UTC
时间
- 如果是Windows系统,则
Django¶
Django问题记录¶
示例如下:
# coding:utf-8
from __future__ import unicode_literals
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Article(models.Model):
title = models.CharField('标题', max_length=256)
content = models.TextField('内容')
pub_date = models.DateTimeField('发表时间', auto_now_add=True, editable = True)
update_time = models.DateTimeField('更新时间',auto_now=True, null=True)
def __str__(self):
return self.title
python_2_unicode_compatible 会自动做一些处理去适应python不同的版本,本例中的 unicode_literals 可以让python2.x 也像 python3 那个处理 unicode 字符,以便有更好地兼容性。
导入数据
import django
if django.VERSION >= (1, 7):#自动判断版本
django.setup()
Django REST framework¶
教程¶
本教程将会通过一些简单的代码来实现 Web API. 这个过程将会介绍
REST framework
的各个组件, 带你深入理解各个组件是如何一起工作.
本教程需要花一些时间, 所以在开始之前,
你可以去准备一些饼干和你喜欢的啤酒. 如果你只是想快速浏览, 请直接查看
快速浏览
的文档.
本教程的代码在GitHub tomchristie/rest-framework-tutorial 中, 在 https://restframework.herokuapp.com/ 有一个沙箱版本用于测试, 它完整实现了该教程.
在做任何事情之前, 我们先使用 virtualenv
创建一个全新的虚拟环境.
这将确保我们的配置跟我们其他的项目完全隔离.
virtualenv env
source env/bin/activate
pip install django
pip install djangorestframework
pip install pygments # We'll be using this for the code highlighting
注意: 退出虚拟环境, 只需要执行deactivate
. 更多信息查看 virtualenv documentation
首先, 创建一个新项目.
cd ~
django-admin.py startproject tutorial
cd tutorial
创建一个应用, 用于创建简单的 Web API
python manage.py startapp snippets
添加 snippets
, rest_framework
应用到 INSTALLED_APPS
. 编辑
tutorial/settings.py
.
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
ok, 我们准备下一步
为了实现本教程, 我们创建一个 Snippet
模型, 用于存储代码片段.
开始编辑 snippets/models.py
文件. 注意:
优秀的编程实践都会对代码进行注释. 本教程的代码仓库有详细的注释,
在这里我们忽略它, 关注代码本身.
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
为 snippet
模型创建初始迁移, 并在第一次同步数据库
python manage.py makemigrations snippets
python manage.py migrate
The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as json. We can do this by declaring serializers that work very similar to Django’s forms. Create a file in the snippets directory named serializers.py and add the following.
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
The first part of the serializer class defines the fields that get serialized/deserialized. The create() and update() methods define how fully fledged instances are created or modified when calling serializer.save()
A serializer class is very similar to a Django Form class, and includes similar validation flags on the various fields, such as required, max_length and default.
The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The {‘base_template’: ‘textarea.html’} flag above is equivalent to using widget=widgets.Textarea on a Django Form class. This is particularly useful for controlling how the browsable API should be displayed, as we’ll see later in the tutorial.
We can actually also save ourselves some time by using the ModelSerializer class, as we’ll see later, but for now we’ll keep our serializer definition explicit.
用序列化(Serializers)工作
在我们深入之前, 我们需要熟练使用新的序列化类(Serializer class). 让我们进入Django命令行
python manage.py shell
导入相关依赖, 并创建一堆代码片段
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
我们已经有了一些 snippet
实例, 让我们看看如何将其中一个实例序列化
注: Model -> Serializer
serializer = SnippetSerializer(snippet)
serializer.data
# {'style': 'friendly', 'code': u'print "hello, world"\n', 'language': 'python', 'title': u'', 'linenos': False, 'id': 2}
现在我们将模型实例(model instance)转化成Python原生数据类型.
为了完成实例化过程, 我们将数据渲染成 json
.
注: Serializer -> JSON
content = JSONRenderer().render(serializer.data)
content
# '{"id":2,"title":"","code":"print \\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'
反序列化相似, 首先我们将流(stream)解析成Python原生数据类型…
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
…然后, 我们将Python原生数据恢复成正常的对象实例
注: json -> serializer
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([(u'title', u''), (u'code', u'print "hello, world"'), (u'linenos', False), (u'language', 'python'), (u'style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
可以看到, API和表单很相似. 当我们用我们的序列(serializer)写视图的时候, 相似性会更明显.
除了将模型模型实例(model instance)序列化外,
我们也能序列化查询集(querysets), 只需要添加一个序列化参数 many=True
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
我们的 SnippetSerializer
类复制了包含 Snippet
模型在内的很多信息. 如果能够简化我们的代码, 那是极好的.
和Django提供的 Form
类和 ModelFrom
类相同, REST
框架包含了
Serializer
类和 ModelSerializer
类.
让我们使用 ModelSerializer
重构我们的Serializer, 再次打开
snippets/serializers.py
, 重写 SnippetSerializer
类.
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
序列一个非常好的属性就是,
你可以通过打印序列实例的结构(representation)查看它的所有字段. 输入
python manage.py shell
打开Django shell, 尝试如下代码:
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
# id = IntegerField(label='ID', read_only=True)
# title = CharField(allow_blank=True, max_length=100, required=False)
# code = CharField(style={'base_template': 'textarea.html'})
# linenos = BooleanField(required=False)
# language = ChoiceField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'),...
# style = ChoiceField(choices=[('abap', 'abap'), ('algol', 'algol'),...
记住, ModelSerializer
类并没有做任何有魔力的事情, 他们只是一个创建
serializer
类的快捷方式.
- 一个自动确认字段的集合
- 简单默认实现的
create()
和update()
方法.
让我们看看, 如何使用我们的序列化类来实现一些API视图. 我们不使用 REST
框架的其他特性, 只是写一些常规的Django视图
编辑 snippets/views.py
:
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
我们的根API视图将支持列出所有存在的 snippets
, 或者创建一个新的
snippet
@csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
注意, 应为我们希望我们可以从没有 csrf token
的客户端 POST
数据到该视图, 我们需要标记该视图为 csrf_exempt
. 通常你不想这样做,
REST
框架视图使用比这个更明智的方式, 不过那不是我们现在的目的.
我们还需要一个视图对应单个 snippet
, 同时我们使用这个视图, 恢复,
更新, 删除 snippet
.
@csrf_exempt
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JsonResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
最后, 我们需要使用路由将这些视图对应起来, 创建 snippets/urls.py
文件
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
同时需要配置 tutorial/urls.py
, 添加我们的 snippet
应用的
URLs
.
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('snippets.urls')),
]
值得注意的事, 有一些边界情况我们没有进行处理, 如果我们发送不正确的
json
数据, 或者使用一个我们的视图没有处理的方法来请求,
我们会得到500的错误, “Server Error”.
退出当前命令行.
quit()
启动Django开发服务器
python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
...
Django version 1.11.11, using settings 'tutorial.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
我们可以打开另一个终端进行测试, 我们可以使用 curl
, 或者 httpie
.
Httpie
是一个使用Python编写的对用户友好的http客户端. 让我们安装它.
pip install httpie
我们可以获取 snippets
列表
http http://127.0.0.1:8000/snippets/
HTTP/1.0 200 OK
...
[
{
"code": "foo = \"bar\"\n",
"id": 1,
"language": "python",
"linenos": false,
"style": "friendly",
"title": ""
},
{
"code": "print \"hello, world\"\n",
"id": 2,
"language": "python",
"linenos": false,
"style": "friendly",
"title": ""
},
]
我们可以指定 id
获取响应 snippet
http http://127.0.0.1:8000/snippets/2/
HTTP/1.0 200 OK
...
{
"code": "print \"hello, world\"\n",
"id": 2,
"language": "python",
"linenos": false,
"style": "friendly",
"title": ""
}
相似地, 使用浏览器访问也可以获得相同的 json
数据.
到目前为止, 我们做得很好, 我们编写的序列化 API
和
Django's Forms API
比较相似, 同时编写了一些常规的Django视图.
我们的 API
没有做什么特殊的事情, 除了作出json响应外,
还有一些边缘事件没有处理, 但至少是一个还有点功能的 Web API
.
在教程的第2部分, 我们将介绍如何对我们的 API
进行改进.
从这节开始, 我们会接触到 REST
框架的核心.
让我们介绍一些基本构建组件.
REST framework
引入了一个 Request
对象, 它扩展了常规的
HttpRequest
, 并提供了灵活的请求解析. Request
对象的核心功能是
request.data
属性, 它和 request.POST
属性很相似, 但是它对
Web APIs
更加有用.
request.POST # 只处理表单数据. 仅用于 'POST' 方法.
request.data # 处理任意数据. 可以用于 'POST', 'PUT' and 'PATCH' 方法.
REST framework
也引入了 Response
对象,
它是一类用为渲染和使用内容协商来决定返回给客户端的正确内容类型的
TemplateResponse
.
return Response(data) # Renders to content type as requested by the client.
在你的视图中使用数字HTTP状态码并不总是易读的, 错误代码也容易被忽略.
REST framework
为每个状态码提供更明确的标识符, 例如 状态模块中的
HTTP_400_BAD_REQUEST
.
使用这种标识符代替纯数字标识符是一个不错的主意.
REST framework
提供两个装饰器.
@api_view
装饰器用在基于视图的方法.APIView
类用于基于类的视图上.
这些装饰器提供一些功能. 例如确保从你的视图中获取 Request
对象,
例如在 Response
对象中添加上下文.
同时还提供一些行为, 例如在合适的时候返回 405 Method Not Allowed
响应, 例如处理在访问错误输入的 request.data
时出现的 ParseError
异常.
Okay, 让我们使用这些新的组件去一些视图.
在 views.py
中不再需要 JSONResponse
类, 现在删除他们,
然后轻微地重构我们的视图
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
我们的实例视图是前面的修改版, 更简洁, 和我们使用的 Form API
很相似,
同时使用了命名状态码, 让响应代码意义更明显.
views.py
中独立 snippet
的视图:
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
这对我们来说应该非常熟悉, 跟常规的Django视图没什么区别.
注意, 我们不再明确指定请求或响应的上下文类型. request.data
可以处理的 json
格式的请求, 同样也可以处理其他格式. 同样的, 我们允许
REST
框架将响应对象的数据渲染成正确的内容类型返回给客户端.
我们的响应不再是单一的内容格式, 根据这个事实,
我们可以在API尾部添加格式后缀, 格式后缀给我们一个参考的格式,
这意味着我们的API可以处理 http://example.com/api/items/4.json.
这样的URLs.
在视图中添加一个 format
关键字参数, 像这样
def snippet_list(request, format=None):
和
def snippet_detail(request, pk, format=None):
现在更新 snippets/urls.py
文件, 在已经存在的URL中添加一个
format_suffix_patterns
集合.
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
我们不必添加额外的URL模式, 但是它给我们一个简单, 清楚的方式指定特定的格式.
继续像 tutorial part 1
中一样, 通过命令行测试 API, 一切都相当类似,
同时我们可以很好地处无效请求产生的错误.
我们可以像之前一样, 获得 snippets
列表
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
我们可以通过使用 Accept
响应头控制返回的响应的格式.
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
或者在URL后添加格式后缀:
http http://127.0.0.1:8000/snippets.json # JSON 后缀
http http://127.0.0.1:8000/snippets.api # 可浏览的 API 后缀
同样的, 我们可以使用 Content-Type
头控制我们请求的格式.
# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"id": 3,
"title": "",
"code": "print 123",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
{
"id": 4,
"title": "",
"code": "print 456",
"linenos": false,
"language": "python",
"style": "friendly"
}
如果你使用 --debug
参数, 你可以看到请求头中的请求类型.
使用浏览器打开 http://127.0.0.1:8000/snippets/.
由于 API
响应类型是根据客户端的请求进行选择的, 因此, 当使用 web
浏览器请求的时候, 默认会使用 HTML
格式来表示资源. 这允许 API
返回一个完整的浏览器可视的 HTML
表示.
拥有一个浏览器可视化的 API
是非常有用的. 这会使得开发和使用 API
变的极为简单. 这也让其他开发者更容易查看和使用你的 API
.
查看 browsable
api
主题获取更更多关于 browsable API
的信息, 比如 特性, 定制.
在教程的第3部分, 我们将开始使用基于类的视图(CBV), 并介绍如何使用通用的视图来减少代码量.
我们也可以使用基于类的视图编写我们的 API
, 如我们所见,
这是一个有利的模式, 允许我们重用共同的功能,
使我们的代码不重复
重构 views.py
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
到目前为止, 一起都好. 它和之前的情况非常类似, 但我们可以更好的区分不同的
HTTP 方法, 我们需要继续更新 views.py
中的实例视图.
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
同时, 我们需要用基于类的视图的方式, 重构 snippets/urls.py
.
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Okay, 重构完成, 再运行开发服务器, 一切都和之前一样正常工作.
使用基于类的视图的最大的好处就是, 允许我们快速的创建可复用的行为.
我们一直使用的 create/retrieve/update/delete
操作和我们创建的任何后端模型 API 很相似. 这些普遍的共同行为在 REST
框架的 mixin
类中实现.
让我们看看如何使用 mixin
类编写 views.py
模块.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
我们话一些时间测试这里发生了什么, 我们使用 GenericAPIView
创建我们的视图, 同时加入 ListModelMixin
和 CreateModelMixin
.
基础类提供核心功能, mixin
类提供 .list()
和 .create()
动作.
然后我们绑定 get
和 post
方法到合适的动作, 到目前为止,
已经变得足够简单.
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
相似地. 我们使用 GenericAPIView
类提供核心功能, 添加 mixins
提供
.retrieve()
, .update()
and .destroy()
动作.
我们使用 mixin
类使用比之前较少的代码编写视图, 但我们可以更进一步.
REST
框架提供一个已经混入一般视图的集合, 我们可以用他们进一步缩减
views.py
模块.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Wow, 我们的代码看起来如此简介, 如此的Django
接下来我们学习 part 4 of the tutorial
, 我们将学到如何为我们的
API
处理授权(authentication
)和权限(permissions
)
当前, 我们的 API
没有限制, 谁都可以编辑或删除 snippets
.
我们需要一些更高级的行为来确保:
- 代码片段总是与创建者联系在一起
- 只有授权用户才能创建
snippets
- 只有
snippet
创建者可以更新或者删除它 - 未授权的请求只有只读权限.
我们需要对我们的 Snippet
模型类做一些修改. 首先, 添加两个字段,
一个用来代表代码片段的创建者, 另一个用来储存高亮显示的HTML代码.
修改 models.py
添加字段到 Snippet
模型中.
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
同时, 我们需要确保, 模型在保存的时候, 使用 pygments
代码高亮库填充
highlighted
字段.
我们需要额外导入:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
添加 .save()
方法到模型类
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
当我们完成这些, 我们需要更新我们的数据库表结构, 正常情况下, 我们创建数据库迁移(database migration), 但是在本教程中, 我们只需要删除原来的数据库, 重新创建.
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你需要创建一些不同的用户, 用来测试 API, 最快的方式是使用
createsuperuser
命令
python manage.py createsuperuser
现在我们已经创建了一些用户, 我们最好将用户添加到我们的 API,
我们很容易创建一个新的序列. 在 serializers.py
文件中添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
因为在用户模型中 snippets
是一个相反的关系, 使用 ModelSerializer
类, 默认不会包含它, 所以我们需要手动为用户序列添加这个字段.
我们还需要添加两个视图到 views.py
中. 我们为用户添加只读视图,
因此我们使用基于视图的一般类 ListAPIView
和 RetrieveAPIView
.
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
确保导入了 UserSerializer
类
from snippets.serializers import UserSerializer
最后, 我们需要修改 URL
配置, 添加这些视图到 API
中,
添加一下内容到 urls.py
中.
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
现在, 如果我们创建一个代码片段, 我们没法将用户和创建的 snippet
实例联系起来. 虽然用户不是序列表示的部分, 但是代表传入请求的一个属性
我们通过重写 snippet
视图的 .perform_create()
方法来处理这个问题. 它允许我们修改如何保存实例,
处理任何请求对象的信息或者请求链接的信息.
在 SnippetList
视图类下添加如下方法
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在, 我们序列的 create()
方法将会传入一个有效请求数据的 owner
字段
现在, snippets
和创建他们的用户已经建立了联系, 更新我们的
SnippetSerializer
来表示用户. 在 serializers.py
的序列定义中添加一下字段:
owner = serializers.ReadOnlyField(source='owner.username')
同时, 确保将 ``’owner’`` 添加到 ``Meta`` 类的字段中.
这个字段会做一些有趣的事情. source
参数控制哪个属性被用作于一个字段,
并可以指向 serialized
实例的任何属性.
它也能像上面一样使用点标记(.
), 这种情况下它会横贯给定的所有属性,
就像我们使用django模板语言一样.
我们添加的字段是无类型的 ReadOnlyField
类, 与其他类型的字段, 例如
CharField
, BooleanField
等等…相比, 无类型的 ReadOnlyField
总是只读的, 用于序列化表示,
但不能用于数据反序列化的时候用来更新模型实例. 这里我们也可以使用
CharField(read_only=True)
.
现在,用户已经和代码片段联系起来, 我们需要确保, 只有授权的用户可以创建, 更新, 删除代码片段
REST
框架包含许多权限类, 可以用来实现视图的访问权限. 这种情况下,
我们需要 IsAuthenticatedOrReadOnly
来确保授权请求获得读写权限,
未经授权的请求只有只读权限.
首先, 在视图模块中引入如下代码:
from rest_framework import permissions
然后在 SnippetList
和 SnippetDetail
视图类中添加如下属性.
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
如果你打开浏览器操控可浏览的API, 你将不再有创建新的代码片段的权限. 为此, 我们需要以用户身份登录.
我们添加一个登录视图, 编辑项目级别的 URLconf
: urls.py
文件
添加导入语句
from django.conf.urls import include
文件末尾, 添加一个包含登录和登出视图的url样式
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls')),
]
r'^api-auth/'
可以使用你想要的URL
现在, 如果再次打开浏览器, 刷新页面, 你将可以看到一个 Login
链接在页面的右上角. 现在可以使用已经创建的用户登录, 创建代码片段.
一旦您创建了一些代码片段, 访问 ‘/users/’ 端点, 你会注意到在每个用户的
snippets
字段, 会显示跟用户有关的 snippets
id.
虽然我们想让所有人看到代码片段, 但同时也要确保只有创建代码片段的用户可以更新或删除它.
我们需要创建自定义权限.
在 snippets
应用中, 创建一个新的文件, permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
现在, 通过编辑 SnippetDetail
视图类中的 permission_classes
属性,
我们可以添加自定义权限到我们的 snippet
实例端点
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
确保导入 IsOwnerOrReadOnly
类.
from snippets.permissions import IsOwnerOrReadOnly
现在, 如果你再次使用浏览器, 你会发现只有你登录与创建代码片段一致的用户,
你才有权限使用 'DELETE'
and 'PUT'
动作.
由于现在 API 有权限集合, 在我们需要编辑任何 snippets
的时候,
需要认证我们的请求, 我们没有设置其他任何认证类(authentication
classes),
默认情况下只有 SessionAuthentication
和 BasicAuthentication
.
当我们通过浏览器进行交互时, 我们可以登录, 浏览器会话(session)将为请求提供认证.
如果我们以编程的方式使用 API, 我们需要为每个请求提供明确的 认证凭证
.
如果我们尝试在没有认证的情况下创建 snippet
, 我们会获得一个 error.
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过提供之前创建的用户的用户名和密码, 来创建 snippet
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
我们的 API
已经具有一个相当精细的权限集合,
同时为系统用户和他们创建的 snippets
提供了端点.
在教程的第5部分, 我们将介绍如何为高亮的 snippets
创建一个HTML端点,
将所有内容联系起来. 同时为系统中的关系使用超链接提高我们 API
的凝聚力.
目前, 我们用主键代表我们API之间的关系. 在这节里面, 我们会用超链接改善API之间的关系.
现在我们有 'snippets'
和 'users'
的端点,
但是没有为我们的API设置单独的入口. 我们使用基于方法的常规视图和
@api_view
装饰器创建一个入口端点. 在文件 snippets/views.py
中添加:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
在这里我们需要注意两点, 首先我们使用 REST
框架的 reverse
方法限定返回的 URLs
, 其次, URL格式使用方便的名字作标识符, 稍后会在
snippets/urls.py
中声明.
还有一个明显的事情就是我们的 pastebin API
缺乏代码高亮的端点.
与我们其他的API端点不通, 我们不想使用 JSON
, 而只使用 HTML
显示.REST
框架提供了两种渲染方式, 一种是用模板渲染,
另一种是用预渲染 HTML
, 在这个端点, 我们使用第二种渲染方式.
另一个需要我们思考的是, 在创建高亮代码视图的时候, 高亮视图在通用视图中是不存在的. 我们不会返回一个对象实例, 而是返回对象的一个属性.
我们不使用通用视图, 而是通过基础类, 在 snippets/views.py
中创建我们自己的 .get()
方法
from rest_framework import renderers
from rest_framework.response import Response
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
像往常一样, 我们需要添加新的视图到 URL
配置中, 文件
snippets/urls.py
url(r'^$', views.api_root),
然后为高亮 snippet
添加一个url样式.
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
在Web API设计中, 处理实体之间的关系是一项非常有挑战的事情. 代表一种关系可以有很多种方式
- 使用主键
- 在实体间使用超链接
- 在相关的实体上使用唯一的
slug
- 使用相关实体的默认字符串
- 在父表述使用嵌套的实体
- 其他自定义的表述
REST
框架支持以上所有的方式, 正向或反向关系均可以使用,
或者像使用一般外键一样使用自定义的管理方式.
在这种情况下, 我们在实体间使用超链接方式. 为了达到目的,
我们将修改我们的序列(serializers
), 扩展
HyperlinkedModelSerializer
代替 ModelSerializer
.
HyperlinkedModelSerializer
和 ModelSerializer
有以下几点不同:
- 默认不包括
id
字段 - 它包括一个
url
字段, 使用HyperlinkedIdentityField
- 关系使用
HyperlinkedRelatedField
代替PrimaryKeyRelatedField
我们可以快速的将存在的序列重写成超链接的方式, 文件
snippets/serializers.py
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner',
'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
注意, 我们还新添加了一个 'highlight'
字段. 这个字段的类型和 url
字段类型一致, 只是它指向 'snippet-highlight'
端点, 而不是
'snippet-detail'
因为我们已经配置了 URLs
后缀, 比如 '.json'
, 同时我们需要在
highlight
字段中指明后缀, .html
如果我们要使用超链接API, 我们必须确保对 URL
模式进行命名,
让我们看看哪些链接需要命名
- 根API指向
'user-list'
和'snippet-list'
. snippet
序列包括一个指向'snippet-highlight'
的字段.user
序列包括一个指向'snippet-detail'
的字段.- 我们的
snippet
和user
序列包括 ‘url’ 字段默认指向'{model_name}-detail'
, 当前情况指向'snippet-detail'
和'user-detail'
.
命名加入 URL 配置之后, snippets/urls.py
应该是下面这样子.
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
用户和代码片段的列表视图可能会返回大量的实例, 所以我们要对返回的结果进行分页, 并允许客户端访问每个单页.
我们可以改变默认的列表样式来使用分页, 轻微的修改
tutorial/settings.py
文件, 添加如下配置
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
REST framework
的所有设置都是在 settings
中 REST_FRAMEWORK
字典中的. 它可以帮我们区分项目中的其他配置.
同时, 我们也可以自定义分页的样式, 在这里, 我们使用默认方式.
如果我们打开浏览器, 并访问可浏览的 API
,
你会发现你可以使用下面的链接使用 API
.
你也可以看到 snippet
实例的 'highlight'
链接,
这些链接会返回高亮的 HTML
代码.
在教程的第6部分, 我们会介绍怎么使用 ViewSets
和 Routers
通过更少的代码, 实现我们的 API
.
REST
框架包含一个 ViewSets
的抽象,
它可以让开发者将精力集中在构建API的状态和交互上, 同时帮助开发者,
基于共同约定, 自动处理 URL 构建.
ViewSet
类几乎和 View
类一样, 除了它提供的 read
或者
update
操作, 而不是像 get
或 put
一样的方法.
一个 ViewSet
类在它被实例化成一个视图集合的最后时刻,
通过一个处理复杂 URL 配置的 Router
类绑定, 且只绑定一个方法集合.
首先使用单个 UserViewSet
视图重构 UserList
和 UserDetail
视图.
文件 snippets/views.py
from rest_framework import viewsets
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
这里我们使用 ReadOnlyModelViewSet
类自动提供默认的 'read-only'
操作. 我们需要像使用常规视图一样, 设置 queryset
和
serializer_class
属性, 但是我们不再需要为两个分开的类提供相同的信息.
接下来替换 SnippetList
, SnippetDetail
and SnippetHighlight
视图类.
from rest_framework.decorators import detail_route
from rest_framework.response import Response
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
@detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
这一次我们使用了 ModelViewSet
类获得默认的完整的读写操作.
注意, 我们同时使用了 @detail_route
装饰器, 用于创建一个自定义动作,
即 highlight
. 这个装饰器可以用于添加任何不适合
create/update/delete
方式的自定义端点.
使用 @detail_route
装饰器自定义的动作默认会响应 GET
请求.
如果我们需要动作响应 POST
请求, 我们可以使用 methods
参数.
自定义动作的默认 URLs
取决于它们的名字. 如果你想改变url构建方法,
你可以在使用装饰器的时候传入 url_path
关键字参数.
处理方法, 只会按照我们的 URL 配置对相应方法进行绑定. 我们为我们的 ViewSets 显示地创建一个视图集合, 来看看发生了什么.
snippets/urls.py
文件, 我们绑定我们的 ViewSet
类到一组具体的视图.
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
'get': 'list'
})
user_detail = UserViewSet.as_view({
'get': 'retrieve'
})
注意我们如何从每个 ViewSet
类,
通过绑定http方法到响应的动作来创建多个视图.
现在, 我们将我们的资源绑定到了具体的视图, 我们可以像往常一样将我们的视图注册到url配置中
urlpatterns = format_suffix_patterns([
url(r'^$', api_root),
url(r'^snippets/$', snippet_list, name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
url(r'^users/$', user_list, name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])
因为我们使用 ViewSet
代替 View
, 实际上我们不需要自己设计 URL
配置. 我们可以通过 Router
类, 将资源(resources
),
视图(views
), urls 自动联系起来.
我们只需要使用一个路由注册合适的视图集合.
重写 snippets/urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from snippets import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
urlpatterns = [
url(r'^', include(router.urls))
]
使用 router
注册的视图集合提供一个 urlpattern. 包括两个参数 -
视图的URl前缀和视图集合本身.
我们使用的默认 DefaultRouter
类也会自动为我们创建 API 根视图.
现在我们可以从 views
模块中删除 api_root
方法
viewsets
是一个非常有用的抽象. 它可以确保 URL
原型和你的 API
保持一致, 最大限度的减少代码量, 允许你将精力放在 API 的交互和表示上,
而不是放在编写 URL conf
上.
这并不意味在所有地方都要使用 viewsets
.
在使用基于类的视图和基于函数的视图时, 需要进行权衡. 使用 viewsets
没有单独构建 views
明确.
在教程第7部分, 我们将介绍, 如何添加一个 APP schema
,
并使用客户端库或命令行工具与我们的 API
进行交互.
schema
是一种机器可读的文档, 用于描述可用的API端点, URLS,
以及他们支持的操作.
schema
可以用于自动生成文档,
也可以用于驱动可以与API交互的动态客户端库.
为了提供 schema
支持, REST
框架使用 Core
API
Core API
是用于描述 APIs
的文档规范.
它可以用来提供内部可用端点内部表示格式和API暴露的可能的交互.
它可以用于服务端或客户端.
当用于服务端时, Core API
允许API支持呈现 schema
或渲染超媒体格式.
当用于客户端, Core API
允许动态驱动的客户端库与任何支持 schema
或超媒体格式的 API
交互.
REST framework
支持明确定义的 schema
视图, 或自动生成的
schemas
. 由于我们使用 ViewSets
和 Routers
,
我们可以很简单的自动生成 schema
你需要安装 coreapi
pip install coreapi
在 URL 配置中包含一个自动生成的 schema
视图.
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title='Pastebin API')
urlpatterns = [
url(r'^schema/$', schema_view),
...
]
如果你使用浏览器访问 API 根节点, 在选项中, 你可以看到 corejson
选项变成可用的状态.

django-rest-framework-2
我们也可以使用命令行, 通过在 Accept
请求头中指定期望的内容类型, 请求
schema
.
$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json
{
"_meta": {
"title": "Pastebin API"
},
"_type": "document",
...
默认的输出风格使用的Core JSON编码.
其他的 schema
格式, 比如 Open API
(以前称作Swagger), 同样支持.
现在, 我们的 API 暴露一个 schema
端点,
我们可以使用动态客户端库与API交互. 为了证明这点, 我们是用 Core API
命令行客户端
命令行客户端需要使用 coreapi-cli
包
pip install coreapi-cli
通过命令行检查, coreapi-cli
是否可用
$ coreapi
Usage: coreapi [OPTIONS] COMMAND [ARGS]...
Command line client for interacting with CoreAPI services.
Visit http://www.coreapi.org for more information.
Options:
--version Display the package version number.
--help Show this message and exit.
Commands:
...
首先, 我们使用命令行客户端加载 API schema
$ coreapi get http://127.0.0.1:8000/schema/
<Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
highlight(id)
list()
read(id)
}
users: {
list()
read(id)
}
我们还没有认证, 所以我们只能看到只读端点, 符合我们设计的 API
权限
让我们尝试使用命令行客户端, 列出已经存在的 snippets
$ coreapi action snippets list
[
{
"url": "http://127.0.0.1:8000/snippets/1/",
"id": 1,
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world!')",
"linenos": true,
"language": "python",
"style": "friendly"
},
...
有些API端点依赖命名参数. 比如, 我们要获取指定 snippet
的高亮 HTML,
需要提供一个 id
.
$ coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Example</title>
...
如果我们想要创建, 编辑, 删除 snippets
, 我们需要认证一个有效的用户.
这种情况下, 我们只使用基本身份验证.
将 <username>
和 <password>
替换成真是的用户名和密码.
$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
Added credentials
127.0.0.1 "Basic <...>"
现在, 如果我们重新获取 schema
, 我们可以看到所有的可用交互的集合.
$ coreapi reload
Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
create(code, [title], [linenos], [language], [style])
delete(id)
highlight(id)
list()
partial_update(id, [title], [code], [linenos], [language], [style])
read(id)
update(id, code, [title], [linenos], [language], [style])
}
users: {
list()
read(id)
}
现在我们可以和这些端点交互. 比如, 创建一个新的 snippet
:
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
"url": "http://127.0.0.1:8000/snippets/7/",
"id": 7,
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world')",
"linenos": false,
"language": "python",
"style": "friendly"
}
删除 snippet
coreapi action snippets delete --param id=7
除了使用命令行客户端, 开发者也可以使用客户端库与您的API进行交互.
Python客户端第一个可用的, 不久之后就会发布 Javascript
客户端库.
有关自定义 schema
生成和使用 Core API
客户端库,
你可以参考完整的文档.
使用很少的代码, 我们拥有了一个完整的可浏览的 pastebin Web API
,
它包含一个 schema-driven
客户端库, 完整的身份认证,
对象级权限和多格式渲染器.
我们走过了设计过程的每一步, 看到了如何使用常规的Django视图进行定制.
你可以在GitHub上查阅最终的代码 tutorial code, 或者在 the sandbox 中进行尝试.
到这里, 我们已经完成了教程. 如果你想跟多的参与到 REST framework
项目, 你可以使用以下几种方式:
- 在 GitHub
上进行审查, 提交问题, 发出
pull requests
. - 加入 REST framework discussion group, 帮助构建社区.
- 在Twitter上关注 作者, 并发送
hi
.
Now go build awesome things.
快速开始¶
创建一个简单的API, 支持管理员用户查看, 编辑用户和组.
# 创建项目目录
mkdir tutorial
cd tutorial
# 创建虚拟环境
Create a virtualenv to isolate our package dependencies locally
virtualenv env
# Windows上使用 `env\Scripts\activate`
source env/bin/activate
# 安装 Django 和 Django REST framework
pip install django
pip install djangorestframework
# 创建 tutorial 项目, 和 quickstart 应用
django-admin.py startproject tutorial . # 注意字符 '.'
cd tutorial
django-admin.py startapp quickstart
cd ..
同步数据库
python manage.py migrate
创建 admin
用户, 密码为 password123
.
python manage.py createsuperuser --email admin@example.com --username admin
准备工作完毕, coding…
首先定义一些序列化类, 创建一个新模块
tutorial/quickstart/serializers.py
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
本例中, 我们使用超链接关系 HyperlinkedModelSerializer
.
你还尅使用主键和其他各种关系, 但超链接是好的 RESTful
设计
打开 tutorial/quickstart/views.py
.
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from tutorial.quickstart.serializers import UserSerializer, GroupSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupSerializer(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
我们将共同的行为写在 ViewSets
类中, 而不是重复写多个视图
在我们需要的时候, 可以很容易的区分每个视图, 但使用viewsets可以很好地, 简洁地组织视图
tutorial/urls.py
from django.conf.urls import url, include
from rest_framework import routers
from tutorial.quickstart import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
我们使用 viewsets
代替 views
, 所以我们可以使用 router
类便捷的注册 view
为我们的 API
自动生成 URL配置.
Again, if we need more control over the API URLs we can simply drop down to using regular class-based views, and writing the URL conf explicitly.
Finally, we’re including default login and logout views for use with the browsable API. That’s optional, but useful if your API requires authentication and you want to use the browsable API.
添加 'rest_framework'
到 INSTALLED_APPS
中. 配置模块
tutorial/settings.py
INSTALLED_APPS = (
...
'rest_framework',
)
测试 API, 从命令行启动服务器.
python manage.py runserver
使用 curl
进行测试
➜ ~ curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
[
{
"url": "http://127.0.0.1:8000/users/2/",
"username": "tom",
"email": "tom@example.com",
"groups": []
},
{
"url": "http://127.0.0.1:8000/users/1/",
"username": "admin",
"email": "admin@example.com",
"groups": []
}
]
直接通过浏览器访问 http://127.0.0.1:8000/users/

django-rest-framework
右上角登录可以进行编辑
Django REST framework¶
当前版本 version 3
http://www.django-rest-framework.org/
Django REST framework
用于构建 web api
, 具有强大,灵活的特性.
REST framework requires the following:
- Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
- Django (1.10, 1.11, 2.0)
可选包:
- coreapi (1.32.0+) - Schema generation support.
- Markdown (2.1.0+) - Markdown support for the browsable API.
- django-filter (1.0.1+) - Filtering support.
- django-crispy-forms - Improved HTML display for filtering.
- django-guardian (1.1.1+) - Object level permissions support.
通过 pip
安装, 需要的可选包也可以一并安上
pip install djangorestframework
pip install markdown # Markdown support for the browsable API.
pip install django-filter # Filtering support
将 ‘rest_framework’ 添加到项目 INSTALLED_APPS
设置中.
INSTALLED_APPS = (
...
'rest_framework',
)
如果你打算使用可视化的 API 或者 REST framework 的登入登出视图.
添加如下配置到 urls.py
文件中.
# url路径自行修改
urlpatterns = [
...
url(r'^api-auth/', include('rest_framework.urls'))
]
# 安装 djangorestframework
pip3 install djangorestframework
# 创建项目
django-admin startproject resttest
cd resttest
# 同步数据库
python3 manage.py makemigrations
python3 manage.py migrate
REST framework API
全局配置均配置在名为 REST_FRAMEWORK
的字典中.
添加如下配置到 settings.py
中
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
注册APP
INSTALLED_APPS = (
...
'rest_framework',
)
完整 urls.py
内容
from django.conf.urls import url, include
from django.contrib.auth.models import User
from rest_framework import routers, serializers, viewsets
# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'is_staff')
# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
启动server
python3 manage.py runserver
访问 http://127.0.0.1:8000 即可看到我们的 users
API.
framework¶
教程¶
本教程将会通过一些简单的代码来实现 Web API. 这个过程将会介绍
REST framework
的各个组件, 带你深入理解各个组件是如何一起工作.
本教程需要花一些时间, 所以在开始之前,
你可以去准备一些饼干和你喜欢的啤酒. 如果你只是想快速浏览, 请直接查看
快速浏览
的文档.
本教程的代码在GitHub tomchristie/rest-framework-tutorial 中, 在 https://restframework.herokuapp.com/ 有一个沙箱版本用于测试, 它完整实现了该教程.
在做任何事情之前, 我们先使用 virtualenv
创建一个全新的虚拟环境.
这将确保我们的配置跟我们其他的项目完全隔离.
virtualenv env
source env/bin/activate
pip install django
pip install djangorestframework
pip install pygments # We'll be using this for the code highlighting
注意: 退出虚拟环境, 只需要执行deactivate
. 更多信息查看 virtualenv documentation
首先, 创建一个新项目.
cd ~
django-admin.py startproject tutorial
cd tutorial
创建一个应用, 用于创建简单的 Web API
python manage.py startapp snippets
添加 snippets
, rest_framework
应用到 INSTALLED_APPS
. 编辑
tutorial/settings.py
.
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
ok, 我们准备下一步
为了实现本教程, 我们创建一个 Snippet
模型, 用于存储代码片段.
开始编辑 snippets/models.py
文件. 注意:
优秀的编程实践都会对代码进行注释. 本教程的代码仓库有详细的注释,
在这里我们忽略它, 关注代码本身.
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
为 snippet
模型创建初始迁移, 并在第一次同步数据库
python manage.py makemigrations snippets
python manage.py migrate
The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as json. We can do this by declaring serializers that work very similar to Django’s forms. Create a file in the snippets directory named serializers.py and add the following.
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
The first part of the serializer class defines the fields that get serialized/deserialized. The create() and update() methods define how fully fledged instances are created or modified when calling serializer.save()
A serializer class is very similar to a Django Form class, and includes similar validation flags on the various fields, such as required, max_length and default.
The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The {‘base_template’: ‘textarea.html’} flag above is equivalent to using widget=widgets.Textarea on a Django Form class. This is particularly useful for controlling how the browsable API should be displayed, as we’ll see later in the tutorial.
We can actually also save ourselves some time by using the ModelSerializer class, as we’ll see later, but for now we’ll keep our serializer definition explicit.
用序列化(Serializers)工作
在我们深入之前, 我们需要熟练使用新的序列化类(Serializer class). 让我们进入Django命令行
python manage.py shell
导入相关依赖, 并创建一堆代码片段
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
我们已经有了一些 snippet
实例, 让我们看看如何将其中一个实例序列化
注: Model -> Serializer
serializer = SnippetSerializer(snippet)
serializer.data
# {'style': 'friendly', 'code': u'print "hello, world"\n', 'language': 'python', 'title': u'', 'linenos': False, 'id': 2}
现在我们将模型实例(model instance)转化成Python原生数据类型.
为了完成实例化过程, 我们将数据渲染成 json
.
注: Serializer -> JSON
content = JSONRenderer().render(serializer.data)
content
# '{"id":2,"title":"","code":"print \\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'
反序列化相似, 首先我们将流(stream)解析成Python原生数据类型…
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
…然后, 我们将Python原生数据恢复成正常的对象实例
注: json -> serializer
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([(u'title', u''), (u'code', u'print "hello, world"'), (u'linenos', False), (u'language', 'python'), (u'style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
可以看到, API和表单很相似. 当我们用我们的序列(serializer)写视图的时候, 相似性会更明显.
除了将模型模型实例(model instance)序列化外,
我们也能序列化查询集(querysets), 只需要添加一个序列化参数 many=True
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
我们的 SnippetSerializer
类复制了包含 Snippet
模型在内的很多信息. 如果能够简化我们的代码, 那是极好的.
和Django提供的 Form
类和 ModelFrom
类相同, REST
框架包含了
Serializer
类和 ModelSerializer
类.
让我们使用 ModelSerializer
重构我们的Serializer, 再次打开
snippets/serializers.py
, 重写 SnippetSerializer
类.
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
序列一个非常好的属性就是,
你可以通过打印序列实例的结构(representation)查看它的所有字段. 输入
python manage.py shell
打开Django shell, 尝试如下代码:
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
# id = IntegerField(label='ID', read_only=True)
# title = CharField(allow_blank=True, max_length=100, required=False)
# code = CharField(style={'base_template': 'textarea.html'})
# linenos = BooleanField(required=False)
# language = ChoiceField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'),...
# style = ChoiceField(choices=[('abap', 'abap'), ('algol', 'algol'),...
记住, ModelSerializer
类并没有做任何有魔力的事情, 他们只是一个创建
serializer
类的快捷方式.
- 一个自动确认字段的集合
- 简单默认实现的
create()
和update()
方法.
让我们看看, 如何使用我们的序列化类来实现一些API视图. 我们不使用 REST
框架的其他特性, 只是写一些常规的Django视图
编辑 snippets/views.py
:
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
我们的根API视图将支持列出所有存在的 snippets
, 或者创建一个新的
snippet
@csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
注意, 应为我们希望我们可以从没有 csrf token
的客户端 POST
数据到该视图, 我们需要标记该视图为 csrf_exempt
. 通常你不想这样做,
REST
框架视图使用比这个更明智的方式, 不过那不是我们现在的目的.
我们还需要一个视图对应单个 snippet
, 同时我们使用这个视图, 恢复,
更新, 删除 snippet
.
@csrf_exempt
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JsonResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
最后, 我们需要使用路由将这些视图对应起来, 创建 snippets/urls.py
文件
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
同时需要配置 tutorial/urls.py
, 添加我们的 snippet
应用的
URLs
.
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('snippets.urls')),
]
值得注意的事, 有一些边界情况我们没有进行处理, 如果我们发送不正确的
json
数据, 或者使用一个我们的视图没有处理的方法来请求,
我们会得到500的错误, “Server Error”.
退出当前命令行.
quit()
启动Django开发服务器
python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
...
Django version 1.11.11, using settings 'tutorial.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
我们可以打开另一个终端进行测试, 我们可以使用 curl
, 或者 httpie
.
Httpie
是一个使用Python编写的对用户友好的http客户端. 让我们安装它.
pip install httpie
我们可以获取 snippets
列表
http http://127.0.0.1:8000/snippets/
HTTP/1.0 200 OK
...
[
{
"code": "foo = \"bar\"\n",
"id": 1,
"language": "python",
"linenos": false,
"style": "friendly",
"title": ""
},
{
"code": "print \"hello, world\"\n",
"id": 2,
"language": "python",
"linenos": false,
"style": "friendly",
"title": ""
},
]
我们可以指定 id
获取响应 snippet
http http://127.0.0.1:8000/snippets/2/
HTTP/1.0 200 OK
...
{
"code": "print \"hello, world\"\n",
"id": 2,
"language": "python",
"linenos": false,
"style": "friendly",
"title": ""
}
相似地, 使用浏览器访问也可以获得相同的 json
数据.
到目前为止, 我们做得很好, 我们编写的序列化 API
和
Django's Forms API
比较相似, 同时编写了一些常规的Django视图.
我们的 API
没有做什么特殊的事情, 除了作出json响应外,
还有一些边缘事件没有处理, 但至少是一个还有点功能的 Web API
.
在教程的第2部分, 我们将介绍如何对我们的 API
进行改进.
从这节开始, 我们会接触到 REST
框架的核心.
让我们介绍一些基本构建组件.
REST framework
引入了一个 Request
对象, 它扩展了常规的
HttpRequest
, 并提供了灵活的请求解析. Request
对象的核心功能是
request.data
属性, 它和 request.POST
属性很相似, 但是它对
Web APIs
更加有用.
request.POST # 只处理表单数据. 仅用于 'POST' 方法.
request.data # 处理任意数据. 可以用于 'POST', 'PUT' and 'PATCH' 方法.
REST framework
也引入了 Response
对象,
它是一类用为渲染和使用内容协商来决定返回给客户端的正确内容类型的
TemplateResponse
.
return Response(data) # Renders to content type as requested by the client.
在你的视图中使用数字HTTP状态码并不总是易读的, 错误代码也容易被忽略.
REST framework
为每个状态码提供更明确的标识符, 例如 状态模块中的
HTTP_400_BAD_REQUEST
.
使用这种标识符代替纯数字标识符是一个不错的主意.
REST framework
提供两个装饰器.
@api_view
装饰器用在基于视图的方法.APIView
类用于基于类的视图上.
这些装饰器提供一些功能. 例如确保从你的视图中获取 Request
对象,
例如在 Response
对象中添加上下文.
同时还提供一些行为, 例如在合适的时候返回 405 Method Not Allowed
响应, 例如处理在访问错误输入的 request.data
时出现的 ParseError
异常.
Okay, 让我们使用这些新的组件去一些视图.
在 views.py
中不再需要 JSONResponse
类, 现在删除他们,
然后轻微地重构我们的视图
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
我们的实例视图是前面的修改版, 更简洁, 和我们使用的 Form API
很相似,
同时使用了命名状态码, 让响应代码意义更明显.
views.py
中独立 snippet
的视图:
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
这对我们来说应该非常熟悉, 跟常规的Django视图没什么区别.
注意, 我们不再明确指定请求或响应的上下文类型. request.data
可以处理的 json
格式的请求, 同样也可以处理其他格式. 同样的, 我们允许
REST
框架将响应对象的数据渲染成正确的内容类型返回给客户端.
我们的响应不再是单一的内容格式, 根据这个事实,
我们可以在API尾部添加格式后缀, 格式后缀给我们一个参考的格式,
这意味着我们的API可以处理 http://example.com/api/items/4.json.
这样的URLs.
在视图中添加一个 format
关键字参数, 像这样
def snippet_list(request, format=None):
和
def snippet_detail(request, pk, format=None):
现在更新 snippets/urls.py
文件, 在已经存在的URL中添加一个
format_suffix_patterns
集合.
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
我们不必添加额外的URL模式, 但是它给我们一个简单, 清楚的方式指定特定的格式.
继续像 tutorial part 1
中一样, 通过命令行测试 API, 一切都相当类似,
同时我们可以很好地处无效请求产生的错误.
我们可以像之前一样, 获得 snippets
列表
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
我们可以通过使用 Accept
响应头控制返回的响应的格式.
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
或者在URL后添加格式后缀:
http http://127.0.0.1:8000/snippets.json # JSON 后缀
http http://127.0.0.1:8000/snippets.api # 可浏览的 API 后缀
同样的, 我们可以使用 Content-Type
头控制我们请求的格式.
# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"id": 3,
"title": "",
"code": "print 123",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
{
"id": 4,
"title": "",
"code": "print 456",
"linenos": false,
"language": "python",
"style": "friendly"
}
如果你使用 --debug
参数, 你可以看到请求头中的请求类型.
使用浏览器打开 http://127.0.0.1:8000/snippets/.
由于 API
响应类型是根据客户端的请求进行选择的, 因此, 当使用 web
浏览器请求的时候, 默认会使用 HTML
格式来表示资源. 这允许 API
返回一个完整的浏览器可视的 HTML
表示.
拥有一个浏览器可视化的 API
是非常有用的. 这会使得开发和使用 API
变的极为简单. 这也让其他开发者更容易查看和使用你的 API
.
查看 browsable
api
主题获取更更多关于 browsable API
的信息, 比如 特性, 定制.
在教程的第3部分, 我们将开始使用基于类的视图(CBV), 并介绍如何使用通用的视图来减少代码量.
我们也可以使用基于类的视图编写我们的 API
, 如我们所见,
这是一个有利的模式, 允许我们重用共同的功能,
使我们的代码不重复
重构 views.py
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
到目前为止, 一起都好. 它和之前的情况非常类似, 但我们可以更好的区分不同的
HTTP 方法, 我们需要继续更新 views.py
中的实例视图.
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
同时, 我们需要用基于类的视图的方式, 重构 snippets/urls.py
.
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Okay, 重构完成, 再运行开发服务器, 一切都和之前一样正常工作.
使用基于类的视图的最大的好处就是, 允许我们快速的创建可复用的行为.
我们一直使用的 create/retrieve/update/delete
操作和我们创建的任何后端模型 API 很相似. 这些普遍的共同行为在 REST
框架的 mixin
类中实现.
让我们看看如何使用 mixin
类编写 views.py
模块.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
我们话一些时间测试这里发生了什么, 我们使用 GenericAPIView
创建我们的视图, 同时加入 ListModelMixin
和 CreateModelMixin
.
基础类提供核心功能, mixin
类提供 .list()
和 .create()
动作.
然后我们绑定 get
和 post
方法到合适的动作, 到目前为止,
已经变得足够简单.
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
相似地. 我们使用 GenericAPIView
类提供核心功能, 添加 mixins
提供
.retrieve()
, .update()
and .destroy()
动作.
我们使用 mixin
类使用比之前较少的代码编写视图, 但我们可以更进一步.
REST
框架提供一个已经混入一般视图的集合, 我们可以用他们进一步缩减
views.py
模块.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Wow, 我们的代码看起来如此简介, 如此的Django
接下来我们学习 part 4 of the tutorial
, 我们将学到如何为我们的
API
处理授权(authentication
)和权限(permissions
)
当前, 我们的 API
没有限制, 谁都可以编辑或删除 snippets
.
我们需要一些更高级的行为来确保:
- 代码片段总是与创建者联系在一起
- 只有授权用户才能创建
snippets
- 只有
snippet
创建者可以更新或者删除它 - 未授权的请求只有只读权限.
我们需要对我们的 Snippet
模型类做一些修改. 首先, 添加两个字段,
一个用来代表代码片段的创建者, 另一个用来储存高亮显示的HTML代码.
修改 models.py
添加字段到 Snippet
模型中.
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
同时, 我们需要确保, 模型在保存的时候, 使用 pygments
代码高亮库填充
highlighted
字段.
我们需要额外导入:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
添加 .save()
方法到模型类
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
当我们完成这些, 我们需要更新我们的数据库表结构, 正常情况下, 我们创建数据库迁移(database migration), 但是在本教程中, 我们只需要删除原来的数据库, 重新创建.
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你需要创建一些不同的用户, 用来测试 API, 最快的方式是使用
createsuperuser
命令
python manage.py createsuperuser
现在我们已经创建了一些用户, 我们最好将用户添加到我们的 API,
我们很容易创建一个新的序列. 在 serializers.py
文件中添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
因为在用户模型中 snippets
是一个相反的关系, 使用 ModelSerializer
类, 默认不会包含它, 所以我们需要手动为用户序列添加这个字段.
我们还需要添加两个视图到 views.py
中. 我们为用户添加只读视图,
因此我们使用基于视图的一般类 ListAPIView
和 RetrieveAPIView
.
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
确保导入了 UserSerializer
类
from snippets.serializers import UserSerializer
最后, 我们需要修改 URL
配置, 添加这些视图到 API
中,
添加一下内容到 urls.py
中.
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
现在, 如果我们创建一个代码片段, 我们没法将用户和创建的 snippet
实例联系起来. 虽然用户不是序列表示的部分, 但是代表传入请求的一个属性
我们通过重写 snippet
视图的 .perform_create()
方法来处理这个问题. 它允许我们修改如何保存实例,
处理任何请求对象的信息或者请求链接的信息.
在 SnippetList
视图类下添加如下方法
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在, 我们序列的 create()
方法将会传入一个有效请求数据的 owner
字段
现在, snippets
和创建他们的用户已经建立了联系, 更新我们的
SnippetSerializer
来表示用户. 在 serializers.py
的序列定义中添加一下字段:
owner = serializers.ReadOnlyField(source='owner.username')
同时, 确保将 ``’owner’`` 添加到 ``Meta`` 类的字段中.
这个字段会做一些有趣的事情. source
参数控制哪个属性被用作于一个字段,
并可以指向 serialized
实例的任何属性.
它也能像上面一样使用点标记(.
), 这种情况下它会横贯给定的所有属性,
就像我们使用django模板语言一样.
我们添加的字段是无类型的 ReadOnlyField
类, 与其他类型的字段, 例如
CharField
, BooleanField
等等…相比, 无类型的 ReadOnlyField
总是只读的, 用于序列化表示,
但不能用于数据反序列化的时候用来更新模型实例. 这里我们也可以使用
CharField(read_only=True)
.
现在,用户已经和代码片段联系起来, 我们需要确保, 只有授权的用户可以创建, 更新, 删除代码片段
REST
框架包含许多权限类, 可以用来实现视图的访问权限. 这种情况下,
我们需要 IsAuthenticatedOrReadOnly
来确保授权请求获得读写权限,
未经授权的请求只有只读权限.
首先, 在视图模块中引入如下代码:
from rest_framework import permissions
然后在 SnippetList
和 SnippetDetail
视图类中添加如下属性.
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
如果你打开浏览器操控可浏览的API, 你将不再有创建新的代码片段的权限. 为此, 我们需要以用户身份登录.
我们添加一个登录视图, 编辑项目级别的 URLconf
: urls.py
文件
添加导入语句
from django.conf.urls import include
文件末尾, 添加一个包含登录和登出视图的url样式
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls')),
]
r'^api-auth/'
可以使用你想要的URL
现在, 如果再次打开浏览器, 刷新页面, 你将可以看到一个 Login
链接在页面的右上角. 现在可以使用已经创建的用户登录, 创建代码片段.
一旦您创建了一些代码片段, 访问 ‘/users/’ 端点, 你会注意到在每个用户的
snippets
字段, 会显示跟用户有关的 snippets
id.
虽然我们想让所有人看到代码片段, 但同时也要确保只有创建代码片段的用户可以更新或删除它.
我们需要创建自定义权限.
在 snippets
应用中, 创建一个新的文件, permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
现在, 通过编辑 SnippetDetail
视图类中的 permission_classes
属性,
我们可以添加自定义权限到我们的 snippet
实例端点
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
确保导入 IsOwnerOrReadOnly
类.
from snippets.permissions import IsOwnerOrReadOnly
现在, 如果你再次使用浏览器, 你会发现只有你登录与创建代码片段一致的用户,
你才有权限使用 'DELETE'
and 'PUT'
动作.
由于现在 API 有权限集合, 在我们需要编辑任何 snippets
的时候,
需要认证我们的请求, 我们没有设置其他任何认证类(authentication
classes),
默认情况下只有 SessionAuthentication
和 BasicAuthentication
.
当我们通过浏览器进行交互时, 我们可以登录, 浏览器会话(session)将为请求提供认证.
如果我们以编程的方式使用 API, 我们需要为每个请求提供明确的 认证凭证
.
如果我们尝试在没有认证的情况下创建 snippet
, 我们会获得一个 error.
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过提供之前创建的用户的用户名和密码, 来创建 snippet
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
我们的 API
已经具有一个相当精细的权限集合,
同时为系统用户和他们创建的 snippets
提供了端点.
在教程的第5部分, 我们将介绍如何为高亮的 snippets
创建一个HTML端点,
将所有内容联系起来. 同时为系统中的关系使用超链接提高我们 API
的凝聚力.
目前, 我们用主键代表我们API之间的关系. 在这节里面, 我们会用超链接改善API之间的关系.
现在我们有 'snippets'
和 'users'
的端点,
但是没有为我们的API设置单独的入口. 我们使用基于方法的常规视图和
@api_view
装饰器创建一个入口端点. 在文件 snippets/views.py
中添加:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
在这里我们需要注意两点, 首先我们使用 REST
框架的 reverse
方法限定返回的 URLs
, 其次, URL格式使用方便的名字作标识符, 稍后会在
snippets/urls.py
中声明.
还有一个明显的事情就是我们的 pastebin API
缺乏代码高亮的端点.
与我们其他的API端点不通, 我们不想使用 JSON
, 而只使用 HTML
显示.REST
框架提供了两种渲染方式, 一种是用模板渲染,
另一种是用预渲染 HTML
, 在这个端点, 我们使用第二种渲染方式.
另一个需要我们思考的是, 在创建高亮代码视图的时候, 高亮视图在通用视图中是不存在的. 我们不会返回一个对象实例, 而是返回对象的一个属性.
我们不使用通用视图, 而是通过基础类, 在 snippets/views.py
中创建我们自己的 .get()
方法
from rest_framework import renderers
from rest_framework.response import Response
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
像往常一样, 我们需要添加新的视图到 URL
配置中, 文件
snippets/urls.py
url(r'^$', views.api_root),
然后为高亮 snippet
添加一个url样式.
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
在Web API设计中, 处理实体之间的关系是一项非常有挑战的事情. 代表一种关系可以有很多种方式
- 使用主键
- 在实体间使用超链接
- 在相关的实体上使用唯一的
slug
- 使用相关实体的默认字符串
- 在父表述使用嵌套的实体
- 其他自定义的表述
REST
框架支持以上所有的方式, 正向或反向关系均可以使用,
或者像使用一般外键一样使用自定义的管理方式.
在这种情况下, 我们在实体间使用超链接方式. 为了达到目的,
我们将修改我们的序列(serializers
), 扩展
HyperlinkedModelSerializer
代替 ModelSerializer
.
HyperlinkedModelSerializer
和 ModelSerializer
有以下几点不同:
- 默认不包括
id
字段 - 它包括一个
url
字段, 使用HyperlinkedIdentityField
- 关系使用
HyperlinkedRelatedField
代替PrimaryKeyRelatedField
我们可以快速的将存在的序列重写成超链接的方式, 文件
snippets/serializers.py
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner',
'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
注意, 我们还新添加了一个 'highlight'
字段. 这个字段的类型和 url
字段类型一致, 只是它指向 'snippet-highlight'
端点, 而不是
'snippet-detail'
因为我们已经配置了 URLs
后缀, 比如 '.json'
, 同时我们需要在
highlight
字段中指明后缀, .html
如果我们要使用超链接API, 我们必须确保对 URL
模式进行命名,
让我们看看哪些链接需要命名
- 根API指向
'user-list'
和'snippet-list'
. snippet
序列包括一个指向'snippet-highlight'
的字段.user
序列包括一个指向'snippet-detail'
的字段.- 我们的
snippet
和user
序列包括 ‘url’ 字段默认指向'{model_name}-detail'
, 当前情况指向'snippet-detail'
和'user-detail'
.
命名加入 URL 配置之后, snippets/urls.py
应该是下面这样子.
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
用户和代码片段的列表视图可能会返回大量的实例, 所以我们要对返回的结果进行分页, 并允许客户端访问每个单页.
我们可以改变默认的列表样式来使用分页, 轻微的修改
tutorial/settings.py
文件, 添加如下配置
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
REST framework
的所有设置都是在 settings
中 REST_FRAMEWORK
字典中的. 它可以帮我们区分项目中的其他配置.
同时, 我们也可以自定义分页的样式, 在这里, 我们使用默认方式.
如果我们打开浏览器, 并访问可浏览的 API
,
你会发现你可以使用下面的链接使用 API
.
你也可以看到 snippet
实例的 'highlight'
链接,
这些链接会返回高亮的 HTML
代码.
在教程的第6部分, 我们会介绍怎么使用 ViewSets
和 Routers
通过更少的代码, 实现我们的 API
.
REST
框架包含一个 ViewSets
的抽象,
它可以让开发者将精力集中在构建API的状态和交互上, 同时帮助开发者,
基于共同约定, 自动处理 URL 构建.
ViewSet
类几乎和 View
类一样, 除了它提供的 read
或者
update
操作, 而不是像 get
或 put
一样的方法.
一个 ViewSet
类在它被实例化成一个视图集合的最后时刻,
通过一个处理复杂 URL 配置的 Router
类绑定, 且只绑定一个方法集合.
首先使用单个 UserViewSet
视图重构 UserList
和 UserDetail
视图.
文件 snippets/views.py
from rest_framework import viewsets
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
这里我们使用 ReadOnlyModelViewSet
类自动提供默认的 'read-only'
操作. 我们需要像使用常规视图一样, 设置 queryset
和
serializer_class
属性, 但是我们不再需要为两个分开的类提供相同的信息.
接下来替换 SnippetList
, SnippetDetail
and SnippetHighlight
视图类.
from rest_framework.decorators import detail_route
from rest_framework.response import Response
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
@detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
这一次我们使用了 ModelViewSet
类获得默认的完整的读写操作.
注意, 我们同时使用了 @detail_route
装饰器, 用于创建一个自定义动作,
即 highlight
. 这个装饰器可以用于添加任何不适合
create/update/delete
方式的自定义端点.
使用 @detail_route
装饰器自定义的动作默认会响应 GET
请求.
如果我们需要动作响应 POST
请求, 我们可以使用 methods
参数.
自定义动作的默认 URLs
取决于它们的名字. 如果你想改变url构建方法,
你可以在使用装饰器的时候传入 url_path
关键字参数.
处理方法, 只会按照我们的 URL 配置对相应方法进行绑定. 我们为我们的 ViewSets 显示地创建一个视图集合, 来看看发生了什么.
snippets/urls.py
文件, 我们绑定我们的 ViewSet
类到一组具体的视图.
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
'get': 'list'
})
user_detail = UserViewSet.as_view({
'get': 'retrieve'
})
注意我们如何从每个 ViewSet
类,
通过绑定http方法到响应的动作来创建多个视图.
现在, 我们将我们的资源绑定到了具体的视图, 我们可以像往常一样将我们的视图注册到url配置中
urlpatterns = format_suffix_patterns([
url(r'^$', api_root),
url(r'^snippets/$', snippet_list, name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
url(r'^users/$', user_list, name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])
因为我们使用 ViewSet
代替 View
, 实际上我们不需要自己设计 URL
配置. 我们可以通过 Router
类, 将资源(resources
),
视图(views
), urls 自动联系起来.
我们只需要使用一个路由注册合适的视图集合.
重写 snippets/urls.py
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from snippets import views
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
# The API URLs are now determined automatically by the router.
urlpatterns = [
url(r'^', include(router.urls))
]
使用 router
注册的视图集合提供一个 urlpattern. 包括两个参数 -
视图的URl前缀和视图集合本身.
我们使用的默认 DefaultRouter
类也会自动为我们创建 API 根视图.
现在我们可以从 views
模块中删除 api_root
方法
viewsets
是一个非常有用的抽象. 它可以确保 URL
原型和你的 API
保持一致, 最大限度的减少代码量, 允许你将精力放在 API 的交互和表示上,
而不是放在编写 URL conf
上.
这并不意味在所有地方都要使用 viewsets
.
在使用基于类的视图和基于函数的视图时, 需要进行权衡. 使用 viewsets
没有单独构建 views
明确.
在教程第7部分, 我们将介绍, 如何添加一个 APP schema
,
并使用客户端库或命令行工具与我们的 API
进行交互.
schema
是一种机器可读的文档, 用于描述可用的API端点, URLS,
以及他们支持的操作.
schema
可以用于自动生成文档,
也可以用于驱动可以与API交互的动态客户端库.
为了提供 schema
支持, REST
框架使用 Core
API
Core API
是用于描述 APIs
的文档规范.
它可以用来提供内部可用端点内部表示格式和API暴露的可能的交互.
它可以用于服务端或客户端.
当用于服务端时, Core API
允许API支持呈现 schema
或渲染超媒体格式.
当用于客户端, Core API
允许动态驱动的客户端库与任何支持 schema
或超媒体格式的 API
交互.
REST framework
支持明确定义的 schema
视图, 或自动生成的
schemas
. 由于我们使用 ViewSets
和 Routers
,
我们可以很简单的自动生成 schema
你需要安装 coreapi
pip install coreapi
在 URL 配置中包含一个自动生成的 schema
视图.
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title='Pastebin API')
urlpatterns = [
url(r'^schema/$', schema_view),
...
]
如果你使用浏览器访问 API 根节点, 在选项中, 你可以看到 corejson
选项变成可用的状态.

django-rest-framework-2
我们也可以使用命令行, 通过在 Accept
请求头中指定期望的内容类型, 请求
schema
.
$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json
{
"_meta": {
"title": "Pastebin API"
},
"_type": "document",
...
默认的输出风格使用的Core JSON编码.
其他的 schema
格式, 比如 Open API
(以前称作Swagger), 同样支持.
现在, 我们的 API 暴露一个 schema
端点,
我们可以使用动态客户端库与API交互. 为了证明这点, 我们是用 Core API
命令行客户端
命令行客户端需要使用 coreapi-cli
包
pip install coreapi-cli
通过命令行检查, coreapi-cli
是否可用
$ coreapi
Usage: coreapi [OPTIONS] COMMAND [ARGS]...
Command line client for interacting with CoreAPI services.
Visit http://www.coreapi.org for more information.
Options:
--version Display the package version number.
--help Show this message and exit.
Commands:
...
首先, 我们使用命令行客户端加载 API schema
$ coreapi get http://127.0.0.1:8000/schema/
<Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
highlight(id)
list()
read(id)
}
users: {
list()
read(id)
}
我们还没有认证, 所以我们只能看到只读端点, 符合我们设计的 API
权限
让我们尝试使用命令行客户端, 列出已经存在的 snippets
$ coreapi action snippets list
[
{
"url": "http://127.0.0.1:8000/snippets/1/",
"id": 1,
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world!')",
"linenos": true,
"language": "python",
"style": "friendly"
},
...
有些API端点依赖命名参数. 比如, 我们要获取指定 snippet
的高亮 HTML,
需要提供一个 id
.
$ coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Example</title>
...
如果我们想要创建, 编辑, 删除 snippets
, 我们需要认证一个有效的用户.
这种情况下, 我们只使用基本身份验证.
将 <username>
和 <password>
替换成真是的用户名和密码.
$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
Added credentials
127.0.0.1 "Basic <...>"
现在, 如果我们重新获取 schema
, 我们可以看到所有的可用交互的集合.
$ coreapi reload
Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
create(code, [title], [linenos], [language], [style])
delete(id)
highlight(id)
list()
partial_update(id, [title], [code], [linenos], [language], [style])
read(id)
update(id, code, [title], [linenos], [language], [style])
}
users: {
list()
read(id)
}
现在我们可以和这些端点交互. 比如, 创建一个新的 snippet
:
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
"url": "http://127.0.0.1:8000/snippets/7/",
"id": 7,
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world')",
"linenos": false,
"language": "python",
"style": "friendly"
}
删除 snippet
coreapi action snippets delete --param id=7
除了使用命令行客户端, 开发者也可以使用客户端库与您的API进行交互.
Python客户端第一个可用的, 不久之后就会发布 Javascript
客户端库.
有关自定义 schema
生成和使用 Core API
客户端库,
你可以参考完整的文档.
使用很少的代码, 我们拥有了一个完整的可浏览的 pastebin Web API
,
它包含一个 schema-driven
客户端库, 完整的身份认证,
对象级权限和多格式渲染器.
我们走过了设计过程的每一步, 看到了如何使用常规的Django视图进行定制.
你可以在GitHub上查阅最终的代码 tutorial code, 或者在 the sandbox 中进行尝试.
到这里, 我们已经完成了教程. 如果你想跟多的参与到 REST framework
项目, 你可以使用以下几种方式:
- 在 GitHub
上进行审查, 提交问题, 发出
pull requests
. - 加入 REST framework discussion group, 帮助构建社区.
- 在Twitter上关注 作者, 并发送
hi
.
Now go build awesome things.
快速开始¶
创建一个简单的API, 支持管理员用户查看, 编辑用户和组.
# 创建项目目录
mkdir tutorial
cd tutorial
# 创建虚拟环境
Create a virtualenv to isolate our package dependencies locally
virtualenv env
# Windows上使用 `env\Scripts\activate`
source env/bin/activate
# 安装 Django 和 Django REST framework
pip install django
pip install djangorestframework
# 创建 tutorial 项目, 和 quickstart 应用
django-admin.py startproject tutorial . # 注意字符 '.'
cd tutorial
django-admin.py startapp quickstart
cd ..
同步数据库
python manage.py migrate
创建 admin
用户, 密码为 password123
.
python manage.py createsuperuser --email admin@example.com --username admin
准备工作完毕, coding…
首先定义一些序列化类, 创建一个新模块
tutorial/quickstart/serializers.py
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
本例中, 我们使用超链接关系 HyperlinkedModelSerializer
.
你还尅使用主键和其他各种关系, 但超链接是好的 RESTful
设计
打开 tutorial/quickstart/views.py
.
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from tutorial.quickstart.serializers import UserSerializer, GroupSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupSerializer(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
我们将共同的行为写在 ViewSets
类中, 而不是重复写多个视图
在我们需要的时候, 可以很容易的区分每个视图, 但使用viewsets可以很好地, 简洁地组织视图
tutorial/urls.py
from django.conf.urls import url, include
from rest_framework import routers
from tutorial.quickstart import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
我们使用 viewsets
代替 views
, 所以我们可以使用 router
类便捷的注册 view
为我们的 API
自动生成 URL配置.
Again, if we need more control over the API URLs we can simply drop down to using regular class-based views, and writing the URL conf explicitly.
Finally, we’re including default login and logout views for use with the browsable API. That’s optional, but useful if your API requires authentication and you want to use the browsable API.
添加 'rest_framework'
到 INSTALLED_APPS
中. 配置模块
tutorial/settings.py
INSTALLED_APPS = (
...
'rest_framework',
)
测试 API, 从命令行启动服务器.
python manage.py runserver
使用 curl
进行测试
➜ ~ curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
[
{
"url": "http://127.0.0.1:8000/users/2/",
"username": "tom",
"email": "tom@example.com",
"groups": []
},
{
"url": "http://127.0.0.1:8000/users/1/",
"username": "admin",
"email": "admin@example.com",
"groups": []
}
]
直接通过浏览器访问 http://127.0.0.1:8000/users/

django-rest-framework
右上角登录可以进行编辑
Django REST framework¶
当前版本 version 3
http://www.django-rest-framework.org/
Django REST framework
用于构建 web api
, 具有强大,灵活的特性.
REST framework requires the following:
- Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
- Django (1.10, 1.11, 2.0)
可选包:
- coreapi (1.32.0+) - Schema generation support.
- Markdown (2.1.0+) - Markdown support for the browsable API.
- django-filter (1.0.1+) - Filtering support.
- django-crispy-forms - Improved HTML display for filtering.
- django-guardian (1.1.1+) - Object level permissions support.
通过 pip
安装, 需要的可选包也可以一并安上
pip install djangorestframework
pip install markdown # Markdown support for the browsable API.
pip install django-filter # Filtering support
将 ‘rest_framework’ 添加到项目 INSTALLED_APPS
设置中.
INSTALLED_APPS = (
...
'rest_framework',
)
如果你打算使用可视化的 API 或者 REST framework 的登入登出视图.
添加如下配置到 urls.py
文件中.
# url路径自行修改
urlpatterns = [
...
url(r'^api-auth/', include('rest_framework.urls'))
]
# 安装 djangorestframework
pip3 install djangorestframework
# 创建项目
django-admin startproject resttest
cd resttest
# 同步数据库
python3 manage.py makemigrations
python3 manage.py migrate
REST framework API
全局配置均配置在名为 REST_FRAMEWORK
的字典中.
添加如下配置到 settings.py
中
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
注册APP
INSTALLED_APPS = (
...
'rest_framework',
)
完整 urls.py
内容
from django.conf.urls import url, include
from django.contrib.auth.models import User
from rest_framework import routers, serializers, viewsets
# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'is_staff')
# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
启动server
python3 manage.py runserver
访问 http://127.0.0.1:8000 即可看到我们的 users
API.
Flask¶
Flask tips¶
CORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。
web开发进入前后端分离的阶段
后端往往只需要吐api数据就ok
一般纯的api接口需要考虑跨域访问问题
下面是简单的跨域访问装饰器在flask中的实现示例
from functools import wraps
from flask import make_response
def allow_cross_domain(fun):
@wraps(fun)
def wrapper_fun(*args, **kwargs):
rst = make_response(fun(*args, **kwargs))
rst.headers['Access-Control-Allow-Origin'] = '*'
rst.headers['Access-Control-Allow-Methods'] = 'PUT,GET,POST,DELETE'
allow_headers = "Referer,Accept,Origin,User-Agent"
rst.headers['Access-Control-Allow-Headers'] = allow_headers
return rst
return wrapper_fun
@app.route('/hosts/')
@allow_cross_domain
def domains():
pass
安装¶
Flask依赖一些额外的库,比如Werkzeug and Jinja2. Werkzeug是一个用于开发部署Web应用以及多样服务的标准python接口的WSGI工具箱.
推荐使用virtualenv环境
前提
Python 2.x 2.6 ~
Python 3.x 3.3 ~
sudo pip install Flask
快速开始¶
hello.py
,不能使用flask.py
做文件名,会与模块冲突
# 导入Flask
from flask import Flask
# app实例是我们的WSGI应用
app = Flask(__name__)
# 使用route()装饰器告诉Flask,什么url将触发函数
@app.route('/')
def hello_world():
return 'hello world'
运行hello.py
使用 flask 命令或者 python -m flask run
.
运行之前需要声明FLASK_APP
环境变量,如果使用Windows,则要使用set
代替export
命令行执行
$ export FLASK_APP=hello.py
$ flask run
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
或者 python -m flask:
$ export FLASK_APP=hello.py
$ python -m flask run
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
使用上述命令会启动一个简单的内置web server
,该Server仅用于测试.
生产环境部署可以参考 Deployment Options.
默认,flask启动在调试模式下,如果需要监听所有IP,使用如下命令
flask run --host=0.0.0.0
Have another debugger in mind? See Working with Debuggers.
route()
装饰器用于绑定函数到URL
示例
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello, World'
同时可以将URL的一部分设置成动态以及附加多个规则在一个函数上
我们可以使用一些特殊标记,将变量添加到URL里面.
比如使用<变量名>
,这种格式,将会作为关键字参数传递给函数.还可以使用一个转换器<转换器:变量名>
@app.route('/user/<username>')
def show_user_profile(username):
# show the user profile for that user
return 'User %s' % username
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
支持的转换器
string | accepts any text without a slash (the default) |
---|---|
int | accepts integers |
float | like int but for floating point values |
path | like the default but also accepts slashes |
any | matches one of the items provided |
uuid | accepts UUID strings |
Flask URL规则基于Werkzeug的路由模块.这个模块背后的理念来自Apache以及早期的HTTP服务.
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
上面两个示例看起来很相似,区别在于结尾的/
.第一种情况,是末尾带有斜杠的规范化URL.它类似文件系统中的一个文件夹,如果不带/
访问,Flask将自动跳转到尾端带斜线的规范化的URL.
第二种情况,url定义尾端不带/
,像类Unix系统上的文件的路径名,如果带/
访问,将会返回404
错误
这种行为,是相对url也能继续工作,符合Apache和其他服务器的工作模式。同时,url将保持独特,这有助于搜索引擎避免两次索引相同的页面。
url_for()
接受一个函数名作为第一个参数
>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
... print url_for('index')
... print url_for('login')
... print url_for('login', next='/')
... print url_for('profile', username='John Doe')
...
/
/login
/login?next=/
/user/John%20Doe
默认情况下,路由仅应答GET
请求,可以通过装饰器route() 中的methods
参数来改变.
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
do_the_login()
else:
show_the_login_form()
当GET
请求存在的时候,HEAD
会被自动添加,并按照 HTTP
RFC
的要求来处理,我们并不需要自己处理.
HTTP方法简单介绍
GET
请求指定页面信息,并返回实体主体
HEAD
仅获取报头,而不关注页面内容
POST
想指定资源提交数据进行处理请求(比如提交表单或上传文件).数据被包含在请求体中,POST请求可能会导致新的资源的建立,或已有资源的修改
PUT
从客服端向服务器传送的数据取代指定的文档的内容
DELETE
请求服务器删除指定的页面
CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS
允许客户端查看服务器的性能。
TRACE
回显服务器收到的请求,主要用于测试或诊断。
动态web应用同时也需要静态文件.
生成静态文件的URLs,使用
url_for('static', filename='style.css')
文件被存放在磁盘上 static/style.css
.
Flask 会自动帮我们配置 Jinja2 template 引擎.
使用render_template()
方法呈现一个模板,我们只需要提供模板名字,以及我们需要呈现的变量的名称作为关键字参数传递给模板引擎.
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
Flask将在模板目录查找对应模板.
1: 一个py
文件
/application.py
/templates
/hello.html
2: 一个包
/application
/__init__.py
/templates
/hello.html
Jinja2模板文档 Jinja2 Template Documentation
模板示例
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
在模板内还可以访问
`request
<http://flask.pocoo.org/docs/0.12/api/#flask.request>`__,
`session
<http://flask.pocoo.org/docs/0.12/api/#flask.session>`__ ,
`g
<http://flask.pocoo.org/docs/0.12/api/#flask.g>`__
[1] 对象以及
`get_flashed_messages()
<http://flask.pocoo.org/docs/0.12/api/#flask.get_flashed_messages>`__
方法.
web应用与客户端发送的数据进行交互是至关重要的.在Flask
中,这些信息由全局对象request
提供.如果你有一些使用python的经验,你会好奇,为什么这个对象是全局的,
为什么Flask
还能保证线程安全, 答案是环境作用域
如果你想理解其工作机制,以及如何利用环境变量实现自动化测试,阅读这节,否则跳过它.
Flask
中某些对象是全局的, 但不是通常的那种.
这些对象实际上是特定环境的局部对象的代理. 虽然很拗口,但是其实很好理解.
想象一下处理线程的上下文. 一个请求到来,
web服务器生成一个新线程(或者其他东西, 只要这个底层对象可以胜任并发系统,
而不仅仅是线程). 当Flask
开始内部请求处理时, 它认定当前线程活动,
并绑定当前应用和WSGI环境到此线程. 它的实现方法很巧妙,
能保证一个应用程序调用另一个应用程序时不会出现问题.
这对你意味着什么?
基本上你可以完全忽略这种情况,除非你要做类似单元测试的事情.你会发现一段依赖请求对象的代码会因为没有请求对象而无法正常运行.
解决方案是, 自行创建一个请求对象, 并且把它绑定到环境中.
单元测试的最简单的解决方案是使用
`test_request_context()
<http://flask.pocoo.org/docs/0.12/api/#flask.Flask.test_request_context>`__
进行环境管理. 结合with
声明,绑定一个测试请求, 这样才可以与之交互.
例如:
from flask import request
with app.test_request_context('/hello', method='POST'):
# now you can do something with the request until the
# end of the with block, such as basic assertions:
assert request.path == '/hello'
assert request.method == 'POST'
另一种可能是, 传递整个WSGI环境给
`request_context()
<http://flask.pocoo.org/docs/0.12/api/#flask.Flask.request_context>`__方法:
from flask import request
with app.request_context(environ):
assert request.method == 'POST'
宽泛介绍一些常用操作,
详情参见API
,`request
<http://flask.pocoo.org/docs/0.12/api/#flask.request>`__
先导入模块
from flask import request
当前请求方法可以通过method
属性来获取.
访问表单数据(PUT或POST请求提交的数据)可以使用``form``属性.
下面是使用前面两个属性的完整实例:
@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
当访问的form
属性key
不存在的时候会发生什么?
这种情况会抛出KeyError
异常.
你可以像捕捉标准KeyError
一样捕捉它. 如果你不这么做,
会显示一个HTTP 400错误页面. 所以, 大多情况下不需要处理这个问题.
访问URL中提交的参数(?key=value
)可以使用args
属性.
searchword = request.args.get('key', '')
我们建议使用get
来访问URL
参数或捕捉KeyError
,
因为用户可能会修改URL, 同时向他们呈现一个400错误是不友好的.
获取请求对象完整的方法和属性可以查阅
`request
<http://flask.pocoo.org/docs/0.12/api/#flask.request>`__文档.
用Flask
上传文件很简单,
只需要在form
表单中设置enctype="multipart/form-data"
属性,
没有该属性,浏览器不会传输文件.
上传的文件存储在内存或者本地文件系统的一个临时位置.
你可以使用请求对象中的files
属性访问它们.
每个上传的文件都会存储在这个字典里面,
它表现为一个标准的Pythonfile
对象,
但它还有一个save()
方法,
这个方法允许你将文件存储在服务器文件系统上. 下面是一个🌰:
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
...
如果你想知道上传的文件,在客户端上的名字, 可以使用filename
属性,
但是这个值可以伪造, 所以不要完全信任这个值.
如果你要以客户端提供的文件名将文件存放在服务器上,
那么请把它传递给Werkzeug提供的`secure_filename()
<http://werkzeug.pocoo.org/docs/utils/#werkzeug.utils.secure_filename>`__方法
from flask import request
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename))
...
其他一些比较好的🌰, Uploading Files模式
你可以使用**``cookies``**属性访问Cookies,
使用响应对象的**``set_cookie``**方法设置Cookies.
请求对象的
`cookies
<http://flask.pocoo.org/docs/0.12/api/#flask.Request.cookies>`__
属性是一个内容由客户端提供的包含整个Cookies的字典,
如果你想要使用sessions
,
不要直接使用Cookies,使用Sessions,
在Flask中已经处理了一些Cookies安全细节.
读取Cookies
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.
存储cookies
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
注意, Cookies是设置在响应对象上的, 由于视图函数通常只返回字符串,
之后由Flask
转换为相应对象.
如果你要显示地转换,可以使用`make_response()
<http://flask.pocoo.org/docs/0.12/api/#flask.make_response>`__函数,
然后再进行修改.
有时候你想在响应对象不存在的时候设置cookie, 可以使用 Deferred Request Callbacks 模式.
也可以阅读文档 About Responses.
你可以使用
`redirect()
<http://flask.pocoo.org/docs/0.12/api/#flask.redirect>`__
函数将用户定向到其他位置; 终止用户请求,返回错误代码使用
`abort()
<http://flask.pocoo.org/docs/0.12/api/#flask.abort>`__
函数:
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
这是一个没有意义的🌰, 用户访问主页, 将重定向到一个不能访问的页面(401意味着禁止访问), 但是它展示了重定向是如何工作的.
默认情况下, 错误代码会显示一个黑白的错误页面, 如果你想定制错误页面,
你可以使用
`errorhandler()
<http://flask.pocoo.org/docs/0.12/api/#flask.Flask.errorhandler>`__
装饰器:
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
注意, 404在调用
`render_template()
<http://flask.pocoo.org/docs/0.12/api/#flask.render_template>`__
之后, 这告诉Flask, 该页面的代码是404(页面不存在). 默认页面代码为200,
表示一切ok.
更多细节查看 Error handlers .
视图函数的返回值会被自动转换为一个响应对象. 如果返回值是一个字符串,
它将被转换为字符串为响应主体, 200 ok
状态码,
MIME
类型为text/html
的响应对象. Flask
将返回值转换为响应对象的逻辑如下:
- 如果返回的是一个合法的响应对象, 会直接从视图返回.
- 如果返回值为字符串, 会使用字符串数据以及默认参数创建响应对象
- 如果返回值是一个元组, 元组可以提供额外的信息,
但是元组必须是
(response, status, headers)
或(response, headers)
的形式, 且至少包含一个元素.status
的值会覆盖状态码,headers
可以是一个列表或者字典, 作为额外的表头值. - 如果上述条件都不满足, Flask会假设返回值是一个合法的WSGI应用程序, 并转换成一个响应对象.
如果你想在视图函数里面操纵响应对象,
可以使用`make_response()
<http://flask.pocoo.org/docs/0.12/api/#flask.make_response>`__
函数.
假设你有这样一个视图函数
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'), 404
你只需要把返回值表达式传给
`make_response()
<http://flask.pocoo.org/docs/0.12/api/#flask.make_response>`__
,获取响应对象, 并修改,然后返回它:
@app.errorhandler(404)
def not_found(error):
resp = make_response(render_template('error.html'), 404)
resp.headers['X-Something'] = 'A value'
return resp
除了请求对象, 还有一个session
对象.
它允许你在不同请求间存储用户特定信息. 它是在Cookies的基础上实现的,
并且对Cookies进行密钥签名. 这意味着用户可以查看cookie的内容,
但是不能修改它, 除非用户知道签名的密钥.
要使用sessions
需要一个设置一个密钥, 下面介绍密钥是如何工作的:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
# set the secret key. keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
这里提到的
`escape()
<http://flask.pocoo.org/docs/0.12/api/#flask.escape>`__
可以在模板引擎外做转义(如同本例).
如何生成密钥
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
Just take that thing and copy/paste it into your code and you're done.
使用基于cookie的sessions需要注意: Flask会将你放进会话对象的值序列化至Cookies. 如果你发现某些值在请求之间没有持久存在, 但是确实启用了Cookies, 也没有得到明确的错误信息. 此时, 检查响应页面中Cookies的大小, 与web浏览器所支持的大小进行对比.
除了默认基于客户端的sessions, 如果你想在服务端处理sessions, Flask也有扩展插件支持.
反馈, 是良好的应用和用户界面的重要组成部分. 如果用户没有得到足够的反馈, 他们可能会开始厌恶这个应用. Flask提供一个简单的闪现系统, 将反馈传递给用户. 闪现系统通常会在请求结束时记录信息, 在下一个(仅在下一个)请求中访问记录的信息. 展现这些内容需要结合模板实现.
使用 `flash()
<http://flask.pocoo.org/docs/0.12/api/#flask.flash>`__
方法闪现一条消息, 要操作消息本身可以使用
`get_flashed_messages()
<http://flask.pocoo.org/docs/0.12/api/#flask.get_flashed_messages>`__
函数, 在模板中也可以使用.
完整的示例查看 Message Flashing .
0.3 新功能
有时候你会处在这样一种情形, 你处理的数据本应该是正确的, 但实际上却不是.
比如, 你需要客户端代码向服务端发送一些请求, 但请求是畸形的.
这可能是用户篡改了数据, 或者客户端代码问题.
大多数情况下返回400 Bad Request
即可, 但是有时, 不能这样做,
并且需要代码继续运行.
你可能还希望记录发生了什么. 此时, loggers
就派上用场了, 从Flask 0.3
开始, Flask就预置了日志系统.
下面是一些记录日志的例子:
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
附带的logger
是一个标准日志 logger
, 更多信息查阅 logging
documentation .
如果你想给你的应用添加WSGI中间件, 你可以封装内部WSGI应用. 比如, 你想使用Werkzeug包中的某个中间件来解决lighttpd中的bugs, 你可以这样做
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
扩展插件可以帮你完成常见的任务. 比如, Flask-SQLAlchemy 提供 SQLAlchemy 支持, 它可以让你更简单的使用Flask.
更多关于Flask 扩展插件的信息, 查阅 Flask Extensions.
Ready to deploy your new Flask app? Go to Deployment Options.
Tutorial¶
本教程里面, 我们会创建一个简单的微博应用. 它仅支持一个用户,
可以创建文本条目, 没有提要和评论, 但是它仍然有我们需要的一切.
我们将使用Flask
和SQLite
.
示例代码 example source.
我们将博客命名为Flaskr, 基本上, 它将实现这些功能:
- 允许用户使用配置文件里面指定的凭证登录登出, 只支持一个用户.
- 用户登录时, 可以新增条目到页面(包含文本标题以及一些HTML文本), 因为用户可信任, 这部分HTML文本不做审查,.
- 首页倒序显示所有条目, 并且用户登录后可在这里添加新条目.
我们会直接使用SQLite3
, 因为它足够应付这个应用.
对于大型项目可以使用 SQLAlchemy,
它可以智能的处理数据库连接, 允许你同时连接不同的关系型数据库.
如果你的数据更适合NoSQL
, 你也可以考虑流行的NoSQL
数据库.
这是应用最终的效果图

python-flask-01
项目开始之前, 我们需要先将目录创建好
/flaskr
/flaskr
/static
/templates
推荐使用Python包进行安装和运行应用. 稍后你可以看到怎么运行flaskr
现在继续创建应用目录结构.
接下来几个步骤将要创建数据库表结构以及主要模块.
static
目录用来存放静态文件, 比如CSS, JavaScript,
通过HTTP的方式提供给用户.templates
目录将用来存放
Jinja2 模板.
我们的应用只需要一张表, 将如下内容写到一个schema.sql
文件,
并存放到flaskr/flaskr
目录
sql lite drop table if exists entries; create table entries ( id integer primary key autoincrement, title text not null, 'text' text not null );
表名为entries
, 有id
, title
, text
列,
id
列为自增主键.
在flaskr/flaskr
目录创建一个flaskr.py
# all the imports
import os
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, \
render_template, flash
The next couple lines will create the actual application instance and
initialize it with the config from the same file in flaskr.py
:
app = Flask(__name__) # create the application instance :)
app.config.from_object(__name__) # load config from this file , flaskr.py
# Load default config and override config from an environment variable
app.config.update(dict(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
SECRET_KEY='development key',
USERNAME='admin',
PASSWORD='default'
))
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
/flaskr
/flaskr
__init__.py
/static
/templates
flaskr.py
schema.sql
setup.py
MANIFEST.in
The content of the setup.py
file for flaskr
is:
from setuptools import setup
setup(
name='flaskr',
packages=['flaskr'],
include_package_data=True,
install_requires=[
'flask',
],
)
当使用setuptools工具时,
还需要将一些特殊文件及目录添加到包里面(MANIFEST.in
). 在这种情况下,
static
和 templates
目录需要被包含进来,
同时还有schema.sql
, 创建文件MANIFEST.in
并添加如下内容.
graft flaskr/templates
graft flaskr/static
include flaskr/schema.sql
简化定位应用程序, 添加如下内容到flaskr/__init__.py
:
from .flaskr import app
这个导入语句将把应用程序实例放在包顶部. 当运行应用程序的时候,
Flask开发服务器需要定位app实例. 这个导入语句简化了定位过程.
如果没有这句, 下面的export
语句需要改成
export FLASK_APP=flaskr.flaskr
.
此时, 可以安装应用程序了, 通常, 推荐在虚拟环境下安装Flask应用. 继续安装应用:
pip install --editable .
上面的命令需要在项目根目录下执行flaskr/
. 可编辑标记允许编辑源代码,
而无需在每次修改之后重新安装Flask应用,
此时Flask应用已经安装在你的虚拟环境中(具体可以查看pip freeze
的输出)
完成上面这些步骤之后, 就可以使用下面的没给你了那个启动你的应用了.
export FLASK_APP=flaskr
export FLASK_DEBUG=true
flask run
(如果在Windows上, 你需要使用set
代替export
).
FLASK_DEBUG
用于启用或禁用交互式调试器.
永远不要在生产环境使用调试模式, 因为这会允许用户在服务器上执行代码.
你将可以看到一些消息, 你可以使用对应的地址访问它.
* Serving Flask app "flaskr"
* Forcing debug mode on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 669-527-819
当你使用浏览器访问的时候, 浏览器会返回一个404错误, 因为我们还没有编写视图函数. 稍后我们会编写, 在这之前需要先让数据库工作.
服务器外部可见
如果想让你的服务器外部课件, 可以参考 externally visible server.
你现在有一个函数connect_db
与数据库建立连接, 但本身不是特别有用.
不断创建和关闭数据库连接效率非常低, 所以你需要让它保持长连接.
因为数据库连接封装了事务, 你需要确保一次只有一个请求使用这个连接.
一种优雅的方式就是利用程序环境.
Flask提供两个环境: 应用环境(Application Context), 请求环境(Request
Context). 不同环境有不同的特殊变量. 例如,
request
变量与当前请求的请求对象有关.
而g
是与当前应用环境有关的通用变量. 之后会深入了解g
那么你何时把数据库连接放到上面? 我们可以编写一个辅助函数. 在函数第一次被调用时, 它将为当前环境创建一个数据库连接, 调用成功后返回已经建立的连接.
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db
现在我们知道怎么连接数据库, 但我们应该如何正确断开呢?
Flask提供了`teardown_appcontext()
<http://flask.pocoo.org/docs/0.12/api/#flask.Flask.teardown_appcontext>`__
装饰器. 它会在应用环境销毁时执行:
@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()
装饰器
`teardown_appcontext()
<http://flask.pocoo.org/docs/0.12/api/#flask.Flask.teardown_appcontext>`__
标记的函数, 每次在应用环境销毁的时候执行, 这意味着什么? 本质上,
应用环境在请求到来之前被创建, 在请求结束时被销毁. 销毁有两种原因:
一切正常 (错误参数为``None``) 或发生异常, 第二种情况,
错误会被传递给销毁时调用的函数.
好奇环境的意义? The Application Context.
提示
我该把这段代码放在哪里?
如果你一直遵循本教程, 你可能想知道这步骤以及之后产生的代码放在什么地方.
逻辑上讲, 应该按模块来组织函数,
即把新的函数get_db
和close_db
函数放在之前的connect_db
函数下面.
如果你想找准定位, 可以查看一下示例代码. 在Flask里面, 你可以把所有代码放在单一的python模块里, 但是当你的应用规模扩大时, 这不是一个好主意.
如前面介绍所说, Flasker是一个数据库驱动的应用程序. 更准确地说, 它是一个由关系型数据库系统驱动的应用程序. 这样的系统需要一个模式来决定存储信息的方式. 所以在第一次启动服务的时候, 需要创建schema.
可以通过管道把schema.sql
作为sqlite3命令来创建.
sqlite3 /tmp/flaskr.db < schema.sql
但是执行该命令需要安装sqlite3
命令,
而并不是所有的系统都会安装这个. 同时它也要求你需要提供数据库路径,
否则将会报错. 我们可以使用一个函数来初始化, 比使用上面的命令更好,
更方便.
在flaskr.py connect_db函数前面
创建一个init_db()
函数
def init_db():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
@app.cli.command('initdb')
def initdb_command():
"""Initializes the database."""
init_db()
print('Initialized the database.')
app.cli.command()
装饰器会使用 flask 脚本注册一个新的命令.
当命令执行的时候, Flask 会自动创建一个应用环境绑定到正确的应用.
使用这个函数, 你可以访问
`flask.g
<http://flask.pocoo.org/docs/0.12/api/#flask.g>`__
以及其他你期望的东西. 当脚本结束的时候, 应用环境会被销毁,
数据库连接会被释放.
你会想要一个真正的函数初始化数据库, 尽管, 我们可以在单元测试里面轻松的创建数据库. (更多信息 Testing Flask Applications.)
应用对象的`open_resource()
<http://flask.pocoo.org/docs/0.12/api/#flask.Flask.open_resource>`__方法是一个辅助函数,
用来打开应用程序所提供的资源. 这个方法从资源位置 ( flaskr/flaskr
目录) 打开文件并允许我们阅读. 在本例中用于在数据库连接执行一个脚本.
SQLite提供的连接对象可以给你一个游标对象. 在这个游标里, 有一个方法执行完整的脚本. 最后, 你只需要提交改变. SQLite3和其他事务数据库在你没有明确表示要提交的时候, 不会进行提交.
现在, 可以使用flask创建数据库
flask initdb
Initialized the database.
故障排除
在你执行命令之后, 得到一个异常, 发现表没有被创建,
此时你可以检查init_db
命令, 以及你的表名是否正确(比如,单数和复数)
现在数据库正常, 你可以开始编写视图函数.
这个视图显示数据库存储的所有条目. 它监听/
,
应用将会从数据库查询title
, text
. 新的条目会显示在页面上面.
返回的行看上去有点像字典, 因为我们使用了sqlite3.Row
.
这视图函数将返回show_entries.html
模板,
并传递entries
变量.
@app.route('/')
def show_entries():
db = get_db()
cur = db.execute('select title, text from entries order by id desc')
entries = cur.fetchall()
return render_template('show_entries.html', entries=entries)
这个视图函数在用户登录的前提下, 允许用户新增项目.
该视图仅响应POST
请求, 表单显示在show_entries
页面.
如果一切正常, 它将在下一次请求的时候flash()
一条信息,
并重定向到show_entries
.
@app.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
db = get_db()
db.execute('INSERT INTO entries (title, text) VALUES (?, ?)',
[request.form['title'], request.form['text']])
db.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))
注意, 这个视图检查用户是否登录(也就是说,
如果logged_in
键存在于session
,并且为True
)
安全事项
在构建SQL语句的时候, 一定要使用?
做占位符,
否则应用程序使用字符串构建时容易受到SQL注入,更多信息Using SQLite 3
with
Flask.
这个函数用于登录用户以及退出. 登录时从配置里检查用户名和密码,
并设置logged_in
键值, 如果用户登录成功, 设置为True
,
用户将被重定向到show_entries
页面, 同时会闪现一条消息,
提示用户登录成功. 如果发生错误, 会提示用户相关信息, 并要求用户重新输入.
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('show_entries'))
return render_template('login.html', error=error)
logout
函数, 会删除session
中的logged_in
key,
这里有一个窍门: 如果使用pop()
方法并传递一个参数(默认),
如果存在该key这个方法将会从字典删除这个key,如果key不存在,则什么都不做.
这样就不需要检查用户是否登录.
@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
安全事项
密码不能使用纯文本存储, 本教程只是为了简单起见,. 如果你计划基于该项目发布一个项目, 密码应该使用散列并且加盐存储在数据库或文件里. hashed and salted.
幸运的是, Flask有扩展插件, 所以添加这个功能很简单, 同时, python也有很多库可用于散列.
是时候使用模板了. 你可能会注意到, 当运行app的时候, 会触发异常,
提示Flask无法找到模板. Flask默认启用
Jinja2 模板 .
这意味着除非你使用
`Markup
<http://flask.pocoo.org/docs/0.12/api/#flask.Markup>`__
标记一段代码或者在模板中使用 |safe
过滤器, 否则Jinja2将自动转义,
确保特殊字符, 例如 <
or >
被转义为等价的XML实体.
我们也会使用模板继承, 在所有网页中重用布局.
将下面的模板放置在templates
目录
这个模板包含HTML主体, 标题, 和登录链接(如果用户已经登录,
则提供登出功能). 如果有, 也会显示闪现消息. {% block body %}
将被子模板中的同名blcok
(body
)替换.
session字典在模板中也是可用的, 你可以用来检查, 用户是否登录.
Jinja支持访问不存在的属性,对象/字典属性或成员,
即便logged_in
key不存在.
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
<h1>Flaskr</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in</a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>
这个模板扩充layout.html
模板.
注意for
循环会遍历我们使用render_template()
传入的变量.
配置表单提交到add_entry
视图, 并且使用POST
方法.
{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
<dl>
<dt>Title:
<dd><input type=text size=30 name=title>
<dt>Text:
<dd><textarea name=text rows=5 cols=40></textarea>
<dd><input type=submit value=Share>
</dl>
</form>
{% endif %}
<ul class=entries>
{% for entry in entries %}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
{% else %}
<li><em>Unbelievable. No entries here so far</em>
{% endfor %}
</ul>
{% endblock %}
登录模板, 仅仅显示一个form表达, 供用户登录.
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method=post>
<dl>
<dt>Username:
<dd><input type=text name=username>
<dt>Password:
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
</form>
{% endblock %}
给应用添加风格, 在 static
目录下创建一个 style.css
样式表.
body { font-family: sans-serif; background: #eee; }
a, h1, h2 { color: #377ba8; }
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl { font-weight: bold; }
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
margin-bottom: 1em; background: #fafafa; }
.flash { background: #cee5F5; padding: 0.5em;
border: 1px solid #aacbe2; }
.error { background: #f0d6d6; padding: 0.5em; }
现在已经完成应用,一切如如预期一样正常. 添加自动化测试来简化将来的修改是一个不错的主意. 上面的应用程序作为一个基本的例子, 用来介绍单元测试,可以查看 Testing Flask Applications. 通过这个可以看到测试Flask应用多么容易.
假设你已经看了 Testing Flask
Applications,
并且已经为flaskr
编写字自己的测试,
或者跟随示例提供的方法进行了测试. 你可能会想知道如何组织项目.
推荐使用下面结构
flaskr/
flaskr/
__init__.py
static/
templates/
tests/
test_flaskr.py
setup.py
MANIFEST.in
现在继续创建 tests/
目录以及 test_flaskr.py
文件.
你可以运行测试, 这里将使用pytest
注意
确保你在开发flaskr
的虚拟环境已经安装pytest
.
否则pytest
将无法导入依赖的组件来测试应用
pip install -e .
pip install pytest
运行以及观看测试过程,在项目根目录下执行
py.test
处理测试的一种方法就是使用setuptools
集成, 它依赖一些设置.
我们在setup.py
里面添加一些内容, 并常见一个setup.cfg
文件,
以这种方式测试的好处是你不需要安装pytest
.继续并更新setup.py
.
from setuptools import setup
setup(
name='flaskr',
packages=['flaskr'],
include_package_data=True,
install_requires=[
'flask',
],
setup_requires=[
'pytest-runner',
],
tests_require=[
'pytest',
],
)
项目根目录下创建setup.cfg
文件 (跟 setup.py
处于同一级目录):
[aliases]
test=pytest
现在可以运行
python setup.py test
This calls on the alias created in setup.cfg
which in turn runs
pytest
via pytest-runner
, as the setup.py
script has been
called. (Recall the setup_requires argument in setup.py
) Following
the standard rules of test-discovery your tests will be found, run, and
hopefully pass.
This is one possible way to run and manage testing. Here pytest
is
used, but there are other options such as nose
. Integrating testing
with setuptools
is convenient because it is not necessary to
actually download pytest
or any other testing framework one might
use.
模板¶
测试Flask应用¶
没有经过测试的东西是不完整的.
部署应用到Heroku¶
- 注册账号 Heroku account.
- 本地安装python 3.6
- 安装setuptools, pip
- 安装virtualenv
- postgres
安装之后,登录, 安装方法往下看
$ heroku login
Enter your Heroku credentials.
Email: python@example.com
Password:
...
brew install heroku
heroku logs --tail
Flask¶
uWSGI¶
Quickstart for Python/WSGI applications¶
uWSGI使用C编写, 所以需要C编译器(比如gcc或clang)以及Python开发包
基于Debian的发行版执行如下命令
apt-get install build-essential python-dev
安装方法
- 通过pip
pip install uwsgi
- 通过curl
curl http://uwsgi.it/install | bash -s default /tmp/uwsgi
(这将安装``uWSGI``二进制包到/tmp/uwsgi
,可自行修改).
- 源码安装
wget https://projects.unbit.it/downloads/uwsgi-latest.tar.gz
tar zxvf uwsgi-latest.tar.gz
cd <dir>
make
从以下示例开始, 将内容保存到foobar.py
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"]
如果前端有负载均衡等, 不要使用--http
, 使用--http-socket
uwsgi --http :9090 --wsgi-file foobar.py
默认uWSGI使用单进程, 单线程
可以使用--processes
参数启动多进程,
使用--threads
参数启动多线程.
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2
如上命令将启动4个进程(每个进程两个线程), 一个master进程(当进程死掉的时候, 重生他们)
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
--stats
参数, 可以允许我们使用JSON格式导出uWSGI的状态
curl 127.0.0.1:9191
即可获取相关信息
同时提供一个类似top
命令的工具监控,uwsgitop
(使用pip安装).
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
使用本地uwsgi协议重新启动 uWSGI.
uwsgi --socket 127.0.0.1:3031 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
如果代理服务器使用HTTP,
那么uWSGI也需要使用HTTP协议(这个参数不同于--http
,
它将通过自身产生一个代理)
uwsgi --http-socket 127.0.0.1:3031 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
使用如下命令开始, myflaskapp.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "<span style='color:red'>I am app 1</span>"
Flask的出口是一个app的WSGI函数(在这里我们称为“application”),我们需要通知uWSGI使用它
uwsgi --socket 127.0.0.1:3031 --wsgi-file myflaskapp.py --callable app --processes 4 --threads 2 --stats 127.0.0.1:9191
仅仅新增了--callable
参数
一个比较受欢迎的选择, 编写一个uWSGI配置文件, uwsgi.ini
[uwsgi]
http = :9090
chdir = /root/foobar
module = myflaskapp
callable = app
# 使用虚拟环境
virtualenv = /root/.pyenv/versions/uwsgi
master = true
processes = 8
执行命令 uwsgi uwsgi.ini
, 使用浏览器访问9090端口
project¶
学生管理系统¶
http://www.cnblogs.com/haiyan123/p/7816877.html
目的:实现学生,老师,课程的增删改查
django-admin startproject stu_manger
cd stu_manger
django-admin startapp app01
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 配置 templates
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
爬虫¶
Scrapy¶
Beautiful Soup¶
安装 Beautiful Soup¶
快速开始¶
通过BeautifulSoup
解析文档, 文档可以是字符串或者文件句柄.
from bs4 import BeautifulSoup
with open("index.html") as fp:
soup = BeautifulSoup(fp)
soup = BeautifulSoup("<html>data</html>")
对象类型¶
四大类型
- Tag
- NavigableString
- BeautifulSoup
- Comment
与XML或HTML文档内容中的tag
一致
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
>>> tag.name
'b'
# 修改tag名字, 此操作会影响所有通过当前Beautiful Soup对象生成的HTML文档
>>> tag.name = "blockquote"
>>> tag
<blockquote class="boldest">Extremely bold</blockquote>
tag的属性的操作方法同字典, tag的属性可以被添加,删除或修改
>>> tag['class']
['boldest']
使用 .
操作, 获取所有属性
>>> tag.attrs
{'class': ['boldest']}
多值属性
在BeautifulSoup中, 多值属性返回类型是list
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]
css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]
如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'
将tag转换成字符串时,多值属性会合并为一个值
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
rel_soup.a['rel']
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>Back to the <a rel="index contents">homepage</a></p>
如果转换的文档是XML格式,那么tag中不包含多值属性
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
xml_soup.p['class']
# u'body strikeout'
BeautifulSoup
对象表示的是一个文档的全部内容. 大部分时候,
可以把它当作 Tag
对象,它支持 遍历文档树
和 搜索文档树
中描述的大部分的方法.
因为 BeautifulSoup
对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.
但有时查看它的 .name
属性是很方便的, 所以 BeautifulSoup
对象包含了一个值为 "[document]"
的特殊属性 .name
>>> soup.name
'[document]'
文档注释
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
>>> type(comment)
<class 'bs4.element.Comment'>
>>> comment
'Hey, buddy. Want to buy a used parser?'
Comment
对象是一个特殊类型的 NavigableString
对象,
当它出现在HTML文档中时, Comment 对象会使用特殊的格式输出
>>> print(soup.b.prettify())
<b>
<!--Hey, buddy. Want to buy a used parser?-->
</b>
Beautiful Soup中定义的其它类型都可能会出现在XML的文档中: CData
,
ProcessingInstruction
, Declaration
, Doctype
.与
Comment
对象类似,这些类都是 NavigableString
的子类,只是添加了一些额外的方法的字符串独享.下面是用CDATA来替代注释的例子:
from bs4 import CData
cdata = CData("A CDATA block")
comment.replace_with(cdata)
print(soup.b.prettify())
# <b>
# <![CDATA[A CDATA block]]>
# </b>
爬虫¶
机器学习¶
TensorFlow¶
基础¶
import tensorflow as tf
matrix1 = tf.constant([[3, 3]])
matrix2 = tf.constant([[2],
[2]])
# matrix multiply np.dot(m1, m2)
product = tf.matmul(matrix1, matrix2)
# # 运行会话的方式
# # method 1
# sess = tf.Session()
# result = sess.run(product)
# print(result)
# sess.close()
# method 2
with tf.Session() as sess:
# 会话每run一次, 运行一次
result2 = sess.run(product)
print(result2)
import tensorflow as tf
state = tf.Variable(0, name='counter')
# print(state.name)
one = tf.constant(1)
new_value = tf.add(state, one)
update = tf.assign(state, new_value)
# 如果有定义变量, 就必须使用如下语句, 并且如下使用 sess.run(init) 进行初始化
# init = tf.initialize_all_variables() , 后续版本要求使用 tf.global_variables_initializer() 代替
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
for _ in range(3):
sess.run(update)
print(sess.run(state))
占位符, 运行的时候, 再传入输入的值
import tensorflow as tf
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
output = tf.multiply(input1, input2)
with tf.Session() as sess:
print(sess.run(output, feed_dict={input1: [7.], input2: [2.]}))
import tensorflow as tf
import numpy as np
# create data
x_data = np.random.rand(100).astype(np.float32)
y_data = x_data * 0.1 + 0.3
# create tensorflow structure #
Weights = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
biases = tf.Variable(tf.zeros([1]))
y = Weights * x_data + biases
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
init = tf.initialize_all_variables()
# create structure end #
sess = tf.Session()
# 必须初始化
sess.run(init)
# 会话每run一次, 执行一次
for step in range(201):
sess.run(train)
if step % 20 == 0:
print(step, sess.run(Weights), sess.run(biases))
执行结果

tf-001
执行的时候会出现一个警告
initialize_all_variables (from tensorflow.python.ops.variables) is deprecated and will be removed after 2017-03-02.
Instructions for updating:
Use `tf.global_variables_initializer` instead.
教程¶
MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片以及对应的标签.
MNIST数据集的官网是Yann LeCun’s website。
安装tensorflow
后, 在对应目录下会有一个示例,
里面的input_data.py
可以用于下载和安装这个数据集.
site-packages/tensorflow/examples/tutorials/mnist
执行如下代码, 会在运行python shell
的目录进行解压
(tensorflow) ➜ ~ python
Python 3.5.1 (default, Jul 24 2017, 08:38:39)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import tensorflow.examples.tutorials.mnist.input_data as input_data
>>> mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST_data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
退出终端可以看到如下内容
(tensorflow) ➜ ~ ls MNIST_data
t10k-images-idx3-ubyte.gz t10k-labels-idx1-ubyte.gz train-images-idx3-ubyte.gz train-labels-idx1-ubyte.gz
下载下来的数据集被分成两部分:60000行的训练数据集(mnist.train
)和10000行的测试数据集(mnist.test
)。这样的切分很重要,在机器学习模型设计时必须有一个单独的测试数据集不用于训练而是用来评估这个模型的性能,从而更加容易把设计的模型推广到其他数据集上(泛化)。
TensorFlow Serving¶
TensorFlow Serving is an open-source software library for serving machine learning models. It deals with the inference aspect of machine learning, taking models after training and managing their lifetimes, providing clients with versioned access via a high-performance, reference-counted lookup table.
编译或使用TensorFlow Serving
, 需要安装一些依赖.
TensorFlow Serving
依赖Bazel
0.4.5 或更高. You can find the
Bazel installation instructions
here.
If you have the prerequisites for Bazel, those instructions consist of the following steps:
- Download the relevant binary from here. Let’s say you downloaded bazel-0.4.5-installer-linux-x86_64.sh. You would execute:
cd ~/Downloads
chmod +x bazel-0.4.5-installer-linux-x86_64.sh
./bazel-0.4.5-installer-linux-x86_64.sh --user
- Set up your environment. Put this in your ~/.bashrc.
export PATH="$PATH:$HOME/bin"
Our tutorials use gRPC (1.0.0 or higher) as our RPC framework. You can find the installation instructions here.
sudo apt-get update && sudo apt-get install -y \
build-essential \
curl \
libcurl3-dev \
git \
libfreetype6-dev \
libpng12-dev \
libzmq3-dev \
pkg-config \
python-dev \
python-numpy \
python-pip \
software-properties-common \
swig \
zip \
zlib1g-dev
TensorFlow依赖可能会改变, 所以注意关注 build
instructions.
注意apt-get install
和 pip install
命令, 你可能需要运行.
运行python客户端代码, 不需要安装Bazel
,
使用如下命令安装tensorflow-serving-api
pip install tensorflow-serving-api
TensorFlow Serving ModelServer 有两种变体:
tensorflow-model-server: 针对使用一些特定指令的编译器的平台(比如SSE4和AVX指令的平台)进行过完整的优化. 这应该是大多数用户的首选, 但是在一些老的机器上可能无法工作.
tensorflow-model-server-universal: 基本优化编译而成,
但不包含特定平台的指令集, 所以在大多数(不是全部)机器上都可以正常工作.
如果使用 tensorflow-model-server
无法工作, 请使用该版本.
注意两个二进制包名是相同的, 如果已经安装过 tensorflow-model-server
,
请先运行一下命令进行卸载:
sudo apt-get remove tensorflow-model-server
- 添加TensorFlow Serving 源 (如没有添加)
echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
- 安装升级TensorFlow ModelServer
sudo apt-get update && sudo apt-get install tensorflow-model-server
安装完毕后, 可以使用 tensorflow_model_server
命令调用.
可以使用如下命令升级tensorflow-model-server
到新版本:
sudo apt-get upgrade tensorflow-model-server
注意: 在上面的命令中, 如果你的处理器不支持AVX 指令, 使用
tensorflow-model-server-universal
代替tensorflow-model-server
.
git clone --recurse-submodules https://github.com/tensorflow/serving
cd serving
使用--recurse-submodules
参数获取TensorFlow
,
gRPC
和其他TensorFlow Serving
依赖的库. 注意,
这将安装TensorFlow Serving
的最新master分之. 如果你想安装指定分支
(如发布分支),通过 -b <branchname>
参数指定 git clone
分支.
按照本文先决条件部分, 安装相关依赖项. 配置TensorFlow
, 运行
cd tensorflow
./configure
cd ..
如果在安装TensorFlow
或相关依赖项遇到任何问题都可以查阅
TensorFlow install
instructions.
TensorFlow Serving
使用Bazel
构建.
使用Bazel
命令构建个人目标或者完整源码树.
构建完整代码数, 执行:
bazel build -c opt tensorflow_serving/...
二进制包将放置在bazel-bin
目录, 可以使用如下命令执行:
bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server
测试你的安装, 执行:
bazel test -c opt tensorflow_serving/...
通过查看 basic
tutorial
and advanced
tutorial
获取运行TensorFlow Serving
更深入的例子.
针对一些使用特殊指令集(比如AVX)的平台, 可以进行编译参数优化,
可以显著提高性能. 无论你在文档哪里看到bazel build
,
你都可以添加参数
-c opt --copt=-msse4.1 --copt=-msse4.2 --copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-O3
(或参数的子集). 例如:
bazel build -c opt --copt=-msse4.1 --copt=-msse4.2 --copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-O3 tensorflow_serving/...
注意: 这些指令集不是在所有机器上都可用, 尤其是处理器比较老的机器,
所以使用上述完整的参数可能无法工作, 你可以尝试里面的部分参数,
或使用基本参数 -c opt
, 该参数可以保证在所有机器上可用.
我们的
持续构建
使用 TensorFlow
ci_build
基础设施(基于docker), 可以简化开发过程. 你需要的工具是git
和
docker
. 不需要手动安装所有依赖项.
git clone --recursive https://github.com/tensorflow/serving
cd serving
CI_TENSORFLOW_SUBMODULE_PATH=tensorflow tensorflow/tensorflow/tools/ci_build/ci_build.sh CPU bazel test //tensorflow_serving/...
注意: serving
目录映射到容器内.
你可以在容器外使用你喜欢的编辑器进行开发, 当你运行该构建的时候,
它将对你所有的更改进行构建.
文档地址 https://github.com/tensorflow/serving/blob/master/tensorflow_serving/g3doc/setup.md
OpenCV¶
OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,
brew tap homebrew/science
brew install opencv
# 安装之后会提示, 需要执行某些命令, 根据提示执行命令
# cd /usr/local/lib/python2.7/site-packages
# echo 'import site; site.addsitedir("/usr/local/lib/python2.7/site-packages")' >> homebrew.pth
使用虚拟环境可能需要将 cv2.so 链接到虚拟环境site-packages目录下
ln -s /usr/local/Cellar/opencv/2.4.10/lib/python2.7/site-packages/cv.py cv.py
ln -s /usr/local/Cellar/opencv/2.4.10/lib/python2.7/site-packages/cv2.so cv2.so
最后使用如下命令验证
import cv
import cv2
# 待验证, 是否可以使用conda来管理
RUN conda install -yc conda-forge opencv
gRPC¶
快速开始¶
https://grpc.io/docs/quickstart/python.html
python -m pip install grpcio
在 El Capitan OSX, 可能会报如下错误:
OSError: [Errno 1] Operation not permitted: '/tmp/pip-qwTLbI-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/six-1.4.1-py2.7.egg-info'
此时使用如下命令
python -m pip install grpcio --ignore-installed
Python gRPC tools
包括 protocol buffer
编译器 protoc
和用于生成服务器和客户端代码的插件, generating server and client code
from .proto
service definitions. For the first part of our
quickstart example, we’ve already generated the server and client stubs
from
helloworld.proto,
but you’ll need the tools for the rest of our quickstart, as well as
later tutorials and your own projects.
安装 gRPC tools:
python -m pip install grpcio-tools
gRPC¶
现代开源高性能RPC框架, 可以在任何环境中运行. 它可以有效地连接服务, 跨数据中心提供负载均衡, 跟踪, 健康检查, 和验证功能. 它同时适用于最后一英里的分布式连接设备, 移动应用和浏览器到后端服务.
主要使用场景
- 高效连接微服务架构的多语言服务.
- 连接移动设备, 浏览器客户端到后端服务.
- 生成高效的客户端库.
核心特性
- 10种语言惯用客户端库.
- 高性能报文, 简单的服务定义框架.
- 基于http/2标准设计.
- 支持认证, 跟踪, 负载均衡和健康检查.
机器学习¶
Deployment¶
uWSGI¶
UWSGI部署Django项目¶
- uWSGI
- nginx
https://uwsgi.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
web服务器网管接口(Web Server Gateway Interface) WSGI is a Python standard.
the web client <-> the web server <-> the socket <-> uwsgi <-> Django
pip install uwsgi
pip3 install uwsgi
创建test.py
文件
# test.py
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"] # python3
# python3依赖 bytes()
#return ["Hello World"] # python2
运行uWSGI
uwsgi --http :8000 --wsgi-file test.py
# http :8000: 使用http协议 , 端口 8000
# wsgi-file test.py: 加载指定文件, test.py
This should serve a ‘hello world’ message directly to the browser on port 8000. Visit:
如果返回hello world
, 则以下组件工作正常:
the web client <-> uWSGI <-> Python
确保项目能够跑起来
python manage.py runserver 0.0.0.0:8000
如果正常, 则可以使用uWSGI尝试
# mysite 即上面创建的Django项目名
uwsgi --http :8000 --module mysite.wsgi
模块 mysite.wsgi
表示加载指定的 wsgi
模块
使用浏览器访问该web服务; 如果可以访问到内容, 则表示 uWSGI
在虚拟环境能够运行Django应用, 如下操作正确:
the web client <-> uWSGI <-> Django
我们通常不会使用浏览器直接访问uWSGI
, 我们可以在前面加上负载均衡.
Ubuntu
sudo apt-get install nginx
sudo /etc/init.d/nginx start # start nginx
使用浏览器访问http://localhost:8080确认Nginx是否可以正常运行, 确定如下组件是否正常工作.
the web client <-> the web server
我们需要uwsgi_params
文件,
https://github.com/nginx/nginx/blob/master/conf/uwsgi_params,
将该文件复制到项目地址
接下来创建mysite_nginx.conf
# mysite_nginx.conf
# the upstream component nginx needs to connect to
upstream django {
# server unix:///path/to/your/mysite/mysite.sock; # for a file socket
server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}
# configuration of the server
server {
# the port your site will be served on
listen 8000;
# the domain name it will serve for
server_name .example.com; # substitute your machine's IP address or FQDN
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /path/to/your/mysite/media; # your Django project's media files - amend as required
}
location /static {
alias /path/to/your/mysite/static; # your Django project's static files - amend as required
}
# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass django;
include /path/to/your/mysite/uwsgi_params; # the uwsgi_params file you installed
}
}
创建软链接到 /etc/nginx/sites-enabled
:
sudo ln -s ~/path/to/your/mysite/mysite_nginx.conf /etc/nginx/sites-enabled/
运行Nginx之前, 需要把Django静态文件放到指定位置, 使用Nginx提供给浏览器.
修改 mysite/settings.py
:
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
运行
python manage.py collectstatic
重启Nginx
sudo /etc/init.d/nginx restart
添加 media.png(随便找张图片)
到
/path/to/your/project/project/media
目录,
使用浏览器访问http://localhost:8000/media/media.png , 如果正常,
就可以确定Nginx服务正常.
# 指定socket , 指定8001端口
uwsgi --socket :8001 --wsgi-file test.py
the web client <-> the web server <-> the socket <-> uWSGI <-> Python
使用socket更简单, 更好(减少开销)
编辑 mysite_nginx.conf
:
server unix:///path/to/your/mysite/mysite.sock; # for a file socket
# server 127.0.0.1:8001; # for a web port socket (we'll use this first)
重启nginx.
重新运行uWSGI:
uwsgi --socket mysite.sock --wsgi-file test.py
This time the socket option tells uWSGI which file to use.
检查 nginx error log(/var/log/nginx/error.log)
.
如果看到如下报错
connect() to unix:///path/to/your/mysite/mysite.sock failed (13: Permission
denied)
你需要设置socket的权限, 让Nginx可以使用它
uwsgi --socket mysite.sock --wsgi-file test.py --chmod-socket=666 # (very permissive)
or:
uwsgi --socket mysite.sock --wsgi-file test.py --chmod-socket=664 # (more sensible)
同时你可能需要切换用户到nginx
做在组(比如 www-data
),
这样Nginx才可能读写你的socket.
根据Nginx日志排除故障.
uwsgi --socket mysite.sock --module mysite.wsgi --chmod-socket=664
现在你的Django
应用可以通过uWSGI
和nginx
为用户提供服务.
通过配置文件运行, 可以使你的维护异常简单
创建文件 mysite_uwsgi.ini
:
# mysite_uwsgi.ini file
[uwsgi]
# Django-related settings
# the base directory (full path)
chdir = /path/to/your/project
# Django's wsgi file
module = project.wsgi
# the virtualenv (full path)
home = /path/to/virtualenv
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe
socket = /path/to/your/project/mysite.sock
# ... with appropriate permissions - may be needed
# chmod-socket = 664
# clear environment on exit
vacuum = true
使用该文件运行 uswgi:
uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file
测试Django站点
到此为止, uWSGI安装在虚拟环境;有时候我们需要直接在系统安装.
退出虚拟环境:
deactivate
安装uWSGI
sudo pip install uwsgi
# Or install LTS (long term support).
pip install https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
运行并检查
uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file
uWSGI可以运行在君主模式下, 该模式uWSGI会监视配置文件目录, 并为所有文件启动实例
当一个配置文件被修改时, uWSGI会自动重启对应实例
# create a directory for the vassals
sudo mkdir /etc/uwsgi
sudo mkdir /etc/uwsgi/vassals
# symlink from the default config directory to your config file
sudo ln -s /path/to/your/mysite/mysite_uwsgi.ini /etc/uwsgi/vassals/
# run the emperor
uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data
你可能需要使用sudo运行
sudo uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data
# emperor: where to look for vassals (config files)
# uid: the user id of the process once it’s started
# gid: the group id of the process once it’s started
检查站点
使用 rc.local
file.
编辑/etc/rc.local
添加:
# 在 “exit 0” 行前添加.
/usr/local/bin/uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data --daemonize /var/log/uwsgi-emperor.log
使用docker,uWSGI,Nginx部署Django项目¶
Deployment¶
生成项目依赖¶
生成requirements.txt
使用 pipreqs¶
这个工具的好处是可以通过对项目目录的扫描,自动发现使用了那些类库,自动生成依赖清单。
缺点是可能会有些偏差,需要检查并自己调整下。
pip install pipreqs
pipreqs ./
...
INFO: Successfully saved requirements file in ./requirements.txt
cat requirements.txt
Django == 1.6
django_redis == 4.3.0
redis == 2.10.5
simplejson == 3.8.2
有时候结果可能会有些偏差, 根据的需要修改 requirements.txt
就好了。
问题记录¶
问题记录¶
Django¶
import sys
import django
# 将项目目录添加到path路径, 如此设置环境才能找到项目
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_name.settings")# project_name 项目名称
django.setup()
Python相关项目¶
tmp¶
print¶
print原型¶
end 默认为’:raw-latex:`\n`’
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
"""
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
"""
pass
格式化输出¶
python格式化字符串有%和{}两种 字符串格式控制符.
字符串输入数据格式类型(%格式操作符号)
%%百分号标记
%c字符及其ASCII码
%s字符串
%d有符号整数(十进制)
%u无符号整数(十进制)
%o无符号整数(八进制)
%x无符号整数(十六进制)
%X无符号整数(十六进制大写字符)
%e浮点数字(科学计数法)
%E浮点数字(科学计数法,用E代替e)
%f浮点数字(用小数点符号) 以为小数 %.1f 百分比 %2.1f %%
%g浮点数字(根据值的大小采用%e或%f)
%G浮点数字(类似于%g)
%p指针(用十六进制打印值的内存地址)
%n存储输出字符的数量放进参数列表的下一个变量中
如果字符串里面的 `%` 是一个普通字符,这个时候就需要转义,用 `%%` 来表示一个 `%`
name:可为空,数字(占位),命名(传递参数名,不能以数字开头)以字典格式映射格式化,其为键名
flag:标记格式限定符号,包含+-#和0,+表示右对齐(会显示正负号),-左对齐,前面默认为填充空格(即默认右对齐),0表示填充0,#表示八进制时前面补充0,16进制数填充0x,二进制填充0b
width:宽度(最短长度,包含小数点,小于width时会填充)
precision:小数点后的位数,与C相同
type:输入格式类型,请看上面
用{}包裹name命名传递给format以命名=值 写法,非字典映射,其他和上面相同
fill = <any character>
#fill是表示可以填写任何字符
align = "<" | ">" | "=" | "^"
#align是对齐方式,<是左对齐,
>是右对齐,^是居中对齐。
sign = "+" | "-" | " "
#sign是符号, +表示正号, -表示负号
width = integer
#width是数字宽度,表示总共输出多少位数字
precision = integer
#precision是小数保留位数
type = "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
#type是输出数字值是的表示方式,比如b是二进制表示;比如E是指数表示;比如X是十六进制表示
>>> print("{color}-{what}".format(**dic))
green-apple
print("{:,}".format(123456))#输出1234,56
print("{a:w^8}".format(a="8"))#输出www8wwww,填充w
print("%.5f" %5)#输出5.000000
print("%-7s3" %("python"))#输出python 3
print("%.3e" %2016)#输出2.016e+03,也可以写大E
print("%d %s" %(123456,"myblog"))#输出123456 myblog
>>> print("{}{}{}".format("xxx",".","cn"))
xxx.cn
>>> print("{0}{1}".format("hello","fun"))
hellofun
>>> print("{a[0]}{a[1]}{a[2]}".format(a=["xxx",".","cn"]))
xxx.cn
>>> print("{dict[host]}{dict[dot]}{dict[domain]}".format(dict={"host":"www","domain":"xxx.cn","dot":"."}))
www.xxx.cn
>>> print("{who} {doing} {0}".format("python",doing="like",who="I"))
I like python
>>> print("%(color)s-%(what)s" % {'color': 'red', 'what': 'xxx'})
red-xxx
Python接收邮件¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 08/12/2017 2:15 PM
# @Author : yang
import sys
import re
import email
from imapclient import IMAPClient
def set_flag_seen(message_id, account_info):
"""
# 设置邮件为已读取
:param message_id: 消息id列表
:return:
"""
imapObj = IMAPClient(account_info['imap_server'])
imapObj.login(username=account_info['username'], password=account_info['password'])
imapObj.select_folder('INBOX', readonly=False)
# 标记为已读
imapObj.add_flags(message_id, br'\Seen')
print("set_flag_seen")
def get_unseen_mail(server):
"""
获取状态为 unseen 的邮件
:param server: IMAPClient
:return:
"""
server.select_folder('INBOX', readonly=True)
result = server.search('UNSEEN')
if not result:
sys.exit(0)
print(result)
# 使用fetch()函数收取邮件
msgdict = server.fetch(result, [b'BODY.PEEK[]'])
for message_id, message in msgdict.items():
print(message, message_id)
print(message[b'BODY[]'])
e = email.message_from_string(message[b'BODY[]'].decode()) # 生成Message类型
# print(e)
subject = email.header.make_header(email.header.decode_header(e['SUBJECT'])) #必须保证包含subject
# <class 'email.header.Header'>
mail_from = email.header.make_header(email.header.decode_header(e['From']))
# 发件人邮箱
mail_from_just_mail = re.search(r'(?!.*<).*(?=>)', str(mail_from)).group()
print('---------')
# 解析邮件正文
maintype = e.get_content_maintype()
if maintype == 'multipart':
for part in e.get_payload():
if part.get_content_maintype() == 'text':
mail_content = part.get_payload(decode=True).strip()
elif maintype == 'text':
mail_content = e.get_payload(decode=True).strip()
# 此时,需要把content转化成中文,利用如下方法:
try:
mail_content = mail_content.decode('gbk')
except UnicodeDecodeError:
print('decode error')
sys.exit(1)
else:
print('new message')
print('From: ', mail_from_just_mail)
print('Subject: ', subject)
print('-' * 10, 'mail content', '-' * 10)
print(mail_content.replace('<br>', '\n'))
print('-' * 10, 'mail content', '-' * 10)
if __name__ == '__main__':
EMAIL_ACCOUNT_INFO = {
'imap_server': 'imap.exmail.qq.com',
'username': 'username',
'password': 'password'
}
server = IMAPClient(EMAIL_ACCOUNT_INFO['imap_server'])
server.login(
username=EMAIL_ACCOUNT_INFO['username'],
password=EMAIL_ACCOUNT_INFO['password']
)
get_unseen_mail(server)
tmp¶
XPath¶
选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
选取当前节点的父节点。 | |
@ | 选取属性。 |
max/min 函数的用法¶
说明¶
def max(*args, key=None): # known special case of max
"""
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
"""
pass
简单用法¶
>>> tmp = max(1,2,4)
>>> print(tmp)
4
>>> a = [1,2,3,4,5,6]
>>> print(max(a))
6
中级用法,key属性的使用¶
当key参数不为空时,就以key的函数对象为判断的标准。
如果我们想找出一组数中绝对值最大的数,就可以配合lamda先进行处理,再找出最大值
>>> a = [-9,-4,6,1,-3]
>>> tmp = max(a,key = lambda x : abs(x))
>>> print(tmp)
-9
有一组商品,其名称和价格在一个字典中,可以用下面的方法快速找到价格最贵的那组商品¶
prices = {
'A' : 123,
'B' : 324,
'C' : 444
}
# 在对字典进行数据操作的时候,默认只会处理key,而不是value
# 先使用zip把字典的keys和values翻转过来,再用max取出值最大的那组数据
max_prices = max(zip(prices.values(),prices.keys()))
print(max_prices)
min_prices = min(zip(prices.values(),prices.keys()))
print(min_prices)
pip¶
安装¶
get-pip.py
安装¶下载 get-pip.py
wget https://bootstrap.pypa.io/get-pip.py
运行命令
python get-pip.py
python3 get-pip.py # 没有python命令,有其他版本的话使用该版本运行
root@ubuntu-linux:~/src# pip -V
pip 9.0.1 from /usr/local/lib/python3.5/dist-packages (python 3.5)
快速开始¶
先决条件: 安装 pip
从PyPI安装
$ pip install SomePackage
[...]
Successfully installed SomePackage
指定文件安装
$ pip install SomePackage-1.0-py2.py3-none-any.whl
[...]
Successfully installed SomePackage
指定版本安装
pip install django==1.9
查看安装的文件
pip show --files SomePackage
示例
# xlrd (1.0.0) - Latest: 1.1.0 [wheel]
# xlwt (1.2.0) - Latest: 1.3.0 [wheel]
查看所有包版本, 当前版本, 最新版本
pip list --outdated
升级包:
$ pip install --upgrade SomePackage
[...]
Found existing installation: SomePackage 1.0
Uninstalling SomePackage:
Successfully uninstalled SomePackage
Running setup.py install for SomePackage
Successfully installed SomePackage
卸载包
$ pip uninstall SomePackage
Uninstalling SomePackage:
/my/env/lib/pythonx.x/site-packages/somepackage
Proceed (y/n)? y
pip使用过程中遇到的问题¶
原因是Mac的内核保护,默认会锁定/system,/sbin,/usr目录
解决(不一定有用,没用可以关掉保护,百度…)
pip install --upgrade pip
sudo pip install numpy --upgrade --ignore-installed
sudo pip install scipy --upgrade --ignore-installed
sudo pip install scikit-learn --upgrade --ignore-installed
python版本管理¶
使用virtualenv管理多个开发环境¶
root@ubuntu-linux:~# mkvirtualenv y # 因为已经指定了默认的Python版本,所以默认的是Python3
Using base prefix '/usr'
New python executable in /root/.virtualenvs/y/bin/python3
Also creating executable in /root/.virtualenvs/y/bin/python
Installing setuptools, pip, wheel...done.
virtualenvwrapper.user_scripts creating /root/.virtualenvs/y/bin/predeactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/y/bin/postdeactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/y/bin/preactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/y/bin/postactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/y/bin/get_env_details
(y) root@ubuntu-linux:~#
# 创建成功之后会自动进入virtualenv中
退出virtualenv
在任意目录执行deactivate就可以退出
(y) root@ubuntu-linux:~# deactivate
root@ubuntu-linux:~#
查看所有virtualenv
root@ubuntu-linux:~# workon
y
在工作环境之间切换
root@ubuntu-linux:~# workon y
(y) root@ubuntu-linux:~#
删除一个virtualenv
(y) root@ubuntu-linux:~# deactivate
root@ubuntu-linux:~# rmvirtualenv y
Removing y...
root@ubuntu-linux:~#
root@ubuntu-linux:~# workon
root@ubuntu-linux:~#
pyevn,管理安装python多版本¶
This tool installs pyenv
and friends. It is inspired by
rbenv-installer.
两种方式安装 pyenv. The PyPi support is not tested by many users yet, so the direct way is still recommended if you want to play it safe.
通过Github安装 (推荐)
Install:
$ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
# 执行之后会有提示,按照提示操作即可
WARNING: seems you still have not added 'pyenv' to the load path.
# Load pyenv automatically by adding
# the following to ~/.bash_profile:
export PATH="/root/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
- Homebrew on Mac OS X
You can also install pyenv using the Homebrew package manager for Mac OS X.
brew update
# 升级使用upgrade
brew install pyenv
将
pyenv init
添加到shellecho ‘eval “$(pyenv init -)”’ >> ~/.bash_profile
如果使用Zsh或者Ubuntu等用如下文件代替~/.bash_profile
Zsh note: Modify your ~/.zshenv
file instead of
~/.bash_profile
.
Ubuntu and Fedora note: Modify your ~/.bashrc
file instead of
~/.bash_profile
.
- 重启shell或执行如下命令开始使用pyenv
exec $SHELL
- Install Python versions into $(pyenv root)/versions.
# 安装2.7.10
pyenv install 2.7.10
pyenv update
pyenv
is installed within $PYENV_ROOT
(default: ~/.pyenv
).
卸载只需要移除该目录:
rm -fr ~/.pyenv
# 或者
rm -rf $(pyenv root)
# 如果使用brew安装的, 可以使用如下命令卸载
brew uninstall pyenv
从 .bashrc
或其他相关文件, 移除环境变量,及pyenv初始化的命令:
export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
帮助信息
➜ ~ pyenv help
Usage: pyenv <command> [<args>]
Some useful pyenv commands are:
commands List all available pyenv commands
local Set or show the local application-specific Python version
global Set or show the global Python version
shell Set or show the shell-specific Python version
install Install a Python version using python-build
uninstall Uninstall a specific Python version
rehash Rehash pyenv shims (run this after installing executables)
version Show the current Python version and its origin
versions List all Python versions available to pyenv
which Display the full path to an executable
whence List all Python versions that contain the given executable
See `pyenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/pyenv/pyenv#readme
设置python版本
pyenv shell
➜ ~ pyenv help shell
Usage: pyenv shell <version>...
pyenv shell -
pyenv shell --unset
安装python版本
pyenv install 2.7.10
删除管理中的python版本
➜ versions git:(master) ls
2.7.10
➜ versions git:(master) pwd
~/.pyenv/versions
pyenv-virtualenv¶
pyenv-virtualenv
是一个pyenv
插件, 在类Unix系统上管理Python
virtualenvs
和conda
环境.
通用
git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
Mac 系统
brew install pyenv-virtualenv
添加echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile
到.bashrc
或其他类似此文件功能的文件中.
➜ ~ tail -5 .zshrc
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
创建虚拟环境
➜ ~ pyenv virtualenv 3.6.1 my-virtual-env-3.6.1 # 指定Python版本生成虚拟环境
➜ ~ pyenv version # 查看当前版本
system (set by /Users/boxfish/.pyenv/version)
➜ ~ pyenv virtualenv venv_system # 使用当前版本生成虚拟环境
切换到venv_system虚拟环境
➜ ~ pyenv shell venv_system
(venv_system) ➜ ~
退出虚拟环境
(venv_system) ➜ ~ pyenv shell --unset
➜ ~
查看所有虚拟环境
➜ ~ pyenv virtualenvs
3.6.1/envs/my-virtual-env-3.6.1 (created from /Users/boxfish/.pyenv/versions/3.6.1)
3.6.1/envs/ven36 (created from /Users/boxfish/.pyenv/versions/3.6.1)
my-virtual-env-3.6.1 (created from /Users/boxfish/.pyenv/versions/3.6.1)
ven36 (created from /Users/boxfish/.pyenv/versions/3.6.1)
venv_system (created from /Library/Frameworks/Python.framework/Versions/3.5)
激活,退出虚拟环境
➜ ~ pyenv activate venv_system
pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.
(venv_system) ➜ ~ pyenv deactivate
➜ ~
删除
pyenv uninstall my-virtual-env-3.6.1
There is a venv module available for CPython 3.3 and newer. It provides an executable module venv which is the successor of virtualenv and distributed by default.
pyenv-virtualenv uses python -m venv if it is available and the virtualenv command is not available.
pyenv-virtualenvwrapper¶
错误记录¶
在Mac上安装了Parallels Desktop,然后安装了ubuntu16虚拟机,虚拟机中在用pyenv安装不同版本python的时候,最后失败,提示如下(部分):
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
ERROR: The Python zlib extension was not compiled. Missing the zlib?
如何解决
ubuntu
sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev
mac
brew install readline xz
NOTE: libssl-dev is required when compiling Python, installing libssl-dev will actually install zlib1g-dev, which leads to uninstall and re-install Python versions (installed before installing libssl-dev). On Redhat and derivatives the package is named openssl-devel.
rbac¶
基于角色的权限访问控制(Role-Based Access Control)
selenium¶
Selenium 自动化浏览器
WebDriver¶
方式一: 指定 /path/to/chromedriver
from selenium import webdriver
path = "/Users/xxx/Downloads/chromedriver"
browser = webdriver.Chrome(path)
browser.get("http://www.baidu.com")
print(browser.page_source)
browser.close()
方式二: Start the ChromeDriver server
先运行 chromedriver
capabilities = webdriver.DesiredCapabilities.CHROME.copy()
# capabilities['platform'] = "MAC"
# capabilities['version'] = "10.12"
browser = webdriver.Remote(command_executor="http://127.0.0.1:9515", desired_capabilities=capabilities)
browser.get("http://www.baidu.com")
print(browser.page_source)
headless¶
无头浏览器 PhantomJS
, NightmareJS
等等
最新版本 selenium
不再支持 PhantomJS
Chrome推出了headless mode
可以利用
- ES2017
- ServiceWork(PWA测试随便耍)
- 无沙盒环境
- 无痛通讯&API调用
- 无与伦比的速度
- …
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument("--headless")
# chrome_options.add_argument('--disable-gpu')
driver = webdriver.Chrome(driver_path, options=chrome_options)
当浏览器不支持headless模式,可以利用python 的Xvfb实现伪 headless mode,Xvfb只是产生了一个虚拟窗口,浏览器只是没有在当前窗口显示.
示例
from selenium import webdriver
from xvfbwrapper import Xvfb
xvfb = Xvfb(width=1280,height=720)
xvfb.start()
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
cookies = driver.get_cookies()
print(cookies)
driver.close()
xvfb.stop()
列表 插入 排序¶
socket编程中问题¶
服务端将,内容大小发送给客户端,客户端将此内容转成int的时候,报错
ValueError: invalid literal for int() with base 10:
原因: 粘包
解决: 服务端发送之后,接收一次客户端的确认,再继续发送
uuid 模块¶
>>> import uuid
>>> uuid.uuid4()
UUID('77d8c6da-4433-4b05-8b8e-a15cb6d01f5e')
>>> str(uuid.uuid4()).replace('-','')
'f0a0e57fb81c432d84c283c1d9d82c73'
>>> str(uuid.uuid4()).replace('-','').upper()
'D98DEA99788241B087CE3352E3639BBC'
webapp¶
~ python -V
Python 3.5.1
pip install aiohttp
pip install jinja2
pip install aiomysql
创建项目目录结构
(lxf_webapp) ➜ lxf_webapp git:(master) ✗ tree
.
├── LICENSE
├── backup
├── conf
├── dist
├── ios
└── www
├── static
└── templates
7 directories, 1 file
编写webapp¶
常用Unicode编码范围¶
https://blog.csdn.net/gywtzh0889/article/details/71083459
数字:[0x30,0x39]
(或十进制[48, 57]
)
小写字母:[0x61,0x7a]
(或十进制[97, 122]
)
大写字母:[0x41,0x5a]
(或十进制[65, 90]
) 基本汉字
20902字 4E00-9FA5
(十进制[19968,40869]
)
pip是Python的包管理器,在新的发行版本中会默认安装¶
在命令行下输入
pip list 或者如 pip3 list
比较老的版本可能需要输入
pip freeze 或者如 pip3 freeze
即可显示所有的安装包
默认参数有个最大的坑¶
默认参数必须指向不变对象!
默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:
先定义一个函数,传入一个list,添加一个END再返回:
def add_end(L=[]):
L.append('END')
return L
当你正常调用时,结果似乎不错:
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的:
>>> add_end()
['END']
但是,再次调用add_end()时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了’END’后的list。
原因解释如下:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
所以,定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None这个不变对象来实现:
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
现在,无论调用多少次,都不会有问题:
>>> add_end()
['END']
>>> add_end()
['END']
为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
未分类¶
print¶
print原型¶
end 默认为’:raw-latex:`\n`’
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
"""
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
"""
pass
格式化输出¶
python格式化字符串有%和{}两种 字符串格式控制符.
字符串输入数据格式类型(%格式操作符号)
%%百分号标记
%c字符及其ASCII码
%s字符串
%d有符号整数(十进制)
%u无符号整数(十进制)
%o无符号整数(八进制)
%x无符号整数(十六进制)
%X无符号整数(十六进制大写字符)
%e浮点数字(科学计数法)
%E浮点数字(科学计数法,用E代替e)
%f浮点数字(用小数点符号) 以为小数 %.1f 百分比 %2.1f %%
%g浮点数字(根据值的大小采用%e或%f)
%G浮点数字(类似于%g)
%p指针(用十六进制打印值的内存地址)
%n存储输出字符的数量放进参数列表的下一个变量中
如果字符串里面的 `%` 是一个普通字符,这个时候就需要转义,用 `%%` 来表示一个 `%`
name:可为空,数字(占位),命名(传递参数名,不能以数字开头)以字典格式映射格式化,其为键名
flag:标记格式限定符号,包含+-#和0,+表示右对齐(会显示正负号),-左对齐,前面默认为填充空格(即默认右对齐),0表示填充0,#表示八进制时前面补充0,16进制数填充0x,二进制填充0b
width:宽度(最短长度,包含小数点,小于width时会填充)
precision:小数点后的位数,与C相同
type:输入格式类型,请看上面
用{}包裹name命名传递给format以命名=值 写法,非字典映射,其他和上面相同
fill = <any character>
#fill是表示可以填写任何字符
align = "<" | ">" | "=" | "^"
#align是对齐方式,<是左对齐,
>是右对齐,^是居中对齐。
sign = "+" | "-" | " "
#sign是符号, +表示正号, -表示负号
width = integer
#width是数字宽度,表示总共输出多少位数字
precision = integer
#precision是小数保留位数
type = "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
#type是输出数字值是的表示方式,比如b是二进制表示;比如E是指数表示;比如X是十六进制表示
>>> print("{color}-{what}".format(**dic))
green-apple
print("{:,}".format(123456))#输出1234,56
print("{a:w^8}".format(a="8"))#输出www8wwww,填充w
print("%.5f" %5)#输出5.000000
print("%-7s3" %("python"))#输出python 3
print("%.3e" %2016)#输出2.016e+03,也可以写大E
print("%d %s" %(123456,"myblog"))#输出123456 myblog
>>> print("{}{}{}".format("xxx",".","cn"))
xxx.cn
>>> print("{0}{1}".format("hello","fun"))
hellofun
>>> print("{a[0]}{a[1]}{a[2]}".format(a=["xxx",".","cn"]))
xxx.cn
>>> print("{dict[host]}{dict[dot]}{dict[domain]}".format(dict={"host":"www","domain":"xxx.cn","dot":"."}))
www.xxx.cn
>>> print("{who} {doing} {0}".format("python",doing="like",who="I"))
I like python
>>> print("%(color)s-%(what)s" % {'color': 'red', 'what': 'xxx'})
red-xxx
ruby¶
ruby¶
gem¶
root@rabbit1:~# gem install rubygems-update
root@rabbit1:~# update_rubygems
gem install rubygems-update
使用RVM¶
root@ubuntu75:~/src/logstash-output/logstash-output-zabbix-3.0.0# \curl -sSL https://get.rvm.io | bash -s stable
Downloading https://github.com/rvm/rvm/archive/1.28.0.tar.gz
Downloading https://github.com/rvm/rvm/releases/download/1.28.0/1.28.0.tar.gz.asc
Found PGP signature at: 'https://github.com/rvm/rvm/releases/download/1.28.0/1.28.0.tar.gz.asc',
but no GPG software exists to validate it, skipping.
Creating group 'rvm'
Installing RVM to /usr/local/rvm/
Installation of RVM in /usr/local/rvm/ is almost complete:
* First you need to add all users that will be using rvm to 'rvm' group,
and logout - login again, anyone using rvm will be operating with `umask u=rwx,g=rwx,o=rx`.
* To start using RVM you need to run `source /etc/profile.d/rvm.sh`
in all your open shell windows, in rare cases you need to reopen all shell windows.
# Administrator,
#
# Thank you for using RVM!
# We sincerely hope that RVM helps to make your life easier and more enjoyable!!!
#
# ~Wayne, Michal & team.
In case of problems: https://rvm.io/help and https://twitter.com/rvm_io
root@ubuntu75:~/src/logstash-output/logstash-output-zabbix-3.0.0# usermod -a -G rvm root
root@ubuntu75:~/src/logstash-output/logstash-output-zabbix-3.0.0# id root
uid=0(root) gid=0(root) groups=0(root),1000(rvm)
root@ubuntu75:~/src/logstash-output/logstash-output-zabbix-3.0.0#
rvm install jruby-9.1.6.0
rvm use jruby-9.1.6.0
https://github.com/jruby/jruby
root@ubuntu75:~/src/jruby-9.1.6.0/bin# ll /usr/bin/ruby
lrwxrwxrwx 1 root root 7 Mar 14 2016 /usr/bin/ruby -> ruby2.3*
ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
apt-get install ruby
root@ubuntu75:/usr/share/logstash/bin# pwd
/usr/share/logstash/bin
root@ubuntu75:/usr/share/logstash/bin# ./logstash-plugin install logstash-output-zabbix
修改gem源¶
# 查看gem源
root@ubuntu75:~# gem sources -l
*** CURRENT SOURCES ***
https://rubygems.org/
# 移除
root@ubuntu75:~# gem sources --remove https://rubygems.org/
https://rubygems.org/ removed from sources
# 添加
root@ubuntu75:~# gem sources -a https://ruby.taobao.org
https://ruby.taobao.org added to sources
root@ubuntu75:~# gem sources -l
*** CURRENT SOURCES ***
https://ruby.taobao.org
service¶
GitLab¶
GitLab¶
环境准备¶
存储空间的大小主要取决于你将存储的Git仓库的大小。但根据 rule of thumb(经验法则) 你应该考虑多留一些空间用来存储Git仓库的备份。
如果你想使用弹性的存储空间,你可以考虑在分配分区的时候使用LVM架构,这样可以在后期需要的清空下添加硬盘在增加存储空间。
除此之外你还可以挂在一个支持NFS的分卷,比如NAS、 SAN、AWS、EBS。
如果你的服务器有足够大的内存和CPU处理性能,GitLab的响应速度主要受限于硬盘的寻道时间。 使用更快的硬盘(7200转)或者SSD硬盘会很大程度的提升GitLab的响应速度。
- 1 核心CPU最多支持100个用户,所有的workers和后台任务都在同一个核心工作这将导致GitLab服务响应会有点缓慢。
- 2核心 支持500用户,这也是官方推荐的最低标准。
- 4 核心支持2,000用户。
- 8 核心支持5,000用户。
- 16 核心支持10,000用户。
- 32 核心支持20,000用户。
- 64 核心支持40,000用户。
- 如果想支持更多用户,可以使用 集群式架构
安装使用GitLab需要至少4GB可用内存(RAM + Swap)! 由于操作系统和其他正在运行的应用也会使用内存, 所以安装GitLab前一定要注意当前服务器至少有4GB的可用内存. 少于4GB内存会导致在reconfigure的时候出现各种诡异的问题, 而且在使用过程中也经常会出现500错误.
- 1GB 物理内存 + 3GB 交换分区 是最低的要求,但我们 强烈反对 使用这样的配置。
- 2GB 物理内存 + 2GB 交换分区 支持100用户,但服务响应会很慢。
- 4GB 物理内存 支持100用户,也是 官方推荐 的配置。
- 8GB 物理内存 支持 1,000 用户。
- 16GB 物理内存 支持 2,000 用户。
- 32GB 物理内存 支持 4,000 用户。
- 64GB 物理内存 支持 8,000 用户。
- 128GB 物理内存 支持 16,000 用户。
- 256GB 物理内存 支持 32,000 用户。
如果想支持更多用户,可以使用 集群式架构
即使你服务器有足够多的RAM, 也要给服务器至少分配2GB的交换分区。 因为使用交换分区可以在你的可用内存波动的时候降低GitLab出错的几率。
注意: Sidekiq的25个workers在查看进程(top或者htop)的时候会发现它会单独显示每个worker,但是它们是共享内存分配的,这是因为Sidekiq是一个多线程的程序。 详细内容查看下面关于Unicorn workers 的介绍。
[root@www ~]# cat /etc/redhat-release
CentOS release 6.7 (Final)
[root@www ~]# uname -r
2.6.32-573.el6.x86_64
安装GitLab¶
如想使用Postfix来发送邮件,在安装期间请选择’Internet Site’. 您也可以用sendmai或者 配置SMTP服务并使用SMTP发送邮件.
在 Centos 6 和 7 系统上, 下面的命令将在系统防火墙里面开放HTTP和SSH端口.
sudo yum install curl openssh-server openssh-clients postfix cronie
sudo service postfix start
sudo chkconfig postfix on
sudo lokkit -s http -s ssh
curl -sS http://packages.gitlab.cc/install/gitlab-ce/script.rpm.sh | sudo bash
sudo yum install gitlab-ce
如果你不习惯使用命令管道的安装方式, 你可以在这里下载 安装脚本 或者 手动下载您使用的系统相应的安装包(RPM/Deb) 然后安装
curl -LJO https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el6/gitlab-ce-XXX.rpm
rpm -i gitlab-ce-XXX.rpm
sudo gitlab-ctl reconfigure
首次访问GitLab,系统会让你重新设置管理员的密码,设置成功后会返回登录界面. 默认的管理员账号是root,如果你想更改默认管理员账号,请输入上面设置的新密码登录系统后修改帐号名.
Maven¶
Manve¶
https://maven.apache.org/index.html
Apache Maven是一个软件项目管理综合工具, 基于项目对象模型(POM)的概念, Maven管理项目的构建,报告和文档
安装¶
先决条件: 已安装JDK, 并配置好环境变量
直接使用二进制包安装
- 解压到想要安装的路径
- 配置环境变量
验证:
新开shell窗口执行, mvn -v
Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T15:58:13+08:00)
Maven home: /usr/local/Cellar/maven/3.5.2/libexec
Java version: 1.8.0_91, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.12.6", arch: "x86_64", family: "mac"
MacOS 也可以直接使用brew install maven
安装
运行¶
语法
mvn [options] [<goal(s)>] [<phase(s)>]
可用选项, 及帮助
mvn -h
使用maven生命周周期构建Maven项目(在pom.xml
文件所在的项目目录)
mvn package
内置生命周期顺序如下:
- clean - pre-clean, clean, post-clean
- default - validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy
- site - pre-site, site, post-site, site-deploy
重新构建项目, 生成所有打包输出, 文档站点并部署到仓库
mvn clean deploy site-deploy
仅仅创建包及安装在本地仓库便于其他项目重用可以使用如下命令:
mvn clean install
综上是常见的Maven构建命令
When not working with a project, and in some other use cases, you might want to invoke a specific task implemented by a part of Maven - this is called a goal of a plugin. E.g.:
mvn archetype:generate
or
mvn checkstyle:check
There are many different plugins avaiable and they all implement different goals.
Further resources:
pom.xml¶
<?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: 组织的唯一标识
artifactId: 项目的唯一标识
version: 项目版本
-->
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 在该标签中 定义变量 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
<!-- 项目依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 编译插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
安装第三方jar包¶
mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> \
-DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging>
如果有pom文件, 可以使用如下命令
mvn install:install-file -Dfile=<path-to-file> -DpomFile=<path-to-pomfile>
ZooKeeper¶
ansible¶
Ansible¶
Ansible快速开始¶
sshpass¶
sshpass - noninteractive ssh password provider
sshpass -p 123456 ssh -o StrictHostKeyChecking=no root@172.16.1.41 "hostname"
创建密钥对
ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1
sshpass非交互式工具使用
sshpass -p 123456 ssh -o StrictHostKeyChecking=no root@172.16.1.41 "uptime"
sshpass -p 123456 scp -o StrictHostKeyChecking=no /etc/hosts root@172.16.1.41:~
# -p:指定ssh连接用户的密码
# -o StrictHostKeyChecking=no 避免第一次登录出现公钥检查。
# 非交互分发公钥
sshpass -p111111 ssh-copy-id -i /root/.ssh/id_dsa.pub "-p50517 -o StrictHostKeyChecking=no 172.16.1.41"
# !/bin/bash
for n in 41 31 8
do
sshpass -p111111 ssh-copy-id -i /root/.ssh/id_dsa.pub "-p50517 -o StrictHostKeyChecking=no 172.16.1.$n"
done
[root@manager scripts]# cat key_provider.sh
# !/bin/bash
[ ! -f ~/.ssh/id_dsa.pub ] && ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1
Port=52113
for n in 41 31 8 51 7
do
sshpass -p111111 ssh-copy-id -i ~/.ssh/id_dsa.pub "-p$Port -o StrictHostKeyChecking=no 172.16.1.$n"
done
基于ssh-copy-id 脚本原理 实现的 批量分发秘钥
for n in 31 61 41 7 8
do
sshpass -p 123456 ssh -o StrictHostKeyChecking=no 172.16.1.$n "mkdir -m 700 -p ~/.ssh/"
sshpass -p 123456 scp -o StrictHostKeyChecking=no ~/.ssh/id_dsa.pub yjj@172.16.1.$n:~/.ssh/authorized_keys
sshpass -p 123456 ssh -o StrictHostKeyChecking=no 172.16.1.$n "chmod 600 ~/.ssh/authorized_keys"
/bin/ls -ld /home/yjj/.ssh
/bin/ls -l /home/yjj/.ssh
done
Ansible¶
python语言是运维人员必会的语言!
ansible是一个基于Python开发的自动化运维工具!其功能实现基于SSH远程连接服务!
ansible可以实现批量系统配置、批量软件部署、批量文件拷贝、批量运行命令等功能
- http://docs.ansible.com/ansible/intro_installation.html
- http://www.ansible.com.cn/
- http://docs.ansible.com/modules_by_category.html http://www.ansible.cn/docs/
特点
no agents
:不需要在被管控主机上安装任何客户端;no server
:无服务器端,使用时直接运行命令即可;modules in any languages
:基于模块工作,可使用任意语言开发模块;yaml
,not code:使用yaml语言定制剧本playbook;ssh by default
:基于SSH工作;strong multi-tier solution
:可实现多级指挥。
配置文件
- ansible 应用程序的主配置文件:
/etc/ansible/ansible.cfg
- Host Inventory 定义管控主机 :
/etc/ansible/hosts
[root@db01 scritps]# ansible --version
ansible 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
添加控制组
[root@db01 scritps]# vim /etc/ansible/hosts
## 表示把下面那些地址加入 yjj这个组
[yjj]
172.16.1.41
172.16.1.31
172.16.1.8
172.16.1.7
172.16.1.51
修改端口
[root@db01 scritps]# vim /etc/ansible/ansible.cfg
##
#poll_interval = 15
#sudo_user = root
#ask_sudo_pass = True
#ask_pass = True
#transport = smart
remote_port = 52113
#module_lang = C
#module_set_locale = True
执行命令
[root@db01 scritps]# ansible yjj -m command -a "hostname"
-m MODULE_NAME, --module-name=MODULE_NAME
module name to execute (default=command)
-a MODULE_ARGS, --args=MODULE_ARGS
module arguments
[root@db01 scritps]# ansible yjj -m command -a "grep keepcache /etc/yum.conf"
[root@db01 scritps]# ansible yjj -m command -a "sed -i 's#keepcache=0#keepcache=1#g' /etc/yum.conf"
[root@db01 scritps]# ansible-doc -h
Usage: ansible-doc [options] [module...]
Options:
-h, --help show this help message and exit
-l, --list List available modules
-M MODULE_PATH, --module-path=MODULE_PATH
specify path(s) to module library (default=None)
-s, --snippet Show playbook snippet for specified module(s)
-v, --verbose verbose mode (-vvv for more, -vvvv to enable
connection debugging)
--version show program's version number and exit
查看模块
[root@manager scripts]# ansible-doc -l
a10_server Manage A10 Networks AX/SoftAX/Thunder/vThunder d...
a10_service_group Manage A10 Networks devices' service groups
a10_virtual_server Manage A10 Networks devices' virtual servers
acl Sets and retrieves file ACL information.
add_host add a host (and alternatively a group) to the an...
airbrake_deployment Notify airbrake about app deployments
alternatives Manages alternative programs for common commands
apache2_module enables/disables a module of the Apache2 webserv...
apk Manages apk packages
……
测试远程主机的运行状态
[root@manager scripts]# ansible yjj -m ping
172.16.1.7 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.16.1.41 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.16.1.8 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.16.1.31 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.16.1.51 | SUCCESS => {
"changed": false,
"ping": "pong"
}
在远程主机上执行命令
相关选项如下:
- creates:一个文件名,当该文件存在,则该命令不执行
- free_form:要执行的linux指令
- chdir:在执行指令之前,先切换到该目录
- removes:一个文件名,当该文件不存在,则该选项不执行
- executable:切换shell来执行指令,该执行路径必须是一个绝对路径
[root@manager scripts]# ansible yjj -m command -a "uptime"
172.16.1.7 | SUCCESS | rc=0 >>
14:20:11 up 4:10, 2 users, load average: 0.00, 0.00, 0.00
172.16.1.8 | SUCCESS | rc=0 >>
14:20:11 up 13:46, 5 users, load average: 0.00, 0.00, 0.00
如果需要使用管道,可以使用shell模块。与command不同的是,此模块可以支持命令管道,同时还有另一个模块也具备此功能:raw
设置文件的属性
相关选项如下
- force:需要在两种情况下强制创建软链接,一种是源文件不存在,但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no
- group:定义文件/目录的属组
- mode:定义文件/目录的权限
- owner:定义文件/目录的属主
- path:必选项,定义文件/目录的路径
- recurse:递归设置文件的属性,只对目录有效
- src:被链接的源文件路径,只应用于state=link的情况
- dest:被链接到的路径,只应用于state=link的情况
- state:
- directory:如果目录不存在,就创建目录
- file:即使文件不存在,也不会被创建
- link:创建软链接
- hard:创建硬链接
- touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
- absent:删除目录、文件或者取消链接文件
-a 'path= mode= owner= group= state={file|directory|link|hard|touch|absent} src=(link,链接至何处)'
示例
远程文件符号链接创建
[root@manager scripts]# ansible yjj -m file -a "src=/etc/resolv.conf dest=/tmp/resolv.conf state=link"
172.16.1.8 | SUCCESS => {
"changed": true,
"dest": "/tmp/resolv.conf",
"gid": 0,
"group": "root",
"mode": "0777",
"owner": "root",
"size": 16,
"src": "/etc/resolv.conf",
"state": "link",
"uid": 0
}
172.16.1.7 | SUCCESS => {
"changed": true,
"dest": "/tmp/resolv.conf",
"gid": 0,
"group": "root",
"mode": "0777",
"owner": "root",
"size": 16,
"src": "/etc/resolv.conf",
"state": "link",
"uid": 0
}
远程文件符号链接删除
[root@manager scripts]# ansible yjj -m command -a "ls -al /tmp/resolv.conf"
172.16.1.7 | SUCCESS | rc=0 >>
lrwxrwxrwx 1 root root 16 Oct 15 14:14 /tmp/resolv.conf -> /etc/resolv.conf
172.16.1.8 | SUCCESS | rc=0 >>
lrwxrwxrwx 1 root root 16 Oct 15 14:14 /tmp/resolv.conf -> /etc/resolv.conf
复制文件到远程主机
相关选项如下
- backup:在覆盖之前,将源文件备份,备份文件包含时间信息。有两个选项:yes|no
- content:用于替代“src”,可以直接设定指定文件的值
- dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录
- directory_mode:递归设定目录的权限,默认为系统默认权限
- force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes
- others:所有的file模块里的选项都可以在这里使用
- src:被复制到远程主机的本地文件,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用“/”来结尾,则只复制目录里的内容,如果没有使用“/”来结尾,则包含目录在内的整个内容全部复制,类似于rsync。
[root@db01 scritps]# ansible yjj -m copy -a "src=/etc/passwd dest=/root/yjj.txt owner=root group=root mode=0755"
172.16.1.31 | SUCCESS => {
"changed": true,
"checksum": "ca4c1e38e150b4de43a9a0fb13dc18e33d901d2e",
"dest": "/root/yjj.txt",
"gid": 0,
"group": "root",
"md5sum": "617c0932c7ac8c71de3dcffbb243cbdd",
"mode": "0755",
"owner": "root",
"size": 1171,
"src": "/root/.ansible/tmp/ansible-tmp-1476442649.82-254246058554780/source",
"state": "file",
"uid": 0
}
将本地文件“/etc/ansible/ansible.cfg”复制到远程服务器
[root@manager scripts]# ansible yjj -m copy -a "src=/etc/ansible/ansible.cfg dest=/tmp/ansible.cfg owner=root group=root mode=0644"
172.16.1.8 | SUCCESS => {
"changed": true,
"checksum": "d84066f339afd959b46f5d4775192d2fe6772edc",
"dest": "/tmp/ansible.cfg",
"gid": 0,
"group": "root",
"md5sum": "7565671ad8d7502d26b4e158a4e85c95",
"mode": "0644",
"owner": "root",
"size": 13821,
"src": "/root/.ansible/tmp/ansible-tmp-1476512336.97-153144333074174/source",
"state": "file",
"uid": 0
}
172.16.1.7 | SUCCESS => {
"changed": true,
"checksum": "d84066f339afd959b46f5d4775192d2fe6772edc",
"dest": "/tmp/ansible.cfg",
"gid": 0,
"group": "root",
"md5sum": "7565671ad8d7502d26b4e158a4e85c95",
"mode": "0644",
"owner": "root",
"size": 13821,
"src": "/root/.ansible/tmp/ansible-tmp-1476512336.94-250207092553974/source",
"state": "file",
"uid": 0
}
切换到某个shell执行指定的指令,参数与command相同。
与command不同的是,此模块可以支持命令管道,同时还有另一个模块也具备此功能:raw
-a 'command'
运行shell命令
ansible all -m shell -a echo "123456789" |passwd --stdin user1'
[root@manager scripts]# ansible 172.16.1.31 -m shell -a 'useradd user1 && echo "123456789" |passwd --stdin user1'
定时任务管理
-a 'name= state= minute= hour= day= month= weekday= job='
[root@manager scripts]# ansible yjj -m cron -a 'name="time sync" state=present minute="*/5" job="/usr/sbin/ntpdate ntp1.aliyun.com >/dev/null 2>&1"'
172.16.1.8 | SUCCESS => {
"changed": true,
"envs": [],
"jobs": [
"Time",
"time sync"
]
}
172.16.1.7 | SUCCESS => {
"changed": true,
"envs": [],
"jobs": [
"Time",
"time sync"
]
}
验证
# Ansible: time sync
*/5 * * * * /usr/sbin/ntpdate ntp1.aliyun.com >/dev/null 2>&1
系统用户管理
[root@manager scripts]# ansible yjj -m user -a 'name=yjj shell=/bin/bash comment="wodege" uid=888'
172.16.1.8 | SUCCESS => {
"changed": true,
"comment": "wodege",
"createhome": true,
"group": 888,
"home": "/home/yjj",
"name": "yjj",
"shell": "/bin/bash",
"state": "present",
"system": false,
"uid": 888
}
172.16.1.7 | SUCCESS => {
"changed": true,
"comment": "wodege",
"createhome": true,
"group": 888,
"home": "/home/yjj",
"name": "yjj",
"shell": "/bin/bash",
"state": "present",
"system": false,
"uid": 888
}
[root@web01 ~]# tail -1 /etc/passwd
yjj:x:888:888:wodege:/home/yjj:/bin/bash
系统用户组管理
系统服务管理
-a 'name= state={started|stopped|restarted} enabled=(是否开机自动启动) runlevel='
#state must be one of: running,started,stopped,restarted,reloaded,
[root@manager scripts]# ansible yjj -m service -a 'name=crond state=started'
172.16.1.7 | SUCCESS => {
"changed": false,
"name": "crond",
"state": "started"
}
172.16.1.8 | SUCCESS => {
"changed": false,
"name": "crond",
"state": "started"
}
The script module takes the script name followed by a list of space-delimited arguments. The local script at path will be transferred to the remote node and then executed. The given script will be processed through the shell environment on the remote node. This module does not require python on the remote system, much like the [raw] module.
远程执行 -a '/PATH/TO/SCRIPT' 运行脚本
ansible all -m script -a '/tmp/a.sh'
[root@db01 scritps]# ansible 172.16.1.31 -m script -a '/server/scritps/1.sh'
apache¶
apache¶
Apache-2.4.23安装方法¶
http://apr.apache.org/download.cgi
[root@web src]# pwd
/root/src
[root@web src]# wget http://mirrors.tuna.tsinghua.edu.cn/apache//apr/apr-1.5.2.tar.gz
[root@web src]# wget http://mirrors.tuna.tsinghua.edu.cn/apache//apr/apr-util-1.5.4.tar.gz
yum install pcre-devel openssl-devel -y
安装apr-1.5.2,低版本会安装不上event模块。
[root@web src]# ls
apr-1.5.2.tar.gz apr-util-1.5.4.tar.gz httpd-2.4.23 httpd-2.4.23.tar.gz
[root@web src]# tar xf apr-1.5.2.tar.gz
[root@web src]# cd apr-1.5.2
[root@web apr-1.5.2]# ./configure --prefix=/usr/local/apr-1.5.2
[root@web apr-1.5.2]# make && make install
安装apr-util-1.5.4.
./configure --prefix=/usr/local/apr1.5 --with-apr=/usr/local/apr1.5
#这个是apr的工具集,它依赖于上面的那个apr, 所以加上--with来指定我们安装apr的目录。
[root@web apr-util-1.5.4]# ./configure --prefix=/usr/local/apr-util-1.5.4 --with-apr=/usr/local/apr-1.5.2
[root@web apr-util-1.5.4]# make && make install
[root@web httpd-2.4.23]# ./configure --prefix=/application/apache-2.4.23 --enable-so --enable-ssl --enable-rewrite --enable-cgi --with-zlib --with-pcre --with-apr=/usr/local/apr-1.5.2/ --with-apr-util=/usr/local/apr-util-1.5.4/ --enable-modules=most --enable-mpms-shared=all --with-mpm=worker
[root@web httpd-2.4.23]# make && make install
ln -s /application/apache-2.4.23/ /application/apache
cd /application/apache/htdocs/
mkdir -p bbs www blog
echo 'apache www' >www/index.html
echo 'apache bbs' >bbs/index.html
echo 'apache blog' >blog/index.html
# /etc/httpd24 :编译安装时指定的配置文件目录;
# /etc/httpd24/httpd.conf :主配置文件
# /etc/httpd24/extra/httpd-default.conf :默认配置文件,keepalive、AccessFileName等设置;
# /etc/httpd24/extra/httpd-userdir.conf :用户目录配置文件;
# /etc/httpd24/extra/httpd-mpm.conf :MPM配置文件;
# /etc/httpd24/extra/httpd-ssl.conf :SSL配置文件,为站点提供https协议;
# /etc/httpd24/extra/httpd-vhosts.conf :虚拟主机配置文件;
# /etc/httpd24/extra/httpd-info.conf :server-status页面配置文件;
yum install zlib-devel libxml2-devel libjpeg-devel libiconv-devel -y
yum install freetype-devel libpng-devel gd-devel curl-devel libxslt-devel -y
yum -y install libmcrypt-devel mhash mhash-devel mcrypt -y
yum install libxslt-devel -y
yum install openssl-devel -y
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
tar zxf libiconv-1.14.tar.gz
cd libiconv-1.14
./configure --prefix=/usr/local/libiconv
make
make install
./configure \
--prefix=/application/php-5.5.38 \
--with-apxs2=/application/apache/bin/apxs \
--with-mysql=mysqlnd \
--with-pdo-mysql=mysqlnd \
--with-iconv-dir=/usr/local/libiconv \
--with-freetype-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib \
--with-libxml-dir=/usr \
--enable-xml \
--disable-rpath \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-inline-optimization \
--with-curl \
--enable-mbregex \
--enable-mbstring \
--with-mcrypt \
--with-gd \
--enable-gd-native-ttf \
--with-mhash \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-soap \
--enable-short-tags \
--enable-static \
--with-xsl \
--enable-ftp
make
make install
ln -s /application/php-5.5.38/ /application/php
ls /application/php/
<IfModule dir_module>
DirectoryIndex index.php index.html
</IfModule>
问题¶
[root@web application]# ./apache/bin/apachectl stop
[Mon Oct 17 11:28:42.619595 2016] [:crit] [pid 54470:tid 140324353431296] Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe. You need to recompile PHP.
AH00013: Pre-configuration failed
在linux下编译,配合此版本,php需去掉--with-openssl 参数。
elk¶
Elastic Stack安装¶
本文环境¶
root@ubuntu75:~# lsb_release -a
LSB Version: core-9.20160110ubuntu0.2-amd64:core-9.20160110ubuntu0.2-noarch:security-9.20160110ubuntu0.2-amd64:security-9.20160110ubuntu0.2-noarch
Distributor ID: Ubuntu
Description: Ubuntu 16.04.1 LTS
Release: 16.04
Codename: xenial
安装Java环境¶
两种方式
oracle下载jdk需要认证,所以直接到官网下载,或者使用如下方法下载,注意版本
root@ubuntu75:~/src# pwd
/root/src
root@ubuntu75:~/src# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u101-b13/jdk-8u101-linux-x64.tar.gz
tar xf jdk-8u101-linux-x64.tar.gz
mv jdk1.8.0_101/ /opt/
ln -s /opt/jdk1.8.0_101/ /opt/jdk
配置环境变量 vim /etc/profile
export JAVA_HOME=/opt/jdk
export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
或使用命令:sed -i.ori '$a export JAVA_HOME=/opt/jdk\nexport PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH\nexport CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar' /etc/profile
. /etc/profile
运行java -version命令,能查到java版本,则安装成功
安装Elastic Stack¶
针对Ubuntu,推荐全部使用deb包进行安装
wget -q https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.1.2.deb &
sha1sum elasticsearch-5.1.2.deb
sudo dpkg -i elasticsearch-5.1.2.deb
wget -q https://artifacts.elastic.co/downloads/logstash/logstash-5.1.2.deb &
dpkg -i logstash-5.1.2.deb
wget -q https://artifacts.elastic.co/downloads/kibana/kibana-5.1.2-amd64.deb &
sha1sum kibana-5.1.2-amd64.deb
sudo dpkg -i kibana-5.1.2-amd64.deb
参考链接
## 安装elasticsearch
sudo apt-get install apt-transport-https
echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-5.x.list
sudo apt-get update && sudo apt-get install elasticsearch
配置文件路径:
/etc/elasticsearch/elasticsearch.yml
/etc/elasticsearch/logging.yml
启动方法:
root@ubuntu75:~# ps -p 1
PID TTY TIME CMD
1 ? 00:00:02 systemd
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable elasticsearch.service
sudo systemctl start elasticsearch.service
sudo systemctl stop elasticsearch.service
## 安装logstash
wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add -
cat >> /etc/apt/sources.list <<EOF
deb http://packages.elasticsearch.org/logstash/5.0/debian stable main
EOF
apt-get update
apt-get install logstash
报错¶
安装时报如下错误:
Selecting previously unselected package logstash.
(Reading database ... 101074 files and directories currently installed.)
Preparing to unpack logstash-5.1.2.deb ...
Unpacking logstash (1:5.1.2-1) ...
Setting up logstash (1:5.1.2-1) ...
Using provided startup.options file: /etc/logstash/startup.options
/usr/share/logstash/vendor/jruby/bin/jruby: line 388: /usr/bin/java: No such file or directory
Unable to install system startup script for Logstash.
解决
root@ubuntu75:~/src# ln -s /opt/jdk/bin/java /usr/bin/java
root@ubuntu75:~/src# ln -s /opt/jdk/bin/javac /usr/bin/javac
elasticsearch¶
配置文件¶
cat >>/etc/elasticsearch/elasticsearch.yml<<EOF
cluster.name: elk-cluster
node.name: node-1
path.data: /data/es-data
path.log: /data/es-data
network.host: 0.0.0.0
http.port: 9200
discovery.zen.ping.unicast.hosts: ["10.174.217.111","10.174.214.247"]
discovery.zen.minimum_master_nodes: 1
EOF
避免脑裂现象,用到的一个参数是:discovery.zen.minimum_master_nodes。这个参数决定了要选举一个Master需要多少个节点(最少候选节点数)。默认值是1。根据一般经验这个一般设置成 N/2 + 1,N是集群中节点的数量,例如一个有3个节点的集群,minimum_master_nodes 应该被设置成 3/2 + 1 = 2(向下取整)。
用到的另外一个参数是:discovery.zen.ping.timeout,等待ping响应的超时时间,默认值是3秒。如果网络缓慢或拥塞,建议略微调大这个值。这个参数不仅仅适应更高的网络延迟,也适用于在一个由于超负荷而响应缓慢的节点的情况。
如果您刚开始使用elasticsearch,建议搭建拥有3个节点的集群,这种方式可以把discovery.zen.minimum_master_nodes设置成2,这样就限制了发生脑裂现象的可能,且保持着高度的可用性:如果你设置了副本,在丢失一个节点的情况下,集群仍可运行
/etc/elasticsearch/jvm.options
-Xms2g
-Xmx2g
启动¶
Running Elasticsearch with SysV initedit
Use the update-rc.d command to configure Elasticsearch to start automatically when the system boots up:
sudo update-rc.d elasticsearch defaults 95 10
Elasticsearch can be started and stopped using the service command:
sudo -i service elasticsearch start
sudo -i service elasticsearch stop
If Elasticsearch fails to start for any reason, it will print the reason for failure to STDOUT. Log files can be found in /var/log/elasticsearch/.
Running Elasticsearch with systemdedit
To configure Elasticsearch to start automatically when the system boots up, run the following commands:
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable elasticsearch.service
Elasticsearch can be started and stopped as follows:
sudo systemctl start elasticsearch.service
sudo systemctl stop elasticsearch.service
elasticsearch-head¶
A web front end for an Elasticsearch cluster
Running as a plugin of Elasticsearch
Install elasticsearch-head:
– for Elasticsearch 5.x:
site plugins are not supported. Run elasticsearch-head as a standalone server
先决条件
1. git
2. node
3. grunt-cli
4. phantomjs
安装步骤
1. npm install -g cnpm --registry=https://registry.npm.taobao.org
2. cnpm install -g grunt-cli
3. git clone git://github.com/mobz/elasticsearch-head.git
4. cd elasticsearch-head
5. cnpm install
6. grunt server
使用screen一直运行,安全问题如何解决?
使用curl命令操作elasticsearch¶
_cat系列提供了一系列查询elasticsearch集群状态的接口。你可以通过执行
curl -XGET localhost:9200/_cat
获取所有_cat系列的操作
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
你也可以后面加一个v,让输出内容表格显示表头,举例
name component version type url
Prometheus analysis-mmseg NA j
Prometheus analysis-pinyin NA j
Prometheus analysis-ik NA j
Prometheus analysis-ik NA j
Prometheus analysis-smartcn 2.1.0 j
Prometheus segmentspy NA s /_plugin/segmentspy/
Prometheus head NA s /_plugin/head/
Prometheus bigdesk NA s /_plugin/bigdesk/
Xandu analysis-ik NA j
Xandu analysis-pinyin NA j
Xandu analysis-mmseg NA j
Xandu analysis-smartcn 2.1.0 j
Xandu head NA s /_plugin/head/
Xandu bigdesk NA s /_plugin/bigdesk/
Onyxx analysis-ik NA j
Onyxx analysis-mmseg NA j
Onyxx analysis-smartcn 2.1.0 j
Onyxx analysis-pinyin NA j
Onyxx head NA s /_plugin/head/
Onyxx bigdesk NA s /_plugin/bigdesk/
1. 查询设置集群状态
curl -XGET localhost:9200/_cluster/health?pretty=true
pretty=true 表示格式化输出
level=indices 表示显示索引状态
level=shards 表示显示分片信息
2. 显示集群系统信息,包括CPU JVM等等
curl -XGET localhost:9200/_cluster/stats?pretty=true
3. 集群的详细信息。包括节点、分片等。
curl -XGET localhost:9200/_cluster/state?pretty=true
4. 获取集群堆积的任务
curl -XGET localhost:9200/_cluster/pending_tasks?pretty=true
3、修改集群配置
举例:
curl -XPUT localhost:9200/_cluster/settings -d '{
"persistent" : {
"discovery.zen.minimum_master_nodes" : 2
}
}'
transient 表示临时的,persistent表示永久的
4、curl -XPOST ‘localhost:9200/_cluster/reroute’ -d ‘xxxxxx’
对shard的手动控制,参考http://zhaoyanblog.com/archives/687.html
5、关闭节点
关闭指定192.168.1.1节点
curl -XPOST ‘http://192.168.1.1:9200/_cluster/nodes/_local/_shutdown’
curl -XPOST ‘http://localhost:9200/_cluster/nodes/192.168.1.1/_shutdown’
关闭主节点
curl -XPOST ‘http://localhost:9200/_cluster/nodes/_master/_shutdown’
关闭整个集群
$ curl -XPOST ‘http://localhost:9200/_shutdown?delay=10s’
$ curl -XPOST ‘http://localhost:9200/_cluster/nodes/_shutdown’
$ curl -XPOST ‘http://localhost:9200/_cluster/nodes/_all/_shutdown’
delay=10s表示延迟10秒关闭
1、查询节点的状态
curl -XGET ‘http://localhost:9200/_nodes/stats?pretty=true’
curl -XGET ‘http://localhost:9200/_nodes/192.168.1.2/stats?pretty=true’
curl -XGET ‘http://localhost:9200/_nodes/process’
curl -XGET ‘http://localhost:9200/_nodes/_all/process’
curl -XGET ‘http://localhost:9200/_nodes/192.168.1.2,192.168.1.3/jvm,process’
curl -XGET ‘http://localhost:9200/_nodes/192.168.1.2,192.168.1.3/info/jvm,process’
curl -XGET ‘http://localhost:9200/_nodes/192.168.1.2,192.168.1.3/_all
curl -XGET ‘http://localhost:9200/_nodes/hot_threads
1、获取索引
curl -XGET ‘http://localhost:9200/{index}/{type}/{id}’
2、索引数据
curl -XPOST ‘http://localhost:9200/{index}/{type}/{id}’ -d'{“a”:”avalue”,”b”:”bvalue”}’
3、删除索引
curl -XDELETE ‘http://localhost:9200/{index}/{type}/{id}’
4、设置mapping
curl -XPUT http://localhost:9200/{index}/{type}/_mapping -d '{
"{type}" : {
"properties" : {
"date" : {
"type" : "long"
},
"name" : {
"type" : "string",
"index" : "not_analyzed"
},
"status" : {
"type" : "integer"
},
"type" : {
"type" : "integer"
}
}
}
}'
5、获取mapping
curl -XGET http://localhost:9200/{index}/{type}/_mapping
6、搜索
curl -XGET 'http://localhost:9200/{index}/{type}/_search' -d '{
"query" : {
"term" : { "user" : "kimchy" } //查所有 "match_all": {}
},
"sort" : [{ "age" : {"order" : "asc"}},{ "name" : "desc" } ],
"from":0,
"size":100
}
curl -XGET 'http://localhost:9200/{index}/{type}/_search' -d '{
"filter": {"and":{"filters":[{"term":{"age":"123"}},{"term":{"name":"张三"}}]},
"sort" : [{ "age" : {"order" : "asc"}},{ "name" : "desc" } ],
"from":0,
"size":100
}
报错信息¶
npm ERR! Failed at the phantomjs-prebuilt@2.1.14 install script 'node install.js'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the phantomjs-prebuilt package,
如果报以上错误,可能是因为网络问题,可以使用淘宝NPM镜像
[2017-02-15T14:02:19,113][INFO ][o.e.n.Node ] [node-1] starting ...
[2017-02-15T14:02:19,415][INFO ][o.e.t.TransportService ] [node-1] publish_address {121.42.244.47:9300}, bound_addresses {0.0.0.0:9300}
[2017-02-15T14:02:19,420][INFO ][o.e.b.BootstrapCheck ] [node-1] bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks
[2017-02-15T14:02:19,431][ERROR][o.e.b.Bootstrap ] [node-1] node validation exception
bootstrap checks failed
max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
[2017-02-15T14:02:19,441][INFO ][o.e.n.Node ] [node-1] stopping ...
[2017-02-15T14:02:19,536][INFO ][o.e.n.Node ] [node-1] stopped
[2017-02-15T14:02:19,536][INFO ][o.e.n.Node ] [node-1] closing ...
[2017-02-15T14:02:19,558][INFO ][o.e.n.Node ] [node-1] closed
是因为操作系统的vm.max_map_count参数设置太小导致的,执行以下命令:
sysctl -w vm.max_map_count=655360
并用以下命令查看是否修改成功
sysctl -a | grep "vm.max_map_count"
如果能正常输出655360,则说明修改成功,然后再次启动elasticsearch
把配置好的安装包拷贝一份到其他两台机器上,修改 config/elasticsearch.yml下的node.name和network.host为对于的机器即可。
sysctl -w vm.max_map_count=655360
结果:vm.max_map_count = 655360
情况:Ubuntu查看日志
执行
systemctl start elasticsearch.service
检查端口不存在,没有生成日志文件
ls /var/log/elasticsearch/查看无内容
查看系统日志
tailf /var/log/syslog
Feb 15 15:30:58 iZm5e7si86xwstzpneime7Z systemd[1]: Started Elasticsearch.
Feb 15 15:30:58 iZm5e7si86xwstzpneime7Z elasticsearch[10379]: Could not find any executable java binary. Please install java in your PATH or set JAVA_HOME
Feb 15 15:30:58 iZm5e7si86xwstzpneime7Z systemd[1]: elasticsearch.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 15:30:58 iZm5e7si86xwstzpneime7Z systemd[1]: elasticsearch.service: Unit entered failed state.
Feb 15 15:30:58 iZm5e7si86xwstzpneime7Z systemd[1]: elasticsearch.service: Failed with result 'exit-code'.
提示java不存在
已经安装java,java -version能显示版本,此时注意查看/usr/bin/java是否存在 解决办法,创建软连接
root@ubuntu66:~# ln -s /opt/jdk/bin/java /usr/bin/java
root@ubuntu66:~# ln -s /opt/jdk/bin/javac /usr/bin/javac
Feb 15 16:28:16 iZm5e7si86xwstzpneime7Z elasticsearch[11697]: Exception in thread "main" SettingsException[Failed to load settings from /etc/elasticsearch/elasticsearch.yml]; nested: AccessDeniedException[/etc/elasticsearch/elasticsearch.yml];
权限问题,解决办法
注意Elastic Stack对应成员的配置文件属主,归属组均要给对应成员
root@ubuntu66:/etc/elasticsearch# ls -l
total 20
-rwxr-x--- 1 root root 3153 Feb 15 15:03 elasticsearch.yml
-rwxr-x--- 1 root root 3160 Feb 15 14:10 elasticsearch.yml.bak
-rwxr-x--- 1 root elasticsearch 2668 Feb 15 14:08 jvm.options
-rwxr-x--- 1 root elasticsearch 3988 Oct 26 12:40 log4j2.properties
drwxr-x--- 2 root elasticsearch 4096 Oct 26 12:40 scripts
root@ubuntu66:/etc/elasticsearch# chown root.elasticsearch elasticsearch.yml
root@ubuntu66:/etc/elasticsearch# chown root.elasticsearch elasticsearch.yml.bak
logstash¶
配置¶
root@ubuntu47:/etc/logstash# grep -Ev "^$|#" logstash.yml
path.data: /var/lib/logstash
path.config: /etc/logstash/conf.d
path.logs: /var/log/logstash
以收集Nginx日志为例,需要先配置Nginx生成的日志格式为json格式(具体设置参考Nginx文档).
root@ubuntu47:/etc/logstash/conf.d# cat 1
cat filter.conf
filter {
if [type] == "nginx-access" {
json {
source => "message"
remove_field => [ "Arg0","Arg1","Arg2","Arg3","Arg4","Arg5","Arg6","Arg7","Arg8","Arg3","Arg9","Arg10" ]
}
mutate {
split => [ "upstreamtime", "," ]
}
mutate {
convert => [ "upstreamtime", "float" ]
}
#if [status] == 304 {
# mutate {
# add_field => { "[@metadata][zabbix_key]" => "nginx_status" }
# add_field => { "[@metadata][zabbix_host]" => "ubuntu47" }
# }
#}
# output-zabbix 插件配置
if [type] == "nginx-access" {
if [request_method] == "GET" {
mutate {
add_field => { "[@metadata][zabbix_key]" => "nginx_request_method" }
add_field => { "[@metadata][zabbix_host]" => "ubuntu47" }
}
}
}
}
}
cat output.conf
output {
if [type] == "nginx-access" {
elasticsearch {
user => logstash
password => logstash
# search guard相关配置
# ssl => true
# ssl_certificate_verification => true
# truststore => "/etc/logstash/truststore.jks"
# truststore_password => "82df5ddf119275a190e0"
hosts => "127.0.0.1:9200"
index => "logstash-%{type}"
document_type => "%{type}"
sniffing => false
manage_template => false
flush_size => 20000
idle_flush_time => 10
template_overwrite => true
}
}
#if [type] == "nginx-access" {
# if [status] == 304 {
# zabbix {
# zabbix_server_host => "10.29.164.37"
# zabbix_host => "[@metadata][zabbix_host]"
# zabbix_key => "[@metadata][zabbix_key]"
# zabbix_value => "status"
# }
#}
#}
# output-zabbix 插件配置
if [type] == "nginx-access" {
if [request_method] == "GET" {
zabbix {
zabbix_server_host => "10.29.164.37"
zabbix_host => "[@metadata][zabbix_host]"
zabbix_key => "[@metadata][zabbix_key]"
zabbix_value => "request_method"
}
}
}
}
logstash启动文件¶
使用logstash启动文件 进行启动即可
cat /etc/init.d/logstash¶
#!/bin/sh
# Init script for logstash
# Maintained by Elasticsearch
# Generated by pleaserun.
# Implemented based on LSB Core 3.1:
# * Sections: 20.2, 20.3
#
### BEGIN INIT INFO
# Provides: logstash
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description:
# Description: Starts Logstash as a daemon.
### END INIT INFO
PATH=/sbin:/usr/sbin:/bin:/usr/bin
export PATH
if [ `id -u` -ne 0 ]; then
echo "You need root privileges to run this script"
exit 1
fi
name=logstash
pidfile="/var/run/$name.pid"
LS_USER=logstash
LS_GROUP=logstash
LS_HOME=/var/lib/logstash
LS_HEAP_SIZE="1g"
LS_LOG_DIR=/var/log/logstash
LS_LOG_FILE="${LS_LOG_DIR}/$name.log"
LS_CONF_DIR=/etc/logstash/conf.d
LS_OPEN_FILES=16384
LS_NICE=19
KILL_ON_STOP_TIMEOUT=${KILL_ON_STOP_TIMEOUT-0} #default value is zero to this variable but could be updated by user request
LS_OPTS="--path.settings=/etc/logstash/ --log.level warn"
[ -r /etc/default/$name ] && . /etc/default/$name
[ -r /etc/sysconfig/$name ] && . /etc/sysconfig/$name
program=/usr/share/logstash/bin/logstash
#args="agent -f ${LS_CONF_DIR} -l ${LS_LOG_FILE} ${LS_OPTS}"
args="${LS_OPTS}"
quiet() {
"$@" > /dev/null 2>&1
return $?
}
start() {
LS_JAVA_OPTS="${LS_JAVA_OPTS} -Djava.io.tmpdir=${LS_HOME}"
HOME=${LS_HOME}
export PATH HOME LS_HEAP_SIZE LS_JAVA_OPTS LS_USE_GC_LOGGING LS_GC_LOG_FILE
# chown doesn't grab the suplimental groups when setting the user:group - so we have to do it for it.
# Boy, I hope we're root here.
SGROUPS=$(id -Gn "$LS_USER" | tr " " "," | sed 's/,$//'; echo '')
if [ ! -z $SGROUPS ]
then
EXTRA_GROUPS="--groups $SGROUPS"
fi
# set ulimit as (root, presumably) first, before we drop privileges
ulimit -n ${LS_OPEN_FILES}
# Run the program!
nice -n ${LS_NICE} chroot --userspec $LS_USER:$LS_GROUP $EXTRA_GROUPS / sh -c "
cd $LS_HOME
ulimit -n ${LS_OPEN_FILES}
exec \"$program\" $args
" > "${LS_LOG_DIR}/$name.stdout" 2> "${LS_LOG_DIR}/$name.err" &
# Generate the pidfile from here. If we instead made the forked process
# generate it there will be a race condition between the pidfile writing
# and a process possibly asking for status.
echo $! > $pidfile
echo "$name started."
return 0
}
stop() {
# Try a few times to kill TERM the program
if status ; then
pid=`cat "$pidfile"`
echo "Killing $name (pid $pid) with SIGTERM"
kill -9 $pid
# Wait for it to exit.
for i in 1 2 3 4 5 6 7 8 9 ; do
echo "Waiting $name (pid $pid) to die..."
status || break
sleep 1
done
if status ; then
if [ $KILL_ON_STOP_TIMEOUT -eq 1 ] ; then
echo "Timeout reached. Killing $name (pid $pid) with SIGKILL. This may result in data loss."
kill -KILL $pid
echo "$name killed with SIGKILL."
else
echo "$name stop failed; still running."
return 1 # stop timed out and not forced
fi
else
echo "$name stopped."
fi
fi
}
status() {
if [ -f "$pidfile" ] ; then
pid=`cat "$pidfile"`
if kill -0 $pid > /dev/null 2> /dev/null ; then
# process by this pid is running.
# It may not be our pid, but that's what you get with just pidfiles.
# TODO(sissel): Check if this process seems to be the same as the one we
# expect. It'd be nice to use flock here, but flock uses fork, not exec,
# so it makes it quite awkward to use in this case.
return 0
else
return 2 # program is dead but pid file exists
fi
else
return 3 # program is not running
fi
}
reload() {
if status ; then
kill -HUP `cat "$pidfile"`
fi
}
force_stop() {
if status ; then
stop
status && kill -KILL `cat "$pidfile"`
fi
}
configtest() {
# Check if a config file exists
if [ ! "$(ls -A ${LS_CONF_DIR}/* 2> /dev/null)" ]; then
echo "There aren't any configuration files in ${LS_CONF_DIR}"
return 1
fi
HOME=${LS_HOME}
export PATH HOME
test_args="--configtest -f ${LS_CONF_DIR} ${LS_OPTS}"
$program ${test_args}
[ $? -eq 0 ] && return 0
# Program not configured
return 6
}
case "$1" in
start)
status
code=$?
if [ $code -eq 0 ]; then
echo "$name is already running"
else
start
code=$?
fi
exit $code
;;
stop)
stop
;;
force-stop)
force_stop
;;
status)
status
code=$?
if [ $code -eq 0 ] ; then
echo "$name is running"
else
echo "$name is not running"
fi
exit $code
;;
reload)
reload
;;
restart)
quiet configtest
RET=$?
if [ ${RET} -ne 0 ]; then
echo "Configuration error. Not restarting. Re-run with configtest parameter for details"
exit ${RET}
fi
stop && start
;;
configtest)
configtest
exit $?
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|force-stop|status|reload|restart|configtest}" >&2
exit 3
;;
esac
exit $?
Nginx配置¶
主配置文件¶
日志使用json格式
root@ubuntu75:/etc/nginx# egrep -v "^$|#" nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_prefer_server_ciphers on;
log_format json '{'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"time_local":"$time_local",'
'"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"request_method":"$request_method",'
'"request":"$request",'
'"uri":"$uri",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_status":"$upstream_status",'
'"upstream_addr":"$upstream_addr"'
'}';
access_log /var/log/nginx/access.log json;
error_log /var/log/nginx/error.log ;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
转发请求到本地5601¶
root@ubuntu75:/etc/nginx# cat conf.d/kibana.conf
server {
listen 80;
server_name _;
# auth_basic "Restricted Access";
# auth_basic_user_file /etc/nginx/htpasswd.users;
location / {
proxy_pass http://127.0.0.1:5601;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
添加密码认证¶
使用htpasswd生成用户名及对应密码文件
root@ubuntu75:/etc/nginx# htpasswd -c /etc/nginx/htpasswd.users yjj
New password:
Re-type new password:
Adding password for user yjj
运行nginx的用户需要有密码文件的读权限
filebeat¶
Filebeat is a log data shipper. Installed as an agent on your servers, Filebeat monitors the log directories or specific log files, tails the files, and forwards them either to Elasticsearch or Logstash for indexing.
Filebeat 用来做数据转发,作为代理安装在服务器上,Filebeat监控日志目录,或者指定日志文件,
安装¶
wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-5.2.0-amd64.deb
sudo dpkg -i filebeat-5.2.0-amd64.deb
使用filebeat¶
root@ubuntu75:/etc/filebeat# /usr/bin/filebeat.sh -configtest -e .
2017/02/06 03:54:11.053687 beat.go:267: INFO Home path: [/usr/share/filebeat] Config path: [/etc/filebeat] Data path: [/var/lib/filebeat] Logs path: [/var/log/filebeat]
2017/02/06 03:54:11.053893 beat.go:177: INFO Setup Beat: filebeat; Version: 5.2.0
2017/02/06 03:54:11.053863 logp.go:219: INFO Metrics logging every 30s
2017/02/06 03:54:11.054148 output.go:167: INFO Loading template enabled. Reading template file: /etc/filebeat/filebeat.template.json
2017/02/06 03:54:11.054478 output.go:178: INFO Loading template enabled for Elasticsearch 2.x. Reading template file: /etc/filebeat/filebeat.template-es2x.json
2017/02/06 03:54:11.054735 client.go:120: INFO Elasticsearch url: http://localhost:9200
2017/02/06 03:54:11.054824 outputs.go:106: INFO Activated elasticsearch as output plugin.
2017/02/06 03:54:11.054947 publish.go:291: INFO Publisher name: ubuntu75
2017/02/06 03:54:11.055179 async.go:63: INFO Flush Interval set to: 1s
2017/02/06 03:54:11.055237 async.go:64: INFO Max Bulk Size set to: 50
Config OK
filebeat配置¶
root@ubuntu75:/etc/filebeat# cat filebeat.yml
filebeat.prospectors:
- input_type: log
paths:
- /var/log/nginx/access.log
document_type: nginx-access
output.logstash:
hosts: ["localhost:5044"]
shipper:
logging:
参考配置¶
filebeat:
spool_size: 1024
idle_timeout: "5s"
registry_file: ".filebeat"
config_dir: "path/to/configs/contains/many/yaml"
prospectors:
-
fields:
ownfield: "mac"
paths:
- /var/log/system.log
- /var/log/wifi.log
include_lines: ["^ERR", "^WARN"]
exclude_lines: ["^OK"]
-
document_type: "apache"
ignore_older: "24h"
scan_frequency: "10s"
tail_files: false
harvester_buffer_size: 16384
backoff: "1s"
paths:
- "/var/log/apache/*"
exclude_files: ["/var/log/apache/error.log"]
-
input_type: "stdin"
multiline:
pattern: '^[[:space:]]'
negate: false
match: after
output.logstash:
hosts: ["localhost:5044"]
注释¶
filebeat:
spool_size: 1024 # 最大可以攒够 1024 条数据一起发送出去
idle_timeout: "5s" # 否则每 5 秒钟也得发送一次
registry_file: ".filebeat" # 文件读取位置记录文件,会放在当前工作目录下。所以如果你换一个工作目录执行 filebeat 会导致重复传输!
config_dir: "path/to/configs/contains/many/yaml" # 如果配置过长,可以通过目录加载方式拆分配置
prospectors: # 有相同配置参数的可以归类为一个 prospector
-
fields:
ownfield: "mac" # 类似 logstash 的 add_fields
paths:
- /var/log/system.log # 指明读取文件的位置
- /var/log/wifi.log
include_lines: ["^ERR", "^WARN"] # 只发送包含这些字样的日志
exclude_lines: ["^OK"] # 不发送包含这些字样的日志
-
document_type: "apache" # 定义写入 ES 时的 _type 值
ignore_older: "24h" # 超过 24 小时没更新内容的文件不再监听。在 windows 上另外有一个配置叫 force_close_files,只要文件名一变化立刻关闭文件句柄,保证文件可以被删除,缺陷是可能会有日志还没读完
scan_frequency: "10s" # 每 10 秒钟扫描一次目录,更新通配符匹配上的文件列表
tail_files: false # 是否从文件末尾开始读取
harvester_buffer_size: 16384 # 实际读取文件时,每次读取 16384 字节
backoff: "1s" # 每 1 秒检测一次文件是否有新的一行内容需要读取
paths:
- "/var/log/apache/*" # 可以使用通配符
exclude_files: ["/var/log/apache/error.log"]
-
input_type: "stdin" # 除了 "log",还有 "stdin"
multiline: # 多行合并
pattern: '^[[:space:]]'
negate: false
match: after
output.logstash:
# The Logstash hosts
hosts: ["localhost:5044"]
Kibana¶
配置文件¶
root@ubuntu75:/etc/nginx/conf.d# tail -3 /etc/kibana/kibana.yml
server.host: "0.0.0.0"
elasticsearch.url: "http://127.0.0.1:9200"
# 使用search guard会有额外的配置:
elasticsearch.ssl.ca: "/etc/kibana/root-ca.pem"
elasticsearch.username: "kibanaserver"
# 密码请自行修改
elasticsearch.password: "xxx"
elasticsearch.ssl.verify: true
启动kibana¶
systemctl start kibana.service
tcp LISTEN 0 128 *:5601 *:* users:(("node",pid=28924,fd=11))
kibana 默认监听5601端口
问题解决¶
kibana Unable to connect to Elasticsearch at https://127.0.0.1:9200.
如果使用了https需要做如下修改
修改kibana.yml配置,添加elasticsearch.ssl.verify: false
解决方法:
server.host: "0.0.0.0"
elasticsearch.url: "http://127.0.0.1:9200"
logstash-output-zabbix¶
由于网络原因,使用下面方式安装¶
美国开通ecs,使用在线安装,对比差异,提取出以下安装方式
操作之前备份logstash目录
root@ubuntu47:~/test-logstash-output-zabbix/chayi# echo 'gem "logstash-output-zabbix"' >> /usr/share/logstash/Gemfile
root@ubuntu47:/usr/share/logstash# tail -2 Gemfile
gem "logstash-output-zabbix"
/usr/share/logstash/Gemfile.jruby-1.9.lock
488 logstash-output-zabbix (3.0.1)
489 logstash-codec-plain
490 logstash-core-plugin-api (>= 1.60, <= 2.99)
491 zabbix_protocol (>= 0.1.5)
610 zabbix_protocol (0.1.5)
611 multi_json
718 logstash-output-zabbix
相关文件已经打包在项目里,文件名add-logstash-output-zabbix.tar.gz
vendor/bundle/jruby/1.9/cache
root@ubuntu47:~/test-logstash-output-zabbix/logstash# ls vendor/bundle/jruby/1.9/cache
logstash-output-zabbix-3.0.1.gem zabbix_protocol-0.1.5.gem
vendor/bundle/jruby/1.9/gems/zabbix_protocol-0.1.5 目录下所有文件
root@ubuntu47:~/test-logstash-output-zabbix/logstash# ls vendor/bundle/jruby/1.9/gems/zabbix_protocol-0.1.5
Gemfile LICENSE.txt README.md Rakefile lib spec zabbix_protocol.gemspec
vendor/bundle/jruby/1.9/gems/logstash-output-zabbix-3.0.1 目录下所有文件
vendor/bundle/jruby/1.9/specifications/logstash-output-zabbix-3.0.1.gemspec
vendor/bundle/jruby/1.9/specifications/zabbix_protocol-0.1.5.gemspec
上述操作完成之后,需要重启logstash,而后通过如下命令验证
root@ubuntu47:/usr/share/logstash# bin/logstash-plugin list|grep zabbix
logstash-output-zabbix
logstash向zabbix发送数据¶

logstash-output-zabbix-1
root@ubuntu47:/etc/logstash/conf.d# cat filter.conf
filter {
if [type] == "nginx-access" {
json {
source => "message"
remove_field => [ "Arg0","Arg1","Arg2","Arg3","Arg4","Arg5","Arg6","Arg7","Arg8","Arg3","Arg9","Arg10" ]
}
mutate {
split => [ "upstreamtime", "," ]
}
mutate {
convert => [ "upstreamtime", "float" ]
}
if [status] == 304 {
mutate {
add_field => { "[@metadata][zabbix_key]" => "nginx_status" } # 同zabbix Web里配置的监控项里对应的key 一致
add_field => { "[@metadata][zabbix_host]" => "ubuntu47" } # zabbix 配置的当前服务器的 Host name 一致
# add_field => { "[nginx_status]" => "字符串用双引号一起来,数字不需要引号" } # 如果有这种需求,可以添加一个field,定义为想要的数据,然后写到zabbix(output里面的配置,zabbix_value => "nginx_status")
}
}
}
}
root@ubuntu47:/etc/logstash/conf.d# cat output.conf
output {
if [type] == "nginx-access" {
elasticsearch {
user => logstash
password => logstash
ssl => true
ssl_certificate_verification => true
truststore => "/etc/logstash/truststore.jks"
truststore_password => "82df5ddf119275a190e0"
hosts => "127.0.0.1:9200"
index => "logstash-%{type}"
document_type => "%{type}"
sniffing => false
manage_template => false
flush_size => 20000
idle_flush_time => 10
template_overwrite => true
}
}
if [type] == "nginx-access" {
if [status] == 304 {
zabbix {
zabbix_server_host => "10.29.164.37" # zabbix-server IP
zabbix_host => "[@metadata][zabbix_host]" # 使用filter里面配置的
zabbix_key => "[@metadata][zabbix_key]" # filter里面配置的key,必须要配置
zabbix_value => "status" # 这里如果使用具体的值,可能会出现 类似这这种错误,Zabbix server at 10.29.164.37 rejected all items sent
# status 将会取上面的 304
}
}
}
}
将漏掉的 Field 添加到filter中
比如 filter中添加如下配置
mutate {
add_field => { "[@metadata][zabbix_key]" => "nginx_status" }
add_field => { "[@metadata][zabbix_host]" => "ubuntu47" }
}
原因: zabbix_value => "1"
修改成如下配置后,解决:
zabbix_value => "status"
search-guard 5¶
安装¶
安装配置Search Guard之前,需要先确定es集群能够健康的跑起来
网络可能比较慢,会出现下载超级慢的情况,多试N变就好,特殊情况可考虑翻墙试试…
# 具体版本号,参考Search Guard GitHub Tag
root@ubuntu47:/usr/share/elasticsearch# bin/elasticsearch-plugin install -b com.floragunn:search-guard-5:5.0.0-9
-> Downloading com.floragunn:search-guard-5:5.0.0-9 from maven central
[=================================================] 100%??
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: plugin requires additional permissions @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.lang.RuntimePermission accessClassInPackage.sun.misc
* java.lang.RuntimePermission accessDeclaredMembers
* java.lang.RuntimePermission getClassLoader
* java.lang.RuntimePermission loadLibrary.*
* java.lang.RuntimePermission setContextClassLoader
* java.lang.RuntimePermission shutdownHooks
* java.lang.reflect.ReflectPermission suppressAccessChecks
* java.security.SecurityPermission getProperty.ssl.KeyManagerFactory.algorithm
* java.util.PropertyPermission java.security.krb5.conf write
* java.util.PropertyPermission javax.security.auth.useSubjectCredsOnly write
* javax.security.auth.AuthPermission doAs
* javax.security.auth.AuthPermission modifyPrivateCredentials
* javax.security.auth.kerberos.ServicePermission * accept
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.
-> Installed search-guard-5
Search Guard 插件配置¶
生成密钥库和信任库¶
5.0之后,插件目录会带如下脚本
在github上下载生成密钥的脚本,或者使用本项目里已经修改过的脚本,具体修改内容没有详细描述得可以对文件进行对比
git clone https://github.com/floragunncom/search-guard-ssl.git
https://github.com/floragunncom/search-guard-ssl/tree/master/example-pki-scripts
下载下来的脚本需要修改后使用
root@ubuntu47:~/src/search-guard-ssl/test# ls search-guard-ssl/example-pki-scripts/ -l
total 28
-rwxr-xr-x 1 root root 141 Mar 17 17:12 clean.sh
drwxr-xr-x 2 root root 4096 Mar 17 17:12 etc
-rwxr-xr-x 1 root root 411 Mar 17 17:12 example.sh # 通过此脚本创建所有证书
-rwxr-xr-x 1 root root 2286 Mar 17 17:12 gen_client_node_cert.sh # 创建客户端证书
-rwxr-xr-x 1 root root 2746 Mar 17 17:12 gen_node_cert.sh # 创建节点证书
-rwxr-xr-x 1 root root 1764 Mar 17 17:12 gen_node_cert_openssl.sh
-rwxr-xr-x 1 root root 1993 Mar 17 17:12 gen_root_ca.sh # 创建根证书
脚本中需要修改的地方有
cat example.sh
#!/bin/bash
#set -e
rand (){
openssl rand -hex 20
}
CA_PASS=`rand | cut -c1-40`
TS_PASS=`rand | cut -c1-20`
./clean.sh
echo "CA password: $CA_PASS" >> Readme.txt
echo "Truststore password: $TS_PASS" >> Readme.txt
./gen_root_ca.sh $CA_PASS $TS_PASS
./gen_node_cert.sh 0 `rand | cut -c1-20` $CA_PASS && ./gen_node_cert.sh 1 `rand | cut -c1-20` $CA_PASS && ./gen_node_cert.sh 2 `rand | cut -c1-20` $CA_PASS && ./gen_node_cert.sh 3 `rand | cut -c1-20` $CA_PASS && ./gen_node_cert.sh 4 `rand | cut -c1-20` $CA_PASS && ./gen_node_cert.sh 5 `rand | cut -c1-20` $CA_PASS && ./gen_node_cert.sh 6 `rand | cut -c1-20` $CA_PASS
#./gen_client_node_cert.sh spock `rand | cut -c1-20` $CA_PASS
./gen_client_node_cert.sh kirk `rand | cut -c1-20` $CA_PASS
./gen_client_node_cert.sh sgadmin `rand | cut -c1-20` $CA_PASS
脚本中一定要修改并且注意的地方,Readme.txt文件里面会保存密码
gen_node_cert.sh
¶如果配置没有问题,es起不来,可以尝试不指定IP生成密钥库,信任库
# jks文件密码写入文本
echo "$NODE_NAME keystore password :$KS_PASS" >> Readme.txt
keytool -genkey \
-alias $NODE_NAME \
-keystore $NODE_NAME-keystore.jks \
-keyalg RSA \
-keysize 2048 \
-validity 712 \
-sigalg SHA256withRSA \
-keypass $KS_PASS \
-storepass $KS_PASS \
-dname "CN=$NODE_NAME.example.com, OU=SSL, O=Test, L=Test, C=DE" \
-ext san=dns:$NODE_NAME.example.com,dns:localhost,ip:127.0.0.1,ip:10.29.164.80,ip:10.29.164.37,oid:1.2.3.4.5.5
#oid:1.2.3.4.5.5 denote this a server node certificate for search guard
echo Generating certificate signing request for node $NODE_NAME
keytool -certreq \
-alias $NODE_NAME \
-keystore $NODE_NAME-keystore.jks \
-file $NODE_NAME.csr \
-keyalg rsa \
-keypass $KS_PASS \
-storepass $KS_PASS \
-dname "CN=$NODE_NAME.example.com, OU=SSL, O=Test, L=Test, C=DE" \
-ext san=dns:$NODE_NAME.example.com,dns:localhost,ip:127.0.0.1,ip:10.29.164.80,ip:10.29.164.37,oid:1.2.3.4.5.5
#oid:1.2.3.4.5.5 denote this a server node certificate for search guard
#一定要把与es通信的logstash和kibana,ip都包含在内,不然秘钥不能在其他ip上使用
gen_client_node_cert.sh
,生成sgadmin keystore password,kirk keystore password¶echo "$CLIENT_NAME keystore password :$KS_PASS" >> Readme.txt
执行example.sh会在当前目录生成密钥
root@ubuntu47:~/src/search-guard-ssl/example-pki-
scripts# cat Readme.txt
CA password: b5e6350c3d1a3001621c3861a215961eb2aeaa5d
Truststore password: a49f3e3807d8c3843972
node-0 keystore password :8d21330bc20e1efedef7
node-1 keystore password :d75d4ed8a0dc91f5a8c3
node-2 keystore password :592901f5cadc97f23e40
node-3 keystore password :7ebd3dedff1a141738cb
node-4 keystore password :c36ff8e062f79ec5fb65
node-5 keystore password :f74bd73382ea407fbf69
node-6 keystore password :d255658bc42074dcd6a6
kirk keystore password :6c627ee52b047eb4fd17
sgadmin keystore password :e9688af7348f08e6d55a
下文操作
cp node-1-keystore.jks /etc/elasticsearch/
cp truststore.jks /etc/elasticsearch/
scp node-2-keystore.jks 10.29.164.37:/etc/elasticsearch/
scp truststore.jks 10.29.164.37:/etc/elasticsearch/
配置elasticsearch¶
节点 | ip | 密钥 | 路径 |
---|---|---|---|
node-1 | 10.29.164.80 | node-1-keystore.jks,truststore.jks | /etc/elasticsearch |
node-2 | 10.29.164.37 | node-2-keystore.jks,truststore.jks | /etc/elasticsearch |
node-1配置示例
elasticsearch用户需要读取文件权限
jsk等文件放置到指定位置之后,注意修改文件属主,属组
chown -R elasticsearch.elasticsearch /etc/elasticsearch
配置elasticsearch的/etc/elasticsearch/elasticsearch.yml
#################node-1-keystore.jks############################
searchguard.ssl.transport.keystore_filepath: node-1-keystore.jks
searchguard.ssl.transport.keystore_password: d75d4ed8a0dc91f5a8c3 # Readme.txt文件里面对应的密码
searchguard.ssl.transport.truststore_filepath: truststore.jks
searchguard.ssl.transport.truststore_password: a49f3e3807d8c3843972
searchguard.ssl.transport.enforce_hostname_verification: false
searchguard.ssl.http.enabled: true
searchguard.ssl.http.keystore_filepath: node-1-keystore.jks
searchguard.ssl.http.keystore_password: d75d4ed8a0dc91f5a8c3
searchguard.ssl.http.truststore_filepath: truststore.jks
searchguard.ssl.http.truststore_password: a49f3e3807d8c3843972
#searchguard.ssl.http.clientauth_mode: REQUIRE # 开启客户端认证(仅接受来自可信客户端的HTTPS连接)
# 需要安装让es节点信任的整数,证书名称为kirk,spock,使用与节点证书相同的Root CA证书生成
searchguard.ssl.http.clientauth_mode: OPTIONAL
# 配置管理证书
searchguard.authcz.admin_dn:
- CN=sgadmin,OU=client,O=client,L=test,C=DE
searchguard.audit.type: internal_elasticsearch
# 配置好后可重启es
/etc/init.d/elasticsearch restart
配置search guard¶
cd /usr/share/elasticsearch/plugins/search-guard-5/sgconfig
cp /root/kirk-keystore.jks ./kirk-keystore.jks
cp /root/sgadmin-keystore.jks ./sgadmin-keystore.jks
cp /root/truststore.jks ./truststore.jks
# 执行 sgadmin.sh
cd /usr/share/elasticsearch/plugins/search-guard-5/
./tools/sgadmin.sh -cd sgconfig/ -ks sgconfig/sgadmin-keystore.jks -kspass 8a223046e542cd8af036 -ts /etc/elasticsearch/truststore.jks -tspass ee70d142789462798858 -cn my-elk-cluster
执行之后就可以看到当前集群节点数等信息
可以使用浏览器进入: 查询客户端身份信息
同样,配置node-2,node-3等等…
##############node-2-keystore.jks###################################
searchguard.ssl.transport.keystore_filepath: node-2-keystore.jks
searchguard.ssl.transport.keystore_password: 592901f5cadc97f23e40
searchguard.ssl.transport.truststore_filepath: truststore.jks
searchguard.ssl.transport.truststore_password: a49f3e3807d8c3843972
searchguard.ssl.transport.enforce_hostname_verification: false
searchguard.ssl.http.enabled: true
searchguard.ssl.http.keystore_filepath: node-2-keystore.jks
searchguard.ssl.http.keystore_password: 592901f5cadc97f23e40
searchguard.ssl.http.truststore_filepath: truststore.jks
searchguard.ssl.http.truststore_password: a49f3e3807d8c3843972
#searchguard.ssl.http.clientauth_mode: REQUIRE
searchguard.ssl.http.clientauth_mode: OPTIONAL
searchguard.authcz.admin_dn:
- CN=sgadmin,OU=client,O=client,L=test,C=DE
# CN=sgadmin CN指定sgadmin,后面执行sgadmin.sh脚本则指定该密钥库
searchguard.audit.type: internal_elasticsearch
修改文件属主,属组
chown -R elasticsearch.elasticsearch /etc/elasticsearch
配置logstash¶
节点 | ip | 密钥 | 路径 |
---|---|---|---|
logstash 1 | 10.29.164.80 | truststore.jks | /etc/logstash/ |
配置logstash的/etc/logstash/conf.d/output.conf
root@ubuntu47:/etc/logstash/conf.d# cat output.conf
output {
if [type] == "nginx-access" {
elasticsearch {
# 需要做如下配置,如下用户相关配置在 /usr/share/elasticsearch/plugins/search-guard-5/sgconfig# cat sg_internal_users.yml
user => logstash
password => logstash
ssl => true
ssl_certificate_verification => true
truststore => "/etc/logstash/truststore.jks"
truststore_password => "82df5ddf119275a190e0"
hosts => "127.0.0.1:9200"
index => "logstash-%{type}"
document_type => "%{type}"
sniffing => false
manage_template => false
flush_size => 20000
idle_flush_time => 10
template_overwrite => true
}
}
if [type] == "nginx-access" {
if [status] == 304 {
zabbix {
zabbix_server_host => "10.29.164.37" # zabbix-server IP
zabbix_host => "[@metadata][zabbix_host]" # 使用filter里面配置的
zabbix_key => "[@metadata][zabbix_key]" # zabbix里面配置的key,必须要配置
zabbix_value => "status" # 这里如果使用具体的值,可能会出现 类似这这种错误,Zabbix server at 10.29.164.37 rejected all items sent
# status 将会取上面的 304
}
}
}
}
配置kibana¶
节点 | ip | 密钥 | 路径 |
---|---|---|---|
kibana 1 | 10.29.164.80 | root-ca.pem | /etc/kibana/ |
配置kibana的/usr/share/kibana/config/kibana.yml
server.port: 5601
elasticsearch.url: "https://10.29.164.80:9200"
elasticsearch.ssl.ca: "/etc/kibana/root-ca.pem"
server.host: "0.0.0.0"
elasticsearch.username: "kibanaserver"
elasticsearch.password: "kibanaserver"
配置sgadmin限制权限¶
# sg 配置文件
配置文件路径 : /usr/share/elasticsearch/plugins/search-guard-5/sgconfig/sg_roles.yml
#给kibana的权限
sg_kibana4_server:
cluster:
- cluster:monitor/nodes/info
- cluster:monitor/health
- indices:data/write/bulk*
indices:
'?kibana':
'*':
- ALL
#给logstash的权限
sg_logstash:
cluster:
- indices:admin/template/get
- indices:admin/template/put
- indices:data/write/bulk*
indices:
'logstash-*':
'*':
- CRUD
- CREATE_INDEX
'*beat*':
'*':
- CRUD
- CREATE_INDEX
#main用户的权限
sg_readonly_main:
cluster:
- cluster:monitor/nodes/info
- cluster:monitor/health
- indices:data/read/mget*
- indices:data/read/msearch*
indices:
'?kibana':
'*':
- ALL
#给online用户的权限
sg_readonly_online:
cluster:
- cluster:monitor/nodes/info
- cluster:monitor/health
- indices:data/read/mget*
- indices:data/read/msearch*
indices:
'?kibana':
'*':
- ALL
配置文件路径: /usr/share/elasticsearch/plugins/search-guard-5/sgconfig/sg_roles_mapping.yml
#权限的对应用户
sg_readonly_main:
users:
- main
sg_readonly_online:
users:
- online
cat /usr/share/elasticsearch/plugins/search-guard-5/sgconfig/sg_internal_users.yml
#创建用户
main:
hash: $2a$12$1WvtrH8SkxcfW0qqmU9VnutFE7giYCmtIrpbxP5SfGX7ajGsE/zy2
#password is: DFVuxsuxnbBNzHsP8afU
online:
hash: $2a$12$vSULu9lWyww6OqQHKYTZ5ezIrGWqmGHirr6FuLyvqRMQaDikUWw/i
#password is: AHVbLWzFwohb9CLLARio
#如何创建用户
cd /usr/share/elasticsearch/plugins/search-guard-5/tools
bash hash.sh -p '密码'
执行sgtool¶
cd /usr/share/elasticsearch/plugins/search-guard-5/
./tools/sgadmin.sh -cd sgconfig/ -ks sgconfig/sgadmin-keystore.jks -kspass 8a223046e542cd8af036 -ts /etc/elasticsearch/truststore.jks -tspass ee70d142789462798858 -cn my-elk-cluster
root@ubuntu47:/usr/share/elasticsearch/plugins/search-guard-5# . ./tools/sgadmin.sh -cd sgconfig/ -ks sgconfig/sgadmin-keystore.jks -kspass 6a278d4484b52dc03dbbfe67fd6c1cdab4d31f46 -ts /etc/elasticsearch/truststore.jks -tspass 9ed1ea485a7e906acb5c -cn my-elk-cluster
注意:每次重启es的时候都需要执行sg重新给权限,只是添加用户和加权不影响集群
可将如下命令写到脚本,方便执行
root@ubuntu47:/usr/share/elasticsearch/plugins/search-guard-5# . ./tools/sgadmin.sh -cd sgconfig/ -ks sgconfig/sgadmin-keystore.jks -kspass e9688af7348f08e6d55a -ts /etc/elasticsearch/truststore.jks -tspass a49f3e3807d8c3843972 -cn my-elk-cluster
Kafka¶
快速开始¶
wget http://apache.forsale.plus/kafka/0.10.1.0/kafka_2.11-0.10.1.0.tgz
tar xf kafka_2.11-0.10.1.0.tgz -C /opt/
ln -s /opt/kafka_2.11-0.10.1.0/ /opt/kafka
cd /opt/kafka
mkdir /data/zookeeper
echo 2 > /data/zookeeper/myid
root@ubuntu75:/opt/kafka# bin/zookeeper-server-start.sh config/zookeeper.properties &[
root@ubuntu191:/opt/kafka# bin/zookeeper-server-start.sh config/zookeeper.properties &
root@ubuntu75:/opt/kafka# bin/kafka-server-start.sh config/server.properties &
报错信息¶
[2017-02-10 11:50:44,222] ERROR Setting LearnerType to PARTICIPANT but 3 not in QuorumPeers. (org.apache.zookeeper.server.quorum.QuorumPeer) [2017-02-10 11:50:44,226] INFO currentEpoch not found! Creating with a reasonable default of 0. This should only happen when you are upgrading your installation (org.apache.zookeeper.server.quorum.QuorumPeer) [2017-02-10 11:50:44,239] INFO acceptedEpoch not found! Creating with a reasonable default of 0. This should only happen when you are upgrading your installation (org.apache.zookeeper.server.quorum.QuorumPeer) [2017-02-10 11:50:44,253] ERROR Unexpected exception, exiting abnormally (org.apache.zookeeper.server.quorum.QuorumPeerMain) java.lang.RuntimeException: My id 3 not in the peer list
root@ubuntu75:/opt/kafka# grep -vE “#|^$” config/zookeeper.properties dataDir=/data/zookeeper clientPort=2181 tickTime=2000 initLimit=20 syncLimit=10 server.1=10.174.217.111:2888:3888 server.2=10.174.214.247:2888:3888
说明: server后面的数字,写入到 cat /data/zookeeper/myid 由于配置文件里面没有3,所以报上述错误
Lucene查询语法¶
字段¶
也可以按页面左侧显示的字段搜索
限定字段全文搜索:field:value
精确搜索:关键字加上双引号 filed:"value"
http.code:404
搜索http状态码为404的文档
字段本身是否存在
_exists_:http
:返回结果中需要有http字段
_missing_:http
:不能含有http字段
模糊搜索¶
~ : 在一个单词后面加上~
启用模糊搜索
first~
也能匹配到 frist
还可以指定需要多少相似度
cromm~0.3
会匹配到 from 和 chrome
数值范围0.0 ~ 1.0,默认0.5,越大越接近搜索的原始值
范围搜索¶
数值和时间类型的字段可以对某一范围进行查询
length:[100 TO 200]
date:{"now-6h" TO "now"}
[ ]
表示端点数值包含在范围内,{ }
表示端点数值不包含在范围内
分组¶
(jakarta OR apache) AND jakarta
Elastic Stack¶
ELK Stack的新名字
elastic公司提供的一套完整的日志收集、展示解决方案
- ElasticSearch
- Logstash
- Kibana
Elasticsearch¶
Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。
Logstash¶
Logstash是一个开源的用于收集,分析和存储日志的工具。
Kibana¶
Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以汇总、分析和搜索重要数据日志。
Beats¶
Beats是elasticsearch公司开源的一款采集系统监控数据的代理agent,是在被监控服务器上以客户端形式运行的数据收集器的统称,可以直接把数据发送给Elasticsearch或者通过Logstash发送给Elasticsearch,然后进行后续的数据分析活动。
Beats 作为日志搜集器没有Logstash 作为日志搜集器消耗资源,解决了 Logstash 在各服务器节点上占用系统资源高的问题。
Beats由如下组成
一个网络数据包分析器,用于监控、收集网络流量信息,
Packetbeat嗅探服务器之间的流量,解析应用层协议,并关联到消息的处理,其支持 ICMP (v4 and v6)、DNS、HTTP、Mysql、PostgreSQL、Redis、
MongoDB、Memcache等协议;
用于监控、收集服务器日志文件,其已取代 logstash forwarder;
可定期获取外部系统的监控指标信息,其可以监控、收集Apache、HAProxy、MongoDB、MySQL、Nginx、PostgreSQL、Redis、System、Zookeeper等服务;
自定义beat ,如果上面的指标不能满足需求,elasticsarch鼓励开发者使用go语言,扩展实现自定义的beats,只需要按照模板,实现监控的输入,日志,输出等即可。
X-Pack¶
x-pack是elasticsearch的一个扩展包,将安全,警告,监视,图形,报告和机器学习功能捆绑在一个易于安装的软件包中,虽然x-pack被设计为一个无缝的工作,但是你可以轻松的启用或者关闭一些功能.
参考推荐¶
- 三斗室 http://chenlinux.com/
- elk-stack-guide-cn, 书籍如果失效, 可以去作者github找
- elk-stack-guide-cn 作者github
配置文件¶
logstash¶
root@ubuntu75:/etc/logstash# egrep -v "#|^$" /etc/logstash/logstash.yml
path.data: /var/lib/logstash
path.config: /etc/logstash/conf.d
path.logs: /var/log/logstash
root@ubuntu75:/etc/elasticsearch# cat /etc/logstash/conf.d/filter.conf
filter {
if [type] == "nginx-access" {
json {
source => "message"
remove_field => [ "Arg0","Arg1","Arg2","Arg3","Arg4","Arg5","Arg6","Arg7","Arg8","Arg3","Arg9","Arg10" ]
}
mutate {
split => [ "upstreamtime", "," ]
}
mutate {
convert => [ "upstreamtime", "float" ]
}
}
}
root@ubuntu75:/etc/elasticsearch# cat /etc/logstash/conf.d/input.conf
input {
beats {
port => 5044
}
}
root@ubuntu75:/etc/elasticsearch# cat /etc/logstash/conf.d/output.conf
output {
if [type] == "nginx-access" {
elasticsearch {
hosts => "127.0.0.1:9200"
index => "logstash-%{type}"
document_type => "%{type}"
sniffing => false
manage_template => false
flush_size => 20000
idle_flush_time => 10
template_overwrite => true
}
}
}
elasticsearch¶
root@ubuntu75:/etc/elasticsearch# egrep -v "#|^$" /etc/elasticsearch/elasticsearch.yml
cluster.name: my-elk-cluster
node.name: node-1
path.data: /data/es-data
network.host: 0.0.0.0
http.port: 9200
discovery.zen.ping.unicast.hosts: ["10.174.217.111","10.174.214.247"]
discovery.zen.minimum_master_nodes: 1
http.cors.enabled: true
http.cors.allow-origin: "*"
http.cors.allow-headers: Authorization
nginx¶
root@ubuntu75:/etc/elasticsearch# egrep -v "#|^$" /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_prefer_server_ciphers on;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" $request_body '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format json '{'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"time_local":"$time_local",'
'"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"request_method":"$request_method",'
'"request":"$request",'
'"uri":"$uri",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_status":"$upstream_status",'
'"upstream_addr":"$upstream_addr"'
'}';
access_log /var/log/nginx/access.log json;
error_log /var/log/nginx/error.log ;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
root@ubuntu75:/etc/elasticsearch#
/etc/nginx/conf.d/kibana.conf
root@ubuntu75:/etc/elasticsearch# cat /etc/nginx/conf.d/kibana.conf
upstream kibana {
server 127.0.0.1:5601;
}
server {
listen 80;
server_name _;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/htpasswd.users;
location / {
proxy_pass http://kibana;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forward-For $remote_addr;
proxy_cache_bypass $http_upgrade;
}
}
nginx-default¶
root@ubuntu75:/etc/elasticsearch# egrep -v "#|^$" /etc/nginx/conf.d/default
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
elasticsearch-head¶
root@ubuntu75:/etc/elasticsearch# cat /etc/nginx/conf.d/elasticsearch-head.conf
server {
listen 81;
server_name _;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/htpasswd.users;
location / {
proxy_pass http://127.0.0.1:9100;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
kibana¶
root@ubuntu75:/etc/elasticsearch# egrep -v "#|^$" /etc/kibana/kibana.yml
server.host: "0.0.0.0"
elasticsearch.url: "http://127.0.0.1:9200"
ftp¶
ftp
使用docker搭建ftp服务¶
使用 pure-ftpd
docker-compose.yml¶
image : stilliard/pure-ftpd
➜ ftp cat docker-compose.yml
ftp:
image: stilliard/pure-ftpd
volumes:
- "$PWD/ftpusers:/home/ftpusers"
- "$PWD/pure-ftpd:/etc/pure-ftpd"
- /etc/localtime:/etc/localtime:ro
ports:
- "21:21"
- "30000:30000"
- "30001:30001"
- "30002:30002"
- "30003:30003"
- "30004:30004"
- "30005:30005"
- "30006:30006"
- "30007:30007"
- "30008:30008"
- "30009:30009"
environment:
PUBLICHOST: 192.168.55.15 # ftp服务器IP,或者域名
- TZ=Asia/Shanghai
创建用户¶
容器内执行:
pure-pw useradd xxx -f /etc/pure-ftpd/passwd/pureftpd.passwd -m -u ftpuser -d /home/ftpusers/xxx
删除用户(禁止用户登录,不会删除设置的家目录,以及文件)
pure-pw useradd xxx
会提示输入密码,照做即可
测试连接¶
ftp -p localhost 21
haproxy¶
haproxy
heartbeat¶
heartbeat
keepalived¶
keepalived
mq¶
activemq¶
activemq
kafka¶
kafka¶
quickstart¶
cd src
wget http://mirrors.cnnic.cn/apache/kafka/0.10.1.0/kafka_2.11-0.10.1.0.tgz
tar xf kafka_2.11-0.10.1.0.tgz -C /opt/
cd /opt/kafka_2.11-0.10.1.0/
bin/zookeeper-server-start.sh config/zookeeper.properties
bin/kafka-server-start.sh config/server.properties
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
可以配置broker自动创建topics
bin/kafka-topics.sh --list --zookeeper localhost:2181
每一行内容会被分为一条消息
启动producer
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
kafka有一个命令行的consumer,可以将消息显示到标准输出
新开一个窗口运行如下命令
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
现在可以在刚刚启动的producer窗口发布消息,你将在consumer中看到
刚刚使用的命令行工具,不加参数运行可以查看帮助信息

kafka-quickstart-2017222
在同一台机器创建
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# pwd
/opt/kafka_2.11-0.10.1.0
cp config/server.properties config/server-1.properties
cp config/server.properties config/server-2.properties
编辑配置文件
config/server-1.properties:
broker.id=1
listeners=PLAINTEXT://:9093
log.dir=/tmp/kafka-logs-1
config/server-2.properties:
broker.id=2
listeners=PLAINTEXT://:9094
log.dir=/tmp/kafka-logs-2
集群中broker.id
唯一识别一个节点,之前已经启动了zookeeper,所以只需要从刚刚生成的两个配置文件启动新的节点
启动kafka
bin/kafka-server-start.sh config/server-1.properties &
bin/kafka-server-start.sh config/server-2.properties &
创建一个具有三个副本的topic
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic my-replicated-topic
Created topic "my-replicated-topic".
replication-factor表示该topic需要在不同的broker中保存几份
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs:
Topic: my-replicated-topic Partition: 0 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1
查看之前建立的topic
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
Topic:test PartitionCount:1 ReplicationFactor:1 Configs:
Topic: test Partition: 0 Leader: 0 Replicas: 0 Isr: 0
可以看到test topic没有副本,并且存放在server 0上
发布消息到新topic
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-replicated-topic

kafka-quickstart2-2017222
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# ps aux |grep server.properties
kill -9 PID
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic
Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs:
Topic: my-replicated-topic Partition: 0 Leader: 2 Replicas: 0,2,1 Isr: 2,1
root@ubuntu47:/opt/kafka_2.11-0.10.1.0#
Leader切换到node 2,node 0不在在in-sync副本里,而消息仍然可用
使用Kafka Connect从文件导入数据,以及导出数据到文件
echo -e "foo\nbar" > test.txt
bin/connect-standalone.sh config/connect-standalone.properties config/connect-file-source.properties config/connect-file-sink.properties
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# cat test.sink.txt
foo
bar
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic connect-test --from-beginning
{"schema":{"type":"string","optional":false},"payload":"foo"}
{"schema":{"type":"string","optional":false},"payload":"bar"}
新窗口,echo “Another line” >> test.txt
可以看到如下信息,同时可以查看test.sink.txt

kafka-quickstart3-2017222
准备使用Kafka Streams处理的数据
echo -e "all streams lead to kafka\nhello kafka streams\njoin kafka summit" > file-input.txt
创建topic
bin/kafka-topics.sh --create \
--zookeeper localhost:2181 \
--replication-factor 1 \
--partitions 1 \
--topic streams-file-input
使用producer将数据输入到指定的topic
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic streams-file-input < file-input.txt
新开窗口,执行如下命令
运行WordCount,处理输入的数据,除了日志,不会有stdout,结果不断地写回另一个topic(streams-wordcount-output)
bin/kafka-run-class.sh org.apache.kafka.streams.examples.wordcount.WordCountDemo
检查WordCountDemo应用,从输出的topic读取
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic streams-wordcount-output \
--from-beginning \
--formatter kafka.tools.DefaultMessageFormatter \
--property print.key=true \
--property print.value=true \
--property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer \
--property value.deserializer=org.apache.kafka.common.serialization.LongDeserializer
all 1
lead 1
to 1
hello 1
streams 2
join 1
kafka 3
summit 1
以上信息是输出到标准输出的内容,第一列是message的key,第二列是value,要注意的是,输出的实际是一个连续的更新流,其中每条数据(原始输出的每行)是一个单词的最新的count,又叫记录键"kafka".
对于同一个key有多个记录,每个记录之后是前一个的更新.

kafka-quickstart4-2017222
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# bin/kafka-server-start.sh config/server-1.properties &
[1] 10436
root@ubuntu47:/opt/kafka_2.11-0.10.1.0# Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c0000000, 1073741824, 0) failed; error='Cannot allocate memory' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 1073741824 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /opt/kafka_2.11-0.10.1.0/hs_err_pid10436.log
修改JVM堆大小,修改启动脚本中,KAFKA_HEAP_OPTS的值
位置:
bin/zookeeper-server-start.sh
bin/kafka-server-start.sh
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-Xmx256M -Xms256M"
fi
修改-Xmm,-Xms,视服务器内存而定
[2017-02-22 17:56:53,828] ERROR Failed to flush WorkerSourceTask{id=local-file-source-0}, timed out while waiting for producer to flush outstanding 1 messages (org.apache.kafka.connect.runtime.WorkerSourceTask:289)
[2017-02-22 17:56:53,828] ERROR Failed to commit offsets for WorkerSourceTask{id=local-file-source-0} (org.apache.kafka.connect.runtime.SourceTaskOffsetCommitter:109)
确认kafka进程是否存在,可以暂停之后,重新启动kafka
rabbitmq¶
rabbitmq
nexus¶
Nexus Repository Manager¶
安装和运行¶
使用Docker运行¶
Docker Hub Sonatype Nexus Repository Manager 3
启动
docker run -d -p 8081:8081 --name nexus -v $PWD/nexus-data:/nexus-data sonatype/nexus3
三个环境变量可以控制 JVM参数
JAVA_MAX_HEAP
, passed as -Xmx. Defaults to 1200m.
JAVA_MIN_HEAP
, passed as -Xms. Defaults to 1200m.
EXTRA_JAVA_OPTS
. Additional options can be passed to the JVM via
this variable.
示例:
docker run -d -p 8081:8081 --name nexus -e JAVA_MAX_HEAP=1500m -v $PWD/nexus-data:/nexus-data sonatype/nexus3
查看日志:
使用 docker logs nexus # 查看日志
目录说明¶
bash-4.2$ ls
LICENSE.txt NOTICE.txt # license,copyright信息
bin # nexus启动脚本,以及启动相关的配置文件
deploy
etc # 配置文件
lib # 库文件
public # 应用程序公共资源
system # 构成应用程序的组件和插件
bash-4.2$ ls /nexus-data/ -l
total 68
drwxr-xr-x 2 nexus nexus 4096 Apr 14 03:43 backup
drwxr-xr-x 3 nexus nexus 4096 Apr 14 03:43 blobs
drwxr-xr-x 250 nexus nexus 12288 Apr 14 03:43 cache # 有关当前缓存的Karaf包的信息
drwxr-xr-x 9 nexus nexus 4096 Apr 14 03:43 db # OrientDB数据库,
drwxr-xr-x 3 nexus nexus 4096 Apr 14 03:43 elasticsearch # 当前配置的Elasticsearch状态。
drwxr-xr-x 3 nexus nexus 4096 Apr 14 03:43 etc
drwxr-xr-x 2 nexus nexus 4096 Apr 14 03:42 generated-bundles
drwxr-xr-x 2 nexus nexus 4096 Apr 14 03:43 health-check
drwxr-xr-x 2 nexus nexus 4096 Apr 14 03:42 instances
drwxr-xr-x 3 nexus nexus 4096 Apr 14 03:43 keystores
-rw-r--r-- 1 nexus nexus 14 Apr 14 03:42 lock
drwxr-xr-x 2 nexus nexus 4096 Apr 14 03:43 log
drwxr-xr-x 2 nexus nexus 4096 Apr 14 03:43 orient
-rw-r--r-- 1 nexus nexus 5 Apr 14 03:42 port
drwxr-xr-x 7 nexus nexus 4096 Apr 14 03:43 tmp
使用web界面¶
点点点
配置¶
问题¶
启动容器的时候,异常退出¶
# 容器异常退出,如下操作发现,权限拒绝
# 原因,宿主机$PWD/nexus-data目录属主不对
root@ubuntu66:~/data# docker logs nexus
mkdir: cannot create directory '../sonatype-work/nexus3/log': Permission denied
mkdir: cannot create directory '../sonatype-work/nexus3/tmp': Permission denied
Java HotSpot(TM) 64-Bit Server VM warning: Cannot open file ../sonatype-work/nexus3/log/jvm.log due to No such file or directory
Warning: Cannot open log file: ../sonatype-work/nexus3/log/jvm.log
Warning: Forcing option -XX:LogFile=/tmp/jvm.log
Unable to update instance pid: Unable to create directory /nexus-data/instances
/nexus-data/log/karaf.log (No such file or directory)
Unable to update instance pid: Unable to create directory /nexus-data/instances
查看容器里用户,及id
root@ubuntu66:~/data# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sonatype/nexus3 latest c66e39c805c9 2 days ago 464 MB
tutum/mongodb latest 64ca9521c703 14 months ago 503 MB
root@ubuntu66:~/data# docker run -ti --rm --entrypoint="/bin/bash" sonatype/nexus3 -c "whoami && id"
nexus
uid=200(nexus) gid=200(nexus) groups=200(nexus)
解决办法
宿主机:
chown -R 200 nexus-data/ # 使用数字id,避免使用名字与容器里用户名不一致
添加非安全仓库
root@ubuntu47:~# vi /etc/systemd/system/multi-user.target.wants/docker.service
ExecStart=/usr/bin/dockerd --insecure-registry 121.42.244.66:8517
root@ubuntu47:~# systemctl daemon-reload
root@ubuntu47:~# systemctl restart docker.service
检查
root@ubuntu47:~# docker info|tail -5
WARNING: No swap limit support
Experimental: false
Insecure Registries:
121.42.244.66:8517
127.0.0.0/8
Live Restore Enabled: false
nginx¶
Nginx + Lua WAF¶
参考链接¶
Start¶
[root@centos tools]# pwd
/home/yjj/tools
[root@centos tools]# wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
[root@centos tools]# ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.39.tar.gz
## 解压到指定位置,后面编译Nginx时会用到
[root@centos tools]# tar xf pcre-8.39.tar.gz -C /usr/local/src/
[root@centos tools]# wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
[root@centos tools]# wget https://github.com/openresty/lua-nginx-module/archive/v0.10.7.tar.gz
解压NDK和lua-nginx-module¶
[root@centos tools]# tar xf v0.10.7.tar.gz
[root@centos tools]# tar xf v0.3.0.tar.gz
[root@centos tools]# ls
LuaJIT-2.0.4.tar.gz mysql-5.6.34.tar.gz ngx_devel_kit-0.3.0 php-7.0.13.tar.gz
lua-nginx-module-0.10.7 nginx-1.10.2 pcre-8.39.tar.gz v0.10.7.tar.gz
mariadb-10.1.19.tar.gz nginx-1.10.2.tar.gz php-5.6.28.tar.gz v0.3.0.tar.gz
安装LuaJIT,Luajit是Lua即时编译器。¶
[root@centos tools]# tar xf LuaJIT-2.0.4.tar.gz
[root@centos tools]# cd LuaJIT-2.0.4
[root@centos LuaJIT-2.0.4]# ls
COPYRIGHT doc dynasm etc Makefile README src
[root@centos LuaJIT-2.0.4]# make && make install
编译安装Nginx¶
[root@centos tools]# wget http://nginx.org/download/nginx-1.10.2.tar.gz
[root@centos tools]# tar nginx-1.10.2.tar.gz
[root@centos tools]# cd nginx-1.10.2
[root@centos nginx-1.10.2]# ./configure --prefix=/application/nginx-1.10.2 --user=www --group=www --with-http_ssl_module --with-http_stub_status_module --with-file-aio --with-http_dav_module --add-module=../ngx_devel_kit-0.3.0/ --add-module=../lua-nginx-module-0.10.7/ --with-pcre=/usr/local/src/pcre-8.39
[root@centos nginx-1.10.2]# make -j2 && make install
[root@centos nginx-1.10.2]# ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
如果不创建符号链接,可能出现以下异常: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
[root@centos nginx-1.10.2]# cd /application/
[root@centos application]# ln -s nginx-1.10.2 nginx
[root@centos application]# ll
total 8
drwxr-xr-x 3 root root 4096 Dec 21 19:31 backup
lrwxrwxrwx 1 root root 12 Dec 21 19:30 nginx -> nginx-1.10.2
drwxr-xr-x 6 root root 4096 Dec 21 19:27 nginx-1.10.2
测试安装¶
[root@centos application]# cd /application/nginx/conf/
[root@centos conf]# vim nginx.conf
42 location /hello {
43 default_type 'text/plain';
44 content_by_lua 'ngx.say("hello,lua")';
45 }
[root@centos conf]# /application/nginx/sbin/nginx -t
nginx: the configuration file /application/nginx-1.10.2/conf/nginx.conf syntax is ok
nginx: configuration file /application/nginx-1.10.2/conf/nginx.conf test is successful
[root@centos conf]# /application/nginx/sbin/nginx
然后访问http://xxx.xxx.xxx.xxx/hello,如果出现hello,lua。表示安装完成,然后就可以。

hello lua-20161221
OpenResty¶
[root@centos ~]# yum install -y readline-devel pcre-devel openssl-devel
[root@centos ~]# cd /home/yjj/tools/
[root@centos tools]# wget https://openresty.org/download/ngx_openresty-1.9.7.2.tar.gz
[root@centos tools]# tar xf ngx_openresty-1.9.7.2.tar.gz
[root@centos tools]# cd ngx_openresty-1.9.7.2
[root@centos ngx_openresty-1.9.7.2]# ./configure --prefix=/application/ngx_openresty-1.9.7.2 \
--with-luajit --with-http_stub_status_module \
--with-pcre=/usr/local/src/pcre-8.39 --with-pcre-jit
[root@centos ngx_openresty-1.9.7.2]# gmake && gmake install
[root@centos ngx_openresty-1.9.7.2]# cd /application/
[root@centos application]# ls
backup nginx nginx-1.10.2 ngx_openresty-1.9.7.2
[root@centos application]# ln -s ngx_openresty-1.9.7.2/ ngx_openresty
测试OpenResty¶
[root@centos application]# vim /application/ngx_openresty/nginx/conf/nginx.conf
server {
location /hello {
default_type text/html;
content_by_lua_block {
ngx.say("Hello YJJ")
}
}
}
[root@centos application]# /application/ngx_openresty/nginx/sbin/nginx -t
nginx: the configuration file /application/ngx_openresty-1.9.7.2/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /application/ngx_openresty-1.9.7.2/nginx/conf/nginx.conf test is successful
[root@centos application]# /application/ngx_openresty/nginx/sbin/nginx
[root@centos application]# curl 127.0.0.1/hello
Hello YJJ
WAF¶
[root@centos ~]# git clone https://github.com/unixhot/waf.git
[root@centos ~]# cp -a ./waf/waf/ /application/ngx_openresty/nginx/conf/
修改Nginx的配置文件,加入以下配置。同时WAF日志默认存放在/tmp/日期_waf.log,同时修改waf路径下的config.lua的规则路径(**config_rule_dir = "/application/ngx_openresty/nginx/conf/waf/rule-config"**)
#WAF 如果路径非以下路径,请注意修改
lua_shared_dict limit 50m;
lua_package_path "/application/ngx_openresty/nginx/conf/waf/?.lua";
init_by_lua_file "/application/ngx_openresty/nginx/conf/waf/init.lua";
access_by_lua_file "/application/ngx_openresty/nginx/conf/waf/access.lua";
[root@centos ~]# /application/ngx_openresty/nginx/sbin/nginx -t
[root@centos ~]# /application/ngx_openresty/nginx/sbin/nginx
浏览器访问
http://www.yjjztt.top/test.php?id=../etc/passwd
问题记录¶
-L/home/yjj/tools/ngx_openresty-1.9.7.2/build/luajit-root/application/ngx_openresty-1.9.7.2/luajit/lib -Wl,-rpath,/application/ngx_openresty-1.9.7.2/luajit/lib -Wl,-E -lpthread -lcrypt -L/home/yjj/tools/ngx_openresty-1.9.7.2/build/luajit-root/application/ngx_openresty-1.9.7.2/luajit/lib -lluajit-5.1 -lm -ldl -lpcre -lssl -lcrypto -ldl -lz
objs/addon/src/ngx_http_lua_regex.o: In function `ngx_http_lua_regex_free_study_data':
/home/yjj/tools/ngx_openresty-1.9.7.2/build/nginx-1.9.7/../ngx_lua-0.10.0/src/ngx_http_lua_regex.c:1948: undefined reference to `pcre_free_study'
objs/addon/src/ngx_http_lua_regex.o: In function `ngx_http_lua_ffi_destroy_regex':
/home/yjj/tools/ngx_openresty-1.9.7.2/build/nginx-1.9.7/../ngx_lua-0.10.0/src/ngx_http_lua_regex.c:2335: undefined reference to `pcre_free_study'
collect2: ld returned 1 exit status
gmake[2]: *** [objs/nginx] Error 1
gmake[2]: Leaving directory `/home/yjj/tools/ngx_openresty-1.9.7.2/build/nginx-1.9.7'
gmake[1]: *** [build] Error 2
gmake[1]: Leaving directory `/home/yjj/tools/ngx_openresty-1.9.7.2/build/nginx-1.9.7'
gmake: *** [all] Error 2
解决办法:
yum -y install libtool
指定pcre源码路径编译安装
nginx
日志:¶
使用json格式
log_format json '{'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"time_local":"$time_local",'
'"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"request_method":"$request_method",'
'"request":"$request",'
'"uri":"$uri",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_status":"$upstream_status",'
'"upstream_addr":"$upstream_addr"'
'}';
access_log /var/log/nginx/access.log json;
带request body
log_format json '{'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request_body":"$request_body",'
'"time_local":"$time_local",'
'"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"request_method":"$request_method",'
'"request":"$request",'
'"uri":"$uri",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_status":"$upstream_status",'
'"upstream_addr":"$upstream_addr"'
'}';
access_log /var/log/nginx/access.log json;
squid,nginx,lighttpd反向代理的区别¶
反向代理从传输上分可以分为2种:
- 同步模式(apache-mod_proxy和squid)
- 异步模式(lighttpd和nginx)
在nginx的文档说明中,提到了异步传输模式并提到它可以减少后端连接数和压力,这是为何?
传统的代理(apache/squid)的同步传输和lighttpd,nginx的异步传输的差异。
同步传输:浏览器发起请求,而后请求会立刻被转到后台,于是在浏览器和后台之间就建立了一个通道。在请求发起直到请求完成,这条通道都是一直存在的。
异步传输:浏览器发起请求,请求不会立刻转到后台,而是将请求数据(header)先收到nginx上,然后nginx再把这个请求发到后端,后端处理完之后把数据返回到nginx上,nginx将数据流发到浏览器,这点和lighttpd有点不同,lighttpd是将后端数据完全接收后才发送到浏览器。
小结:apache和squid的反向会增加后端web的负担,因为每个用户请求都会在proxy上与后端server建立的长久链接,知道数据取完前,连接都不会消失。因为wan速度与lan速度的不同,虽然lan之间的速度是极度快的,但是用户的wan连接决定了这个时间长。而lighttpd和nginx的异步模式,是不管你用户要求的数据有多大,都是先收下来,再与后端联系,这是非常迅速的速度,所以proxy与后端连接时间也会很短,几十M的东西也是几秒内。后端不需要维护这么多连接。而lighttpd也和nginx不同的异步,lighttpd是先收完再转向客户浏览器,而nginx是边收数据边转向用户浏览器。
那么这到底有什么好处呢?
- 假设用户执行一个上传文件操作,因为用户网速又比较慢,因此需要花半个小时才能把文件传到服务器。squid的同步代理在用户开始上传后就和后台建立了连接,半小时后文件上传结束,由此可见,后台服务器连接保持了半个小时;而nginx异步代理就是先将此文件收到nginx上,因此仅仅是nginx和用户保持了半小时连接,后台服务器在这半小时内没有为这个请求开启连接,半小时后用户上传结束,nginx才将上传内容发到后台,nginx和后台之间的带宽是很充裕的,所以只花了一秒钟就将请求发送到了后台,由此可见,后台服务器连接保持了一秒。同步传输花了后台服务器半个小时,异步传输只花一秒,可见优化程度很大。
- 在上面这个例子中,假如后台服务器因为种种原因重启了,上传文件就自然中断了,这对用户来说是非常恼火的一件事情,想必各位也有上传文件传到一半被中断的经历。用nginx代理之后,后台服务器的重启对用户上传的影响减少到了极点,而nginx是非常稳定的并不需要常去重启它,即使需要重启,利用kill -HUP就可以做到不间断重启nginx。
- 异步传输可以令负载均衡器更有保障,为什么这么说呢?在其它的均衡器(lvs/haproxy/apache等)里,每个请求都是只有一次机会的,假如用户发起一个请求,结果该请求分到的后台服务器刚好挂掉了,那么这个请求就失败了;而nginx因为是异步的,所以这个请求可以重新发往下一个后台,下一个后台返回了正常的数据,于是这个请求就能成功了。还是用用户上传文件这个例子,假如不但用了nginx代理,而且用了负载均衡,nginx把上传文件发往其中一台后台,但这台服务器突然重启了,nginx收到错误后,会将这个上传文件发到另一台后台,于是用户就不用再花半小时上传一遍。
- 假如用户上传一个10GB大小的文件,而后台服务器没有考虑到这个情况,那么后台服务器岂不要崩溃了。用nginx就可以把这些东西都拦在nginx上,通过nginx的上传文件大小限制功能来限制,另外nginx性能非常有保障,就放心的让互联网上那些另类的用户和nginx对抗去吧。
用异步传输会造成问题:
后台服务器有提供上传进度的功能的话,用了nginx代理就无法取得进度,这个需要使用nginx的一个第三方模块来实现。
nginx故障集锦¶
1 nginx 403 forbidden多种原因及故障模拟重现¶
基础环境
[root@ruin ~]# cat /etc/redhat-release
CentOS release 6.6 (Final)
[root@ruin ~]# uname -a
Linux ruin 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@ruin ~]# ifconfig|sed -n '2p'|awk -F'[ :]+' '{print $4}'
10.0.11.103
[root@ruin ~]# tail -1 /etc/hosts
10.0.11.103 www.liurui.space
[root@ruin ~]# yum install nginx -y
[root@ruin ~]# nginx -v
nginx version: nginx/1.10.2
[root@ruin ~]# /etc/init.d/nginx start
[root@ruin ~]# netstat -lntp|grep 80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 60788/nginx
tcp 0 0 :::80 :::* LISTEN 60788/nginx
[root@ruin ~]# vim /etc/nginx/conf.d/liurui.conf
server {
listen 80;
server_name www.liurui.space;
location / {
root /data/www;
index index.html index.htm;#<==配置首页文件配置
}
access_log off;
}
[root@ruin ~]# mkdir -p /data/www
[root@ruin ~]# echo "this is a test index.html" >>/data/www/index.html
[root@ruin ~]# ls /data/www/
index.html #<==存在首页文件
[root@ruin ~]# nginx -t
[root@ruin ~]# nginx -s reload
[root@ruin ~]# curl www.liurui.space
this is a test index.html
[root@ruin ~]# mv /data/www/index.html /tmp/ <==移除首页文件
[root@ruin ~]# curl -I -s www.liurui.space|head -1
HTTP/1.1 403 Forbidden
当没有首页文件时可以使用参数autoindex on;,当找不到首页文件时,会展示目录结构,这个功能一般不要用除非有需求。
[root@ruin ~]# vi /etc/nginx/conf.d/liurui.conf
server {
listen 80;
server_name www.liurui.space;
location / {
root /data/www;
#index index.html index.htm;#<==配置首页文件配置
autoindex on;
}
access_log off;
}
[root@ruin ~]# nginx -t
[root@ruin ~]# nginx -s reload
[root@ruin ~]# curl www.liurui.space
<html>
<head><title>Index of /</title></head>
<body bgcolor="white">
<h1>Index of /</h1><hr><pre><a href="../">../</a>
</pre><hr></body>
</html>
[root@ruin ~]# curl -I -s www.liurui.space
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 05 Jan 2017 07:17:35 GMT
Content-Type: text/html
Connection: keep-alive
[root@ruin ~]# cat /etc/nginx/conf.d/liurui.conf
server {
listen 80;
server_name www.liurui.space;
location / {
root /data/www;
index index.html index.htm;#<==配置首页文件配置
}
access_log off;
}
[root@ruin ~]# cat /data/www/index.html
this is a test index.html
[root@ruin ~]# curl -I -s www.liurui.space|head -1
HTTP/1.1 200 OK
[root@ruin ~]# chmod 700 /data/www/index.html
[root@ruin ~]# curl -I -s www.liurui.space|head -1
HTTP/1.1 403 Forbidden
[root@ruin ~]# chmod 644 /data/www/index.html
[root@ruin ~]# curl -I -s www.liurui.space|head -1
HTTP/1.1 200 OK
[root@ruin ~]# cat /etc/nginx/conf.d/liurui.conf
server {
listen 80;
server_name www.liurui.space;
location / {
root /data/www;
index index.html index.htm;#<==配置首页文件配置
allow 192.168.1.0/24;
deny all;
}
access_log off;
}
[root@ruin ~]# nginx -t
[root@ruin ~]# nginx -s reload
[root@ruin ~]# curl -I -s www.liurui.space|head -1
HTTP/1.1 403 Forbidden
nginx的一些变量¶
$arg_PARAMETER 这个变量包含在查询字符串时GET请求PARAMETER的值。
$args 这个变量等于请求行中的参数。
$binary_remote_addr 二进制码形式的客户端地址。
$body_bytes_sent
$content_length 请求头中的Content-length字段。
$content_type 请求头中的Content-Type字段。
$cookie_COOKIE cookie COOKIE的值。
$document_root 当前请求在root指令中指定的值。
$document_uri 与$uri相同。
$host 请求中的主机头字段,如果请求中的主机头不可用,则为服务器处理请求的服务器名称。
$is_args 如果$args设置,值为"?",否则为""。
$limit_rate 这个变量可以限制连接速率。
$nginx_version 当前运行的nginx版本号。
$query_string 与$args相同。
$remote_addr 客户端的IP地址。
$remote_port 客户端的端口。
$remote_user 已经经过Auth Basic Module验证的用户名。
$request_filename 当前连接请求的文件路径,由root或alias指令与URI请求生成。
$request_body 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义。
$request_body_file 客户端请求主体信息的临时文件名。
$request_completion 请求完成
$request_method 这个变量是客户端请求的动作,通常为GET或POST。包括0.8.20及之前的版本中,这个变量总为main request中的动作,如果当前请求是一个子请求,并不使用这个当前请求的动作。
$request_uri 这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI。
$schemeHTTP 方法(如http,https)。按需使用,例:
rewrite ^(.+)$ $scheme://example.com$1 redirect;
$server_addr 服务器地址,在完成一次系统调用后可以确定这个值,如果要绕开系统调用,则必须在listen中指定地址并且使用bind参数。
$server_name 服务器名称。
$server_port 请求到达服务器的端口号。
$server_protocol 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
$uri 请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改。
上面的这些是nginx支持一些内置的变量,也可以自定义,例如$http_x_forwarded_for,这个变量就是自定义的,用来获得用了代理用户的真实IP:
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
Nginx负载均衡,反向代理¶
upstream¶
简单配置
- 在http标签下,添加upstream
upstream linuxidc {
server 10.0.0.10:7080;
server 10.0.0.20:8980;
}
- 配置proxy_pass
location / {
root html;
index index.html index.htm;
proxy_pass http://linuxidc;
}
The address can also be specified using variables (1.11.3):
proxy_pass $upstream;
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。虽然这种方式简便、成本低廉。 缺点是:可靠性低和负载分配不均衡。适用于图片服务器集群和纯静态页面服务器集群。
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。默认为1
upstream linuxidc{
server 10.0.0.77 weight=5;
server 10.0.0.88 weight=10;
}
按权重轮询的方式,服务器是随机的,分散后按权重比重分配
➜ nginx-cs for n in {1..10};do curl 127.0.0.1:60000;done
82 3
81 6
83 1
81 6
82 3
81 6
81 6
82 3
81 6
81 6
测试配置
➜ conf.d ls
81.conf 82.conf 83.conf default.conf
➜ conf.d cat -n *
1 server {
2 listen 81;
3 server_name localhost;
4 location / {
5 root /usr/share/nginx/html;
6 index 81index.html ;
7 }
8 }
1 server {
2 listen 82;
3 server_name localhost;
4 location / {
5 root /usr/share/nginx/html;
6 index 82index.html ;
7 }
8 }
1 server {
2 listen 83;
3 server_name localhost;
4 location / {
5 root /usr/share/nginx/html;
6 index 83index.html ;
7 }
8 }
1 upstream yang.com {
2 server 127.0.0.1:81 weight=6;
3 server 127.0.0.1:82 weight=3;
4 server 127.0.0.1:83 weight=1;
5 }
6 server {
7 listen 80;
8 server_name _;
9
10 location / {
11 proxy_pass http://yang.com;
12 }
13 }
请求发送到激活连接数最少的服务器,服务器权重也会成为选择因素
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
}
least_time header | last_byte;
header表示是计算从后台返回的第一个字节,last_byte计算的是从后台返回的所有数据时间
请求发送到具有最短平均响应时间和最少活动连接数的服务器,同时考虑服务器的权重。如果有几个这样的服务器,则使用加权循环平衡方法依次尝试它们.
请求发送到哪个服务器取决于一个用户端定义的关键词,如文本,变量或两者组合。例如,这个关键词可以是来源IP和端口,或者URI:
upstream backend {
hash $request_uri consistent;
server backend1.example.com;
server backend2.example.com;
}
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
upstream favresin{
ip_hash;
server 10.0.0.10:8080;
server 10.0.0.11:8080;
}
按后端服务器的响应时间来分配请求,响应时间短的优先分配。与weight分配策略类似。
upstream favresin{
server 10.0.0.10:8080;
server 10.0.0.11:8080;
fair;
}
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
注意:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法。
upstream resinserver{
server 10.0.0.10:7777;
server 10.0.0.11:8888;
hash $request_uri;
hash_method crc32;
}
upstream一些参数的含义分别如下:
weight=number
设置权重, 默认为1。weight越大,负载的权重就越大。down
: 表示当前的server暂时不参与负载.max_fails
: 允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream 模块定义的错误.fail_timeout
: max_fails次失败后,暂停的时间。backup
: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。resolve
: 监视与服务器的域名对应的IP地址的改变,并自动修改上游配置,而不需要重新启动nginx。服务器组必须驻留在共享内存中。slow_start=time
:设置服务器将其权重从零恢复到标称值的时间,当不正常服务器变得正常时,或者当服务器在一段时间之后变为可用时,其被认为不可用。默认值为零,即禁用慢启动。
proxy¶
http代理,以及通过TCP、UDP、UNIX-domain sockets的方式代理数据流
Module ngx_http_proxy_module Module ngx_stream_proxy_module
配置文件示例
server {
listen 10.0.0.136:80;
location / {
resolver 10.0.0.200; # DNS服务器IP地址,可以指定多个,以轮训方式请求
resolver_timeout 30s; # 解析超时时间
proxy_pass http://$http_host$request_uri;
}
}
客户端访问
export http_proxy=http://10.0.0.136:80 # 设定环境变量,指定代理服务器的ip及端口,或者浏览器中添加代理服务器IP地址
配置文件示例
server {
listen 80;
server_name 10.0.0.136; #根据环境介绍,nginx server ip
location / {
proxy_pass http://10.0.0.137; #被代理的服务器ip
}
location /web2 { #多个location
proxy_pass http://10.0.0.111;
proxy_set_header X-Real-IP $remote_addr;
}
}
proxy_set_header Host $host;
proxy_set_header X-Forward-For $remote_addr;
使用request_body记录POST请求日志¶
添加$request_body字段
常规不带request_body
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
带request_body
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" $request_body '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format json '{'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"time_local":"$time_local",'
'"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"request_method":"$request_method",'
'"request":"$request",'
#'"request_body":"$request_body",'
'"uri":"$uri",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_status":"$upstream_status",'
'"upstream_addr":"$upstream_addr"'
'}';
完整配置¶
root@ubuntu75:/etc/nginx# egrep -v "^#|^$" nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" $request_body '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format json '{'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"time_local":"$time_local",'
'"@timestamp":"$time_iso8601",'
'"@source":"$server_addr",'
'"request_method":"$request_method",'
'"request":"$request",'
#'"request_body":"$request_body",'
'"uri":"$uri",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_status":"$upstream_status",'
'"upstream_addr":"$upstream_addr"'
'}';
access_log /var/log/nginx/access.log json;
error_log /var/log/nginx/error.log ;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
root@ubuntu75:/etc/nginx/conf.d# cat kibana.conf
server {
listen 80;
server_name _;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/htpasswd.users;
location / {
proxy_pass http://127.0.0.1:5601;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
upstream¶
1、轮询¶
2、加权循环¶
- weight=number默认是加权循环,先按weight的权重分配,
- max_fails最大失败次数2次,失败超时时间30s,失败找其他的机器 默认权重是1
- fail_timeout 超时失败时间
- backup 之前所有的访问都失效开始尝试backup
upstream backend {
server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.1:8080 weight=1 max_fails=2 fail_timeout=30s backup;
}
3、ip_hash¶
ip_hash确保同一个客户端的ip每次都去同一台服务器
down 当前server不参加负载
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}
4、hash¶
5、least_conn¶
最小活动连接数,权重的负载均衡
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
}
6、least_time header|last_byte;¶
响应时间最短,活动连接数
upstream backend {
least_time header;
server backend1.example.com;
server backend2.example.com;
}
推荐使用:加权循环¶
upstream backend {
server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.1:8080 weight=1 max_fails=2 fail_timeout=30s backup;
}
upstream¶
1、轮询¶
2、加权循环¶
- weight=number默认是加权循环,先按weight的权重分配,
- max_fails最大失败次数2次,失败超时时间30s,失败找其他的机器 默认权重是1
- fail_timeout 超时失败时间
- backup 之前所有的访问都失效开始尝试backup
upstream backend {
server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.1:8080 weight=1 max_fails=2 fail_timeout=30s backup;
}
3、ip_hash¶
ip_hash确保同一个客户端的ip每次都去同一台服务器
down 当前server不参加负载
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}
4、hash¶
5、least_conn¶
最小活动连接数,权重的负载均衡
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
}
6、least_time header|last_byte;¶
响应时间最短,活动连接数
upstream backend {
least_time header;
server backend1.example.com;
server backend2.example.com;
}
推荐使用:加权循环¶
upstream backend {
server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.1:8080 weight=1 max_fails=2 fail_timeout=30s backup;
}
自建CA,配置nginx的https访问¶
openssl是一个开源程序的套件、这个套件有三个部分组成:一是libcryto,这是一个具有通用功能的加密库,里面实现了众多的加密库;二是libssl,这个是实现ssl机制的,它是用于实现TLS/SSL的功能;三是openssl,是个多功能命令行工具,它可以实现加密解密,甚至还可以当CA来用,可以让你创建证书、吊销证书。
默认情况ubuntu和CentOS上都已安装好openssl。CentOS 6.x 上有关ssl证书的目录结构:
/etc/pki/CA/
certs
crl 吊销的证书
newcerts 存放CA签署(颁发)过的数字证书(证书备份目录)
private 用于存放CA的私钥
/etc/pki/tls/
cert.pem 软链接到certs/ca-bundle.crt
certs/ 该服务器上的证书存放目录,可以房子自己的证书和内置证书
ca-bundle.crt 内置信任的证书
private 证书密钥存放目录
openssl.cnf openssl的CA主配置文件
1 自建CA¶
系统环境
[root@ruin ~]# cat /etc/redhat-release
CentOS release 6.6 (Final)
[root@ruin ~]# uname -a
Linux ruin 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@ruin ~]# ifconfig|sed -n '2p'|awk -F'[ :]+' '{print $4}'
10.0.11.102
CA要给别人颁发证书,首先自己得有一个作为根证书,我们得在一切工作之前修改好CA的配置文件、序列号、索引等等。
[root@ruin ~]# vi /etc/pki/tls/openssl.cnf
[ CA_default ]
dir = /etc/pki/CA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
#unique_subject = no # Set to 'no' to allow creation of
# several ctificates with same subject.
new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem# The private key
RANDFILE = $dir/private/.rand # private random number file
创建两个初始文件
[root@ruin ~]# touch /etc/pki/CA/{serial,index.txt}
[root@ruin ~]# echo 01 > /etc/pki/CA/serial
安全起见,修改cakey.pem私钥文件权限为600或400,在子shell中执行该命令 umask 077不影响当前shell的umask
[root@ruin ~]# (umask 077;openssl genrsa -out /etc/pki/CA/private/cakey.pem 1024)
[root@ruin ~]# ll /etc/pki/CA/private/cakey.pem
-rw------- 1 root root 1675 1月 12 16:41 /etc/pki/CA/private/cakey.pem
使用req命令生成自签证书:
[root@ruin ~]# openssl req -new -x509 -key /etc/pki/CA/private/cakey.pem -out /etc/pki/CA/cacert.pem -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:CN
State or Province Name (full name) []:北京
Locality Name (eg, city) [Default City]:北京
Organization Name (eg, company) [Default Company Ltd]:北京九歌
Organizational Unit Name (eg, section) []:jiuge
Common Name (eg, your name or your server's hostname) []:51mcp.com
Email Address []:
会提示输入一些内容,因为是私有的,所以可以随便输入(之前修改的openssl.cnf会在这里呈现),最好记住能与后面保持一致。 上面的自签证书cacert.pem应该生成在/etc/pki/CA下。
2 nginx 配置ssl密钥¶
以上都是在CA服务器上做的操作,而且只需进行一次,现在转到nginx服务器上执行:
nginx服务器基础环境
[root@ruin ~]# cat /etc/redhat-release
CentOS release 6.6 (Final)
[root@ruin ~]# uname -a
Linux ruin 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
[root@ruin ~]# ifconfig|sed -n '2p'|awk -F'[ :]+' '{print $4}'
10.0.11.103
[root@ruin ~]# rpm -qa nginx
nginx-1.10.2-1.el6.x86_64
[root@ruin ~]# mkdir /etc/nginx/ssl -p
[root@ruin ~]# (umask 077;openssl genrsa -out /etc/nginx/ssl/nginx.key 1024)
[root@ruin ~]# ll /etc/nginx/ssl/nginx.key
-rw------- 1 root root 1679 1月 12 16:57 /etc/nginx/ssl/nginx.key
[root@ruin ~]# openssl req -new -key /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.csr -days 3650
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:北京
Locality Name (eg, city) [Default City]:北京
Organization Name (eg, company) [Default Company Ltd]:北京九歌
Organizational Unit Name (eg, section) []:jiuge
Common Name (eg, your name or your server's hostname) []:51mcp.com
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
[root@ruin ~]# scp /etc/nginx/ssl/nginx.csr root@10.0.11.102:/tmp
[root@ruin ~]# openssl ca -in /tmp/nginx.csr -out /etc/pki/CA/certs/nginx.crt -days 3650
Using configuration from /etc/pki/tls/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 1 (0x1)
Validity
Not Before: Jan 12 09:17:58 2017 GMT
Not After : Jan 10 09:17:58 2027 GMT
Subject:
countryName = CN
stateOrProvinceName = \E5\8C\97\E4\BA\AC
organizationName = \E5\8C\97\E4\BA\AC\E4\B9\9D\E6\AD\8C
organizationalUnitName = jiuge
commonName = 51mcp.com
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
6D:A4:3F:D0:74:AF:3B:7C:BE:0C:AD:6A:33:C3:12:59:11:C8:10:B0
X509v3 Authority Key Identifier:
keyid:DA:63:5D:6A:CD:AE:64:50:B8:61:E5:44:0A:BC:65:05:8D:FE:41:CF
Certificate is to be certified until Jan 10 09:17:58 2027 GMT (3650 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
[root@ruin ~]# scp /etc/pki/CA/certs/nginx.crt root@10.0.11.103:/etc/nginx/ssl/
3 配置nginx https加密¶
[root@ruin conf.d]# vim /etc/nginx/conf.d/ssl.conf
server {
listen 443;
server_name 10.0.11.103;
root /data/www;
index index.html index.htm;
ssl on;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
# ssl_prefer_server_ciphers on;
}
[root@ruin ~]# mkdir /data/www -p
[root@ruin ~]# echo 'test ssl is ok!' >/data/www/index.html
[root@ruin ~]# vim /etc/nginx/conf.d/default.conf
[root@ruin ~]# egrep -v "#|^$" /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name 10.0.11.103;
root /usr/share/nginx/html;
include /etc/nginx/default.d/*.conf;
return 301 https://$server_name$request_uri;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
[root@ruin ~]# nginx -t
[root@ruin ~]# nginx -s reload
saltstack¶
tomcat¶
tomcat
Tomcat日志切割¶
环境¶
[root@server /opt/apache-tomcat-icbc/bin]# sh version.sh
Using CATALINA_BASE: /opt/apache-tomcat-icbc
Using CATALINA_HOME: /opt/apache-tomcat-icbc
Using CATALINA_TMPDIR: /opt/apache-tomcat-icbc/temp
Using JRE_HOME: /usr
Using CLASSPATH: /opt/apache-tomcat-icbc/bin/bootstrap.jar:/opt/apache-tomcat-icbc/bin/tomcat-juli.jar
Server version: Apache Tomcat/7.0.57
Server built: Nov 3 2014 08:39:16 UTC
Server number: 7.0.57.0
OS Name: Linux
OS Version: 2.6.32-504.el6.x86_64
Architecture: amd64
JVM Version: 1.7.0_71-b14
JVM Vendor: Oracle Corporation
案例¶
[root@server /opt/apache-tomcat-icbc/logs]# du -sh catalina.out
210M catalina.out
解决¶
# 1. 安装cronolog
[root@server ~]#yum –y install cronolog
[root@server ~]# which cronolog
/usr/sbin/cronolog
# 2. 修改bin/catalina.sh
# 1. 找到下面行并把它用#注释掉
touch "$CATALINA_OUT"
# 2. 替换下面的行(有两处,不过一般在 -security 中的那一行不需要去关注,不妨两处全替换了)
>> "$CATALINA_OUT" 2>&1 "&"
替换为
2>&1 |/usr/sbin/cronolog "$CATALINA_BASE/logs/catalina-%Y-%m-%d.out" &
# 3. 重启tomcat
[root@server /opt/apache-tomcat-icbc/bin]#sh shutdown.sh
[root@server /opt/apache-tomcat-icbc/bin]#sh startup.sh
tomcat¶
tomcat监控¶
在Zabbix中,JMX监控数据的获取由专门的代理程序来实现,即Zabbix-Java-Gateway
来负责数据的采集,Zabbix-Java-Gateway
和JMX
的Java
程序之间通信获取数据
vim /etc/zabbix/zabbix_agentd.conf #编辑配置文件引用key
Include=/etc/zabbix/zabbix_agentd.d/*.conf
mkdir /etc/zabbix/scripts #存放Shell脚本
yum install zabbix-java-gateway java-1.8.0-openjdk -y
systemctl start zabbix-java-gateway
netstat -lntup|grep 10052
tcp6 0 0 :::10052 :::* LISTEN 13042/java
4.修改zabbix-server 配置文件
vim /etc/zabbix/zabbix_server.conf
JavaGateway=192.168.90.11 # java gateway地址(如果和zabbix-server装一起可以写127.0.0.1)
JavaGatewayPort=10052 #java gateway端口,默认端口10052
StartJavaPollers=5 #启动进程轮询java gateway
5.重启zabbix-server
systemctl restart zabbix-server
6.开启tomcat的远程jvm配置文件
vim /usr/local/tomcat/bin/catalina.sh #找到自己本机tomcat路径(如果是salt来管,修改salt模板即可)
CATALINA_OPTS="$CATALINA_OPTS
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=12345
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=192.168.90.11"
#远程jvm配置文件解释
CATALINA_OPTS="$CATALINA_OPTS
-Dcom.sun.management.jmxremote # #启用远程监控JMX
-Dcom.sun.management.jmxremote.port=12345 #jmx远程端口,Zabbix添加时必须一致
-Dcom.sun.management.jmxremote.authenticate=false #不开启用户密码认证
-Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=192.168.90.11" #运行tomcat服务IP(不要填写错了)
7.重启tomcat服务
/usr/local/tomcat/bin/shutdown.sh
/usr/local/tomcat/bin/startup.sh
vpn¶
vpn
ikev2-vpn-server¶
镜像
下载镜像
docker pull gaomd/ikev2-vpn-server
启动IKEv2 VPN SERVER
docker run --privileged -d --name ikev2-vpn-server --restart=always -p 500:500/udp -p 4500:4500/udp gaomd/ikev2-vpn-server:0.3.0
生成.mobileconfig
配置文件
用于 IOS/macOS
直接安装
HOST可以写域名,或者IP
docker run --privileged -i -t --rm --volumes-from ikev2-vpn-server -e "HOST=vpn1.example.com" gaomd/ikev2-vpn-server:0.3.0 generate-mobileconfig > ikev2-vpn.mobileconfig
安装配置文件即可使用
zabbix¶
zabbix¶
Zabbix告警记录¶
Zabbix poller processes more than 75% busy¶
可能原因,zabbix_server不断轮训agent,向agent提取数据,所以报如上错误
解决办法,agent主动将值发送给server
Docker容器监控¶
步骤:
- zabbix-agent 加载
zabbix_module_docker.so
模块(配置之后,重启zabbix-agent) - 导入模块,添加主机
监控方案¶
使用 zabbix-docker-monitoring 插件。
gh-pages有已经编译好的so文件,可以直接使用
项目用来持续集成的脚本思路,可以学习
配置服务端
Zabbix 是 C/S 架构,服务端最好能配置在一台独立的宿主机上。
服务端 docker-compose 文件:
容器方式运行 Zabbix-agent
可以无需在宿主机安装 Zabbix-agent,直接运行官方的容器即可。
运行 Zabbix-agent 容器:
docker run \
--name=zabbix-agent-xxl \
-h $(hostname) \
-p 10050:10050 \
-v /:/rootfs \
-v /var/run:/var/run \
-e"ZA_Server=" \
-d monitoringartist/zabbix-agent-xxl-limited:latest
配置容器
- 修改 ZA_Server,直接改成服务器 ip。
如果想覆盖容器中 agent 的配置变量,可以在 run 的时候使用
-e ZA_Variable=value
的方法,但是对 AllowRoot, LoadModulePath,
LoadModule, LogType 的配置无法覆盖,其中 AllowRoot 的默认值就是 1,参看
Github Issue。
宿主机直接运行 Zabbix-agent¶
容器的方式运行 zabbix-agent
不支持 docker.xnet
数据的监控,想要监控 docker.xnet
数据,得直接在宿主机上运行
zabbix-agent
,并加载 zabbix_module_docker.so
,参看 Github
Issue。
添加 zabbix 用户和组
groupadd zabbix useradd -g zabbix zabbix
安装 zabbix-agent
下载或编译 zabbix_module_docker.so:
启动 zabbix_agentd
使用 systemd 管理进程,关于 systemd 参考 阮一峰的网络日志
创建 /lib/systemd/system/zabbix-agentd.service
文件:
[Unit]
Description=Zabbix Agent
After=syslog.target
After=network.target
[Service]
Environment="CONFFILE=/usr/local/etc/zabbix_agentd.conf"
Type=forking
Restart=on-failure
PIDFile=/tmp/zabbix_agentd.pid
KillMode=control-group
ExecStart=/usr/local/sbin/zabbix_agentd -c $CONFFILE
ExecStop=/bin/kill -SIGTERM $MAINPID
RestartSec=10s
[Install]
WantedBy=multi-user.target
执行下面命令告知 systemctl 如何启动 zabbix-agentd
sudo systemctl enable zabbix-agentd.service
- 配置加载项
修改 zabbix-agentd 配置文件 /usr/local/etc/zabbix_agentd.conf
中的下面几个参数:
Server=Zabbix-Server-IP
ServerActive=Zabbix-Server-IP
Hostname=Current-Host-Name
Timeout=30
# 目录可以自行设定 注意属主及属组
LoadModulePath=/usr/lib/zabbix/agent
LoadModule=zabbix_module_docker.so
运行下面命令启动 zabbix-agentd
systemctl start zabbix-agentd.service
- 启动失败分析
- 如果启动失败,查看日志
报错:
zabbix_agentd [xxxxx]: cannot attach to existing shared memory: [13] Permission denied
cannot allocate shared memory for collector
可能是 zabbix_module_docker.so 编译错误,重新编译一次即可。
设置监控
导入模板
zabbix监控snmp设备¶
模板
参考zabbix自带模板
zbx_export_templates-Network Traffic Check By Public Snmp V2
使用模板的时候需要填写宏, {$SNMP_COMMUNITY}
团体名 ->
示例:zabbix通过AC自动发现AP,并记录对应AP上连入的用户¶
AC6005
添加自动发现
SNMP OID
OID具体参考对应设备及版本的MIB文档
discovery[{#SNMPVALUE},SNMPv2-SMI::enterprises.2011.6.139.2.6.1.1.7]
使用 此SNMP OID
SNMPv2-SMI::enterprises.2011.6.139.2.6.1.1.7
可以列出AC管理的所有AP

zabbix-snmp-02
而后将此SNMPINDEX
用于其他的 SNMP OID
监控项原型

zabbix-snmp-01
另外可根据需求添加触发器类型,以及图形原型
zabbix监控snmp问题记录¶
网页提示无法解析OID
查看zabbix_server日志,详细内容如下:
...
7147:20170505:144603.955 item "s5700_172.16.1.254:ifOutOctets[1,2]" became not supported: snmp_parse_oid(): cannot parse OID "IF-MIB::ifOutOctets.26".
7147:20170505:144703.978 item "s5700_172.16.1.254:sysUpTime" became not supported: snmp_parse_oid(): cannot parse OID "SNMPv2-MIB::sysUpTime.0".
...
解决办法:
安装 snmp-mibs-downloader
root@ubuntu-server04:~# apt-get install snmp-mibs-downloader
service¶
服务相关笔记
etcd¶
A distributed, reliable key-value store for the most critical data of a distributed system.
protobuf¶
Protocol Buffers - Google’s data interchange format https://developers.google.com/protocol-buffers/
安装¶
python¶
https://github.com/google/protobuf/tree/master/python
获取protobuf-python
wget https://github.com/google/protobuf/releases/download/v3.5.0/protobuf-python-3.5.0.tar.gz
python版本, 2.6或更高
python -V
当运行
setup.py
时, 会自动安装setuptools, 如果想手动安装看这个构建
protoc
的C++代码, 或者安装二进制包. 如果安装的二进制包, 确保更当前包版本一致.protoc –version
OS X
没安装过, 可以使用如下指令安装
brew install protobuf
- 构建并运行测试
python setup.py build
python setup.py test
使用 C++ 实现 , 需要先编译libprotobuf.so
:
cd .. && make
OS X
如果使用Homebrew安装的Python, 需要确保其他版本的protobuf没有安装,
Homebrew 安装的 Python
搜索 libprotobuf.so
会在搜索
../src/.libs.
目录之前搜索 /usr/local/lib
你可以不链接通过Homebrew安装的protobuf 或在编译之前安装libprotobuf
brew unlink protobuf
or
cd .. && make install
On other *nix:
You must make libprotobuf.so dynamically available. You can either install libprotobuf you built earlier, or set LD_LIBRARY_PATH:
export LD_LIBRARY_PATH=../src/.libs
or
cd .. && make install
To build the C++ implementation run:
python setup.py build --cpp_implementation
Then run the tests like so:
python setup.py test --cpp_implementation
安装
sudo python setup.py install
or:
cd .. && make install
python setup.py install --cpp_implementation
问题记录¶
python setup.py test
, 依赖 six>=1.9
, 而系统已经安装 six 1.4.1
sudo pip install six --upgrade --ignore-installed six
编译参数¶
php¶
./configure --prefix=/usr/local/qqwebsrv/php7 --with-pear=/usr/local/qqwebsrv/php7/PEAR --with-config-file-scan-dir=/usr/local/qqwebsrv/php7/lib/conf.d --enable-libxml --enable-session --with-pcre-regex --enable-xml --enable-simplexml --enable-filter --disable-debug --enable-inline-optimization --disable-rpath --disable-static --enable-shared --with-pic --with-gnu-ld --enable-re2c-cgoto --enable-hash --with-mhash --enable-fpm --with-pdo-mysql --enable-sockets --with-curl --with-zlib --enable-bcmath --enable-ftp --with-gd --enable-exif --with-bz2 --enable-calendar --enable-sysvmsg --enable-sysvsem --enable-sysvshm --enable-shmop --enable-soap --with-readline --enable-zip --with-xsl --with-mhash --with-gettext --with-gmp --enable-mbstring --with-mcrypt --enable-calendar --with-openssl --enable-pcntl --with-xmlrpc --enable-wddx --enable-dba
shell¶
scripts¶
命令¶
awk¶
判断1-100
,如果数字为3的倍数, 输出Three
, 数字为5的倍数,
输出Five
,同时为3,5的倍数, 输出three and five
seq 100|awk '{if($0 % 3 == 0) print $0,"Three" ; \
if($0 % 5 == 0 )print $0,"Five"; \
if($0 % 3 == 0 && $0 % 5 == 0) print $0,"three and five"; \
if($0 % 3 != 0 && $0 % 5 != 0) print $0,"0";}'
输出单双引号¶
# 双引号
awk '{print "\""}'
# 单引号
echo 1 | awk '{print "\047" }'
# 或者
awk '{print "'\''"}'
awk 多个$用法¶
[yjj@centos ~]$ cat a
1 2 3 4 5
1 2 3 a 5
1 2 3 b 5
[yjj@centos ~]$ awk '{print $$4}' a
4
1 2 3 a 5
1 2 3 b 5
awk 字符集问题¶
export LC_ALL=C
echo {a..e} {A..E} {a..e}|xargs -n1|awk '/[A-Z]/'
A
B
C
D
E
awk FIELDWIDTHS¶
[root@centos ~]# echo "20161002"|awk -vFIELDWIDTHS="4 2 2" -vOFS="-" '{print $1,$2,$3}'
2016-10-02
[root@centos ~]# awk -v FIELDWIDTHS="4 2 2" -v OFS="-" '{print $1,$2,$3}' <<<20160202
2016-02-02
[root@centos ~]# awk -vFIELDWIDTHS="4 2 2" '{printf "%s-%s-%s\n",$1,$2,$3}' <<<20160202
2016-02-02
[root@centos ~]# sed -r 's@(.{4})(..)(..)@\1-\2-\3@' <<<"20161002"
2016-10-02
案例¶
[yjj@centos awk]$ cat chengji
yuwen shuxue yingyu
10 10 10
10 10 10
10 10 10
[yjj@centos awk]$ awk '{a=$1+$2+$3;print $0 "\t" (NR==1?"Total":a)}' chengji
yuwen shuxue yingyu Total
10 10 10 30
10 10 10 30
10 10 10 30
[yjj@centos awk]$ awk '{NR==1;$4="Total"}NR>1{$4=$1+$2+$3}1' OFS="\t" chengji
yuwen shuxue yingyu Total
10 10 10 30
10 10 10 30
10 10 10 30
用awk如何自动填充空数据的列为最近的不为空的数据¶
用awk如何自动填充空数据的列为最近的不为空的数据?
比如以下文本:
name1,1,21,address1
name2,0,,
name3,0,,
name4,1,30,address4
name5,0,24,address5
name6,1,,
name7,1,29,address7
其中name2、name3和name6的第三列和第四列都为空值,我想实现这些空值自动填充为它们上方的相应列不为空的数据,如下所示:
name1,1,21,address1
name2,0,21,address1
name3,0,21,address1
name4,1,30,address4
name5,0,24,address5
name6,1,24,address5
name7,1,29,address7
vi 1.txt
name1,1,21,address1
name2,0,,
name3,0,,
name4,1,30,address4
name5,0,24,address5
name6,1,,
name7,1,29,address7
# 1
awk -F',' 'BEGIN{OFS=","}{if($1!=""&&$2!=""&&$3!=""&&$4!=""){a=$1;b=$2;c=$3;d=$4}else{if($1==""){$1=a;}if($2==""){$2=b;}if($3==""){$3=c;}if($4==""){$4=d}}print;}' 1.txt
# 2
awk -F"," '{if($3){b=null;for(i=3;i<=NF;i++){b=b","$i}print $0}else{$0=$0b;gsub(/,+/,",",$0);print $0}}' 1.txt
# 3
awk -F"," '{if(FNR==1){tmp3=$3;tmp4=$4;}if($3==null)$3=tmp3;if($4==null)$4=tmp4;tmp3=$3;tmp4=$4;a[FNR]=$1","$2","$3","$4; print a[FNR]}' 1.txt
# 4
awk -F"," 'BEGIN{OFS=","}{if($3){th=$3;fo=$4;print $0}else{print $1,$2,th,fo}}' 1.txt
# 5
awk -F, '{
for(i = 1; i <= NF; i++) {
a[i] = $i != "" ? $i : a[i];
}
printf("%s %s %s %s\n", a[1], a[2], a[3], a[4])}' 1.txt
# 6
awk -F, 'BEGIN{OFS=","}
{
for(i=1;i<5;++i)
if(length($i)==0)
$i = rec[i];
split($0,rec);
print
}' 1.txt
#!/bin/bash
awk '{print}' aa.txt | while read line
do
a1=`echo $line | awk -F , '{print $1}'`
a2=`echo $line | awk -F , '{print $2}'`
a3=`echo $line | awk -F , '{print $3}'`
a4=`echo $line | awk -F , '{print $4}'`
if [[ ! -z $a1 && ! -z $a2 && ! -z $a3 && ! -z $a1 ]];then
echo "$a1,$a2,$a3,$a4" >> bb.txt
b1=$a1
b2=$a2
b3=$a3
b4=$a4
else
if [ -z $a1 ];then
a1=$b1
fi
if [ -z $a2 ];then
a2=$b2
fi
if [ -z $a3 ];then
a3=$b3
fi
if [ -z $a4 ];then
a4=$b4
fi
echo "$a1,$a2,$a3,$a4" >> bb.txt
fi
done
curl¶
GET¶
curl www.baidu.com
POST请求¶
curl -d "param1=value1¶m2=value2" "http://www.baidu.com"
JSON格式的请求
curl -l -H "Content-type: application/json" -X POST -d '{"phone":"13521389587","password":"test"}' http://domain/apis/users.json
echo¶
-e¶
echo显示带颜色,需要使用参数-e man console_codes(不同数字对应的背景字体颜色)
echo -e "\033[字背景颜色;文字颜色m字符串\033[0m"
注:
1. 字背景颜色和文字颜色之间是英文的";"
2. 文字颜色后面有个m
3. 字符串前后可以没有空格,如果有的话,输出也是同样有空格
下面是相应的字和背景颜色,可以自己来尝试找出不同颜色搭配
echo -e "\033[30m 黑色字 \033[0m"
echo -e "\033[31m 红色字 \033[0m"
echo -e "\033[32m 绿色字 \033[0m"
echo -e "\033[33m 黄色字 \033[0m"
echo -e "\033[34m 蓝色字 \033[0m"
echo -e "\033[35m 紫色字 \033[0m"
echo -e "\033[36m 天蓝字 \033[0m"
echo -e "\033[37m 白色字 \033[0m"
echo -e "\033[41;37m 红底白字 \033[0m"
echo -e "\033[42;37m 绿底白字 \033[0m"
echo -e "\033[43;37m 黄底白字 \033[0m"
echo -e "\033[44;37m 蓝底白字 \033[0m"
echo -e "\033[45;37m 紫底白字 \033[0m"
echo -e "\033[46;37m 天蓝底白字 \033[0m"
echo -e "\033[47;30m 白底黑字 \033[0m"
\33[0m 关闭所有属性
\33[1m 设置高亮度
\33[4m 下划线
\33[5m 闪烁
\33[7m 反显
\33[8m 消隐
\33[30m — \33[37m 设置前景色
\33[40m — \33[47m 设置背景色
\33[nA 光标上移n行
\33[nB 光标下移n行
\33[nC 光标右移n行
\33[nD 光标左移n行
\33[y;xH设置光标位置
\33[2J 清屏
\33[K 清除从光标到行尾的内容
\33[s 保存光标位置
\33[u 恢复光标位置
\33[?25l 隐藏光标
\33[?25h 显示光标
grep¶
范例¶
[root@ centos ~]# ip a |grep -Po 'inet \K\S+'
127.0.0.1/8
10.0.0.52/24
172.16.1.52/24
[root@ centos ~]# ifconfig eth0|grep -Po '(?<=t addr:)\S+'
10.0.0.52
[root@ centos ~]# ifconfig eth0|grep -Po '[(\d).]+(?=\s+Bca)'
10.0.0.52
ifconfig eth0|grep -Po --color '\w:\K.*(?= B)'
[yjj@centos awk]$ echo axyzba123bcc456 | grep -oP 'x|(?<=a).*(?=b)'
x
123
零宽断言¶
(?=)
向右(?!)
向右不匹配(?<=)
向左(?<!)
向左不匹配\K
向右匹配 不用指定长度
用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像
\b ^ $ \< \>
这样的锚定作用,这个位置应该满足一定的条件(即断言)
(?=exp)
也叫零宽度正预测先行断言- 它断言自身出现的位置的后面能匹配表达式exp。
- 括号中可以不用固定长度
比如grep -Po ‘:raw-latex:`\b`:raw-latex:`\w`+(?=ing:raw-latex:`b`)’,匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找 I’m singing while you’re dancing.
(?<=exp)
也叫零宽度正回顾后发断言,- 它断言自身出现的位置的前面能匹配表达式exp。
- 比如(?<=:raw-latex:bre):raw-latex:w`+:raw-latex:b会匹配以re开头的单词的后半部分`(除了re以外的部分),
- 例如在查找reading a book时,它匹配ading。
- 括号中必须固定长度
[root@centos ~]# echo '127.host = 10.0.0.6'
host = 10.0.0.6
echo ": 456," 取出456
(?<=abc).* 可以匹配 abcdefgabc 中的什么
(?<=\s)\d+(?=\s)
23w32
131
213
echo "/etc/sysconfig/network-scripts/ifcfg-eth0"
断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。
echo "https://www.baidu.com/scxzcx"
echo "https://www.baidu.com /scxzcx"
匹配
/scxzcx
echo "https://www.baidu.com/jin/db/scxzcx"|grep -P --color '(?<=[^/]/)[^/]+'
https://www.baidu.com/jin/db/scxzcx
负向零宽断言也有“先行”和“后发”两种
负向零宽后发断言 (?<!表达式)
负向零宽先行断言 (?!表达式)
如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词–它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:
匹配q后面不是字母u的单词
[root@centos grep_test]# echo -e "query\nqazfg\ndfaq tee"
query
qazfg
dfaq tee
[root@centos grep_test]# echo -e "query\nqazfg\ndfaq tee"|grep -Po '\b\w+q[^u]\w*\b'
dfaq tee
[root@centos grep_test]# echo -e "query\nqazfg\ndfaq tee"|grep -Po '\b\w*q[^u]\w*\b'
qazfg
dfaq tee
负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。
零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。
例如
\d{3}(?!\d)
\b((?!abc)\w)+\b
grep -Po '\d{3}(?!\d)' <<< "1234\n12345\n123abc"
grep -Po '\d{3}(?=[a-z])' <<< "1234\n12345\n123abc"
(?<!exp)
,零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:- 匹配前面不是exp的位置
因此匹配前面不是反斜杠的正则表达式应该这样写
(?<!\\)\*
(?<![a-z])\d{7}
(?<=<(\w+)>).*(?=<\/\1>)
(?<=\bre)\w+\b 查找reading a book时
(?<=\s)\d+(?=\s)
\d{3}(?!\d) 匹配三位数字,而且这三位数字的后面不能是数字
\b((?!abc)\w)+\b 匹配不包含连续字符串abc的单词。
(?<=<(\w+)>).*(?=<\/\1>) 匹配不包含属性的简单HTML标签内里的内容。
[root@localhost ~]# echo '127.0.0.1 = localhost'|grep -Po '=\s\K.*'
localhost
[root@localhost ~]# grep -Po '(^[^ ]+).*\1' 123
qujian --- qujian
chiling --- achiling
lihao --- alihao
[root@localhost ~]# grep -Po '(^[^ ]+).*\1\w+' 123
qujian --- qujian123
chiling --- achiling999
lihao --- alihao123
[root@localhost ~]# ifconfig eth0|grep -Po --color '\w:\K.*(?= B)'
10.0.0.7
[root@localhost ~]# cat 1
127 127ip1
abc abc123
baidu google123
123 123sina
[root@localhost ~]# grep -Po '(\w+)\s.*\1\w+' 1
127 127ip1
abc abc123
123 123sina
[root@localhost ~]# grep -Po '(^[^ ]).*\1\w+' 1
127 127ip1
abc abc123
123 123sina
[root@localhost ~]# grep -Po '(^.*)\s.*\1\w+' 1
127 127ip1
abc abc123
123 123sina
He like his liker.
He like his lover.
She love her liker.
She love her lover.
1. 删除以上内容当中包含单词“l..e”前后一致的行;
2. 将文件中“l..e”前后一致的行中,最后一个l..e词首的l换成大写L;
[root@localhost ~]# grep -Po '<([Hh].>).*\1' 2
<H1>welcome to my homepage</H1>
<h2>welcome to my homepage</h2>
<h3>welcome to my homepage</h3>
[root@localhost ~]# cat 2
<div>hello world</div>
<H1>welcome to my homepage</H1>
<div>hello world</div>
<h2>welcome to my homepage</h2>
<div>hello world</div>
<h3>welcome to my homepage</h3>
<div>hello world</div>
iconv¶
参数¶
-f encoding: 把字符从encoding编码开始转换。
-t encoding: 把字符转换到encoding编码。
-l: 列出已知的编码字符集合
# 不使用 -o 参数, 则会输出到标准输出, 也可以使用重定向写入文件
-o file: 指定输出文件
-c: 忽略输出的非法字符
-s: 禁止警告信息,但不是错误信息
--verbose: 显示进度信息
-f和-t所能指定的合法字符在-l选项的命令里面都列出来了。
实例¶
批量文件编码转换
# 创建对应目录, 转码之后的文件放置在对应utf目录下
find . -type d | sed 's#^./##g' |awk 'NR!=1{print "utf/"$0}'|xargs mkdir -p
# 转码, 将所有 txt, 及 java 文件 gbk 编码转成 utf-8
find . -type f -name "*.txt" -o -name "*.java"|sed 's#^./##g'|awk '{print "iconv -f gbk -t utf-8 \""$1"\" > \"utf/"$1"\""}' |bash
job,bg,fg¶
&¶
命令后台执行
ctrl + z¶
将一个正在前台执行的命令放到后台,并且暂停
jobs¶
查看当前有多少在后台运行的命令
进程的终止¶
- 通过jobs命令查看job号(假设为num),然后执行kill %num
- 通过ps命令查看job的进程号(PID,假设为pid),然后执行kill pid
- 前台进程的终止:
ctrl+c
kill除了可以终止进程,还能给进程发送其它信号,使用kill -l 可以察看kill支持的信号。
SIGTERM是不带参数时kill发送的信号,意思是要进程终止运行,但执行与否还得看进程是否支持。如果进程还没有终止,可以使用
kill -SIGKILL pid
,这是由内核来终止进程,进程不能监听这个信号。
less¶
从文件底部往上看
对于一些很大的log文件,我们用more查看时会很费劲,没有办法直接跳到末尾再向前查看。 我们可以用less来解决,less查看一个文件时,可以使用类似vi的command命令,在command模式下按G跳到文件末尾,再使用f或B来翻页
less filename
:G 跳到底部,就可以用 向上 向下 箭头 或 向滚动鼠标来查看log了
上下翻页
Ctrl + u/d
nc¶
~ nc -v -w 2 61.135.169.121 80
found 0 associations
found 1 connections:
1: flags=82<CONNECTED,PREFERRED>
outif en0
src 192.168.55.15 port 57475
dst 61.135.169.121 port 80
rank info not available
TCP aux info available
Connection to 61.135.169.121 port 80 [tcp/http] succeeded!
nmap¶
用nmap对局域网扫描一遍,然后查看arp缓存表就可以知道局域内ip对应的mac了。nmap比较强大也可以直接扫描mac地址和端口。执行扫描之后就可以 cat /proc/net/arp查看arp缓存表了。
进行ping扫描,打印出对扫描做出响应的主机
nmap -sP 192.168.1.0/24
仅列出指定网络上的每台主机,不发送任何报文到目标主机
nmap -sL 192.168.1.0/24
探测目标主机开放的端口,可以指定一个以逗号分隔的端口列表(如-PS 22,23,25,80)
nmap -PS 192.168.1.234
使用UDP ping探测主机
nmap -PU 192.168.1.0/24
使用频率最高的扫描选项(SYN扫描,又称为半开放扫描),它不打开一个完全的TCP连接,执行得很快:
nmap -sS 192.168.1.0/24
route¶
route - show / manipulate the IP routing table This program is obsolete. For replacement check ip route.
常用参数¶
-n show numerical addresses instead of trying to determine symbolic host names. This is useful if you are
trying to determine why the route to your nameserver has vanished.
del delete a route.
add add a new route.
target the destination network or host. You can provide IP addresses in dotted decimal or host/network names.
-net the target is a network.
-host the target is a host.
netmask NM
when adding a network route, the netmask to be used.
gw GW route packets via a gateway. NOTE: The specified gateway must be reachable first. This usually means
that you have to set up a static route to the gateway beforehand. If you specify the address of one of
your local interfaces, it will be used to decide about the interface to which the packets should be
routed to. This is a BSDism compatibility hack.
dev If force the route to be associated with the specified device, as the kernel will otherwise try to determine
the device on its own (by checking already existing routes and device specifications, and where the route
is added to). In most normal networks you won’t need this.
If dev If is the last option on the command line, the word dev may be omitted, as it’s the default. Oth-
erwise the order of the route modifiers (metric - netmask - gw - dev) doesn’t matter.
EXAMPLES¶
route add -net 127.0.0.0
adds the normal loopback entry, using netmask 255.0.0.0 (class A net, determined from the destination
address) and associated with the "lo" device (assuming this device was prviously set up correctly with
ifconfig(8)).
route add -net 192.56.76.0 netmask 255.255.255.0 dev eth0
adds a route to the network 192.56.76.x via "eth0". The Class C netmask modifier is not really necessary
here because 192.* is a Class C IP address. The word "dev" can be omitted here.
route add default gw mango-gw
adds a default route (which will be used if no other route matches). All packets using this route will
be gatewayed through "mango-gw". The device which will actually be used for that route depends on how we
can reach "mango-gw" - the static route to "mango-gw" will have to be set up before.
route add ipx4 sl0
Adds the route to the "ipx4" host via the SLIP interface (assuming that "ipx4" is the SLIP host).
route add -net 192.57.66.0 netmask 255.255.255.0 gw ipx4
This command adds the net "192.57.66.x" to be gatewayed through the former route to the SLIP interface.
route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0
This is an obscure one documented so people know how to do it. This sets all of the class D (multicast)
IP routes to go via "eth0". This is the correct normal configuration line with a multicasting kernel.
route add -net 10.0.0.0 netmask 255.0.0.0 reject
This installs a rejecting route for the private network "10.x.x.x."
OUTPUT¶
[root@centos ~]# route -n ## netstat -rn也可以查看路由
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
172.16.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1003 0 0 eth1
0.0.0.0 10.0.0.2 0.0.0.0 UG 0 0 0 eth0
#最后一行表示系统的默认网关信息,去任何地方(0.0.0.0),都发给10.0.0.2,因为是默认网关,所以,放在了最后一条。
#路由也是有顺序的,如果不符合任何一条规则就交给默认网关处理。
OUTPUT
The output of the kernel routing table is organized in the following columns
Destination
The destination network or destination host.
Gateway
The gateway address or ’*’ if none set.
Genmask
The netmask for the destination net; ’255.255.255.255’ for a host destination and ’0.0.0.0’ for the
default route.
Flags Possible flags include
U (route is up)
H (target is a host)
G (use gateway)
R (reinstate route for dynamic routing)
D (dynamically installed by daemon or redirect)
M (modified from routing daemon or redirect)
A (installed by addrconf)
C (cache entry)
! (reject route)
Metric The ’distance’ to the target (usually counted in hops). It is not used by recent kernels, but may be
needed by routing daemons.
Ref Number of references to this route. (Not used in the Linux kernel.)
Use Count of lookups for the route. Depending on the use of -F and -C this will be either route cache misses
(-F) or hits (-C).
Iface Interface to which packets for this route will be sent.
MSS Default maximum segement size for TCP connections over this route.
Window Default window size for TCP connections over this route.
irtt Initial RTT (Round Trip Time). The kernel uses this to guess about the best TCP protocol parameters with-
out waiting on (possibly slow) answers.
HH (cached only)
The number of ARP entries and cached routes that refer to the hardware header cache for the cached route.
This will be -1 if a hardware address is not needed for the interface of the cached route (e.g. lo).
Arp (cached only)
Whether or not the hardware address for the cached route is up to date.
FILES¶
/proc/net/ipv6_route
/proc/net/route
/proc/net/rt_cache
默认路由、网络路由及主机路由¶
默认路由
默认网关就是数据包不匹配任何设定的路由规则,最后匹配的路由规则。
网络路由
即去往某一网络或网段的路由
一般多网段之间互相通信,希望建立一条优先路由,而不是通过默认网关时就可以配置网络路由。
实际工作中会有需求,两个不同的内部网络互访
[root@tomcat ~]# route add -net 192.168.1.0 netmask 255.255.255.0 gw 192.168.1.1
SIOCADDRT: Network is unreachable #当连不通地址192.168.1.1时,无法添加路由
[root@tomcat ~]# ifconfig eth0:0 192.168.1.1/24 up #添加别名IP临时测试,如果需要永久生效最好加双网卡或写入到配置文件
[root@tomcat ~]# ifconfig eth0:0 #查看添加的IP别名(网络里把这种多IP的方式称为子接口)
eth0:0 Link encap:Ethernet HWaddr 00:0C:29:9F:D7:6D
inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
添加去192.168.1.0的数据包,交给192.168.1.1处理
route add -net 192.168.1.0 netmask 255.255.255.0 gw 192.168.1.1
route add -net 192.168.1.0 netmask 255.255.255.0 dev eth0 ## 指定设备名,这种写法也可以
route add -net 192.168.1.0/24 dev eth0
route del -net 192.168.1.0/24 dev eth0
主机路由
去往某个主机地址的路由
route add -host 192.168.2.13 dev eth2
route add -host 202.81.11.91 dev lo
例如:keepalived或heartbeat高可用服务器对之间的使用单独网卡接心跳线通信就会用到以上主机路由。
实例¶
route命令
添加路由
route add -net 192.168.0.0/24 gw 192.168.0.1
route add -host 192.168.1.1 dev 192.168.0.1
删除路由
route del -net 192.168.0.0/24 gw 192.168.0.1
add 增加路由
del 删除路由
-net 设置到某个网段的路由
-host 设置到某台主机的路由
gw 出口网关 IP地址
dev 出口网关 物理设备名
增加默认路由
route add default gw 192.168.0.1
route add default gw 192.168.0.1 就相当于route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.0.1
删除默认路由
route del default gw 192.168.0.1
删除一条静态路由:
route del -net 192.168.1.0/24
或route del -net 192.168.1.0 netmask 255.225.255.0
删除一条主机路由:
route del -host 192.168.1.10 dev eth0
查看路由表
route -n
netstat -rn
ip命令
添加路由
ip route add 192.168.0.0/24 via 192.168.0.1
ip route add 192.168.1.1 dev 192.168.0.1
删除路由
ip route del 192.168.0.0/24 via 192.168.0.1
via 网关出口 IP地址
dev 网关出口 物理设备名
增加默认路由
ip route add default via 192.168.0.1 dev eth0
via 192.168.0.1 是我的默认路由器
查看路由信息
ip route
保存路由设置,使其在网络重启后任然有效¶
一:
在/etc/sysconfig/network-script/目录下创建名为route-eth0的文件
vi /etc/sysconfig/network-script/route-eth0
在此文件添加如下格式的内容
192.168.1.0/24 via 192.168.0.1
二:
/etc/rc.d/init.d/network中有这么几行:
# Add non interface-specific static-routes.
if [ -f /etc/sysconfig/static-routes ]; then
grep "^any" /etc/sysconfig/static-routes | while read ignore args ; do
/sbin/route add -$args
done
fi
也就是说,将静态路由加到/etc/sysconfig/static-routes 文件中就行了。
如加入:
route add -net 192.168.0.1 netmask 255.255.255.0 gw 192.168.1.1
则static-routes的格式为
any net 192.168.0.1 netmask 255.255.255.0 gw 192.168.1.1
三:
vi /etc/rc.local
加入如下内容:
route add -net 192.168.1.0/24 gw 192.168.1.1
PS: 方法一推荐生产环境使用
提示:此方法写到/etc/rc.local里只在开机时加载,当手工重启网络后会失效,但是重启系统后会生效!
默认路由可在网卡配置里面指定:
/etc/sysconfig/network-scripts/ifcfg-eth0
GATEWAY=192.168.0.1
sed¶
删除 md 中的 TOC
grep -rn '<!-- TOC -->' .|awk -F ":" '{print "sed -i \047/<!-- TOC -->/,/<!-- \\/TOC -->/\047d \""$1"\""}'|bash
ssh¶
SSH会话连接超时问题解决办法¶
如果显示空白,表示没有设置, 等于使用默认值0, 一般情况下应该是不超时. 如果大于0, 可以在如/etc/profile之类文件中设置它为0.
使用命令直接用户修改配置文件,设置TMOUT=180
,即超时时间为3分钟
vim /etc/profile 添加下面两行
设置为3分钟
TMOUT=180
查看使用的ssh客户端是否有类似功能
上述方法应该能解决大部分问题, 如果不行, 请 man sshd_config, 然后尝试更改其他设置吧.
1、设置服务器向SSH客户端连接会话发送频率和时间
vi /etc/ssh/sshd_config,添加如下两行
ClientAliveInterval 60
ClientAliveCountMax 86400
注:
ClientAliveInterval选项定义了每隔多少秒给SSH客户端发送一次信号;
ClientAliveCountMax选项定义了超过多少秒后断开与ssh客户端连接
2、重新启动系统SSH服务
# service sshd restart
# Ubuntu 16.04.1 LTS:
systemctl reload ssh.service
scp¶
scp -P 50333 root@192.168.0.11:~/docker.docx .
使用反斜线转义,并使用双引号引起来
示例
scp -r root@192.168.0.100:"/share/svn/coolest\ guy" /share/svn/
ssh-copy-id¶
ssh-copy-id -i ~/.ssh/id_rsa.pub -p 50333 root@192.168.0.11
问题¶
ssh -c aes256-cbc root@xx.xx.xx
man ssh
-c cipher_spec
Selects the cipher specification for encrypting the session. cipher_spec is a comma-separated list of ciphers listed in order of preference. See
the Ciphers keyword in ssh_config(5) for more information.
man ssh_config
搜索
Ciphers
The supported ciphers are:
3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com
The default is:
chacha20-poly1305@openssh.com,
aes128-ctr,aes192-ctr,aes256-ctr,
aes128-gcm@openssh.com,aes256-gcm@openssh.com,
aes128-cbc,aes192-cbc,aes256-cbc
The list of available ciphers may also be obtained using "ssh -Q cipher".
command¶
seq¶
[root@centos get_artTitlelist]# seq -s "+" 10
1+2+3+4+5+6+7+8+9+10
[root@centos get_artTitlelist]# seq -f "stu%02g" 10
stu01
stu02
stu03
stu04
stu05
stu06
stu07
stu08
stu09
stu10
[root@centos get_artTitlelist]# seq --help
Usage: seq [OPTION]... LAST
or: seq [OPTION]... FIRST LAST
or: seq [OPTION]... FIRST INCREMENT LAST
Print numbers from FIRST to LAST, in steps of INCREMENT.
-f, --format=FORMAT use printf style floating-point FORMAT
-s, --separator=STRING use STRING to separate numbers (default: \n)
-w, --equal-width equalize width by padding with leading zeroes
--help display this help and exit
--version output version information and exit
...
yum¶
yum install yum-utils -y
yumdownloader
yum clean all //清除原有的yum信息
yum list //检查DVD软件列表
rpm -ivh --nodeps http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-3.2.29-73.el6.centos.noarch.rpm
rpm -ivh --nodeps http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-metadata-parser-1.1.2-16.el6.x86_64.rpm
yum repolist ##查看epel源是否安装成功
yum --enablerepo=epel info inotify-tools 查看某个包的详细信息
yum --disablerepo="*" --enablerepo="epel" list available | less ##列出epel源的所有包列表
yum remove epel...
yum -y update
# 升级所有包,改变软件设置和系统设置,系统版本内核都升级
yum -y upgrade
# 升级所有包,不改变软件设置和系统设置,系统版本升级,内核不改变
1、备份(如有配置其他epel源)
mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.backup
mv /etc/yum.repos.d/epel-testing.repo /etc/yum.repos.d/epel-testing.repo.backup
2、下载新repo 到/etc/yum.repos.d/
epel(RHEL 7)
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
epel(RHEL 6)
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo
epel(RHEL 5)
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-5.repo
## 方法二 ##
yum install -y epel-release
keepcache=1 # 修改成1即可
[root@centos scripts]# cat /etc/yum.conf
[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=1 # 修改成1即可
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=5
bugtracker_url=http://bugs.centos.org/set_project.php?project_id=19&ref=http://bugs.centos.org/bug_report_page.php?category=yum
distroverpkg=centos-release
# This is the default, if you make this bigger yum won't see if the metadata
# is newer on the remote and so you'll "gain" the bandwidth of not having to
# download the new metadata and "pay" for it by yum not having correct
# information.
# It is esp. important, to have correct metadata, for distributions like
# Fedora which don't keep old packages around. If you don't like this checking
# interupting your command line usage, it's much better to have something
# manually check the metadata once an hour (yum-updatesd will do this).
# metadata_expire=90m
# PUT YOUR REPOS HERE OR IN separate files named file.repo
# in /etc/yum.repos.d
压缩与解压¶
tar 加密压缩解压¶
加密压缩
tar zcvf - file | openssl des3 -salt -k secretpassword -out /path/to/file.tar.gz
# 使用dd
tar zcvf - file | openssl des3 -salt -k secretpassword | dd of=/path/to/file.tar.gz
解密解压
openssl des3 -d -k secretpassword -salt -in /path/to/file.tar.gz | tar xf -
# 使用dd
dd if=/path/to/file.tar.gz |openssl des3 -d -k secretpassword|tar xf -
不解压查看tar包内容¶
file.tar.gz
gzip -dc file.tar.gz | tar tvf -
file.tar.bz2
bzip2 -dc file.tar.bz2 |tar tvf -
file.tar
tar tvf file.tar
Shell¶
在Ubuntu里面的坑¶
root@iZm5ebwtdyoe99lpvnt1o6Z:~# ll /bin/sh
lrwxrwxrwx 1 root root 4 Dec 14 18:54 /bin/sh -> dash*
默认为dash,而不是bash
解决办法:
- 使用bash执行
- 脚本里面添加#!/bin/bash,然后添加执行权限,使用.执行
- 修改软连接,指向bash
测试shell¶
# sh -n database.sh #检测脚本是否正确,并不执行
# sh -x database.sh #执行脚本,输出执行过程
bash技巧¶
bash的一些技巧
变量子串¶
替换运算符¶
$(varname:-word) 如果varname存在且非null,则返回其值;否则,返回word。
用途:如果变量未定义,则返回默认值。
范例:如果count未定义,则$(count:-0)的值为0
获取参数=后面的内容¶
[root@web01 ~]# echo "--basedir=/applica" | sed -e 's/^[^=]*=//'
/applica
创建只含小写字母的随机数¶
openssl rand -base64 48|sed 's#[^a-z]##g'
调试¶
set -x 调试当前窗口命令
set +x 取消调试
set -e 脚本中有一条命令返回值不为零就退出脚本,可使用test,当前面脚语句执行失败的时候,执行后面的语句
删除无效软链接¶
方法1.
for f in $(find $1 -type l); do [ -e $f ] && rm -f $f; done
方法2.
symlinks -d
symlinks: scan/change symbolic links - v1.2 - by Mark Lord
Usage: symlinks [-crsv] dirlist
Flags: -c == change absolute/messy links to relative
-d == delete dangling links
-r == recurse into subdirs
-s == shorten lengthy links (only displayed if -c not specified)
-v == verbose (show all symlinks)
统计每个文件夹占用的inode总数¶
find */ -exec stat -c "%n %i" {} \; | awk -F "[/ ]" '{if(! a[$1-$NF]++) l[$1]++}END{for (i in l) print i,l[i]}'
拼接时间¶
for n in 20 21
do
for m in {00..59}
do
echo "$n:$m"
done
done
xargs 处理文件名带空格的方法¶
# 使用 -i 参数 '{}'
xargs -i sed -i '/^<extoc>/d;2a<extoc></extoc>\n' '{}'
# 示例
find . -type f -name "*.md" ! -path "./README.md" -a ! -path "./SUMMARY.md"|xargs -i sed -i '/^<extoc>/d;2a<extoc></extoc>\n' '{}'
指定行插入文本内容¶
# 知道行号用以下方法插入
sed -i '88 r b.file' a.file #在a.file的第88行插入文件b.txt
awk '1;NR==88{system("cat b.file")}' a.file >c.file
## 如果不知道行号,用正则匹配
sed -i '/regex/ r b.txt' a.txt
awk '/target/{system("cat b.txt")}' a.txt > c.txt
大小写转换¶
awk '{print toupper($0)}' <<< 'this is a dog!'
# toupper()
# tolower()
# 全文大小写转换
tr a-z A-Z
tr A-Z a-z
# 大小写互换
echo "aBcDE" | tr '[a-zA-Z]' '[A-Za-z]'
示例
比如说:a.txt b.txt c.txt
更名变成 A.txt B.txt C.txt
ls *.txt|sed -nr 's/(.)(\..*)/mv & \u\1\2/e'
[解析]
\u 是转换后面的内容第一个字母为大写,\U是全部为大写直到遇到 \E 为止。这就是区别:
echo 'abc'|sed 's/^../\u&/'
Abc
echo 'abc'|sed 's/^../\U&\E/'
ABc
把每个单词的第一个字母替换成大写¶
this is a dog!
➜ ~ sed 's/\b[a-z]/\u&/g' <<< 'this is a dog!'
This Is A Dog!
# \b大家应该知道是锚定的意思,说白了就是边界符,那么这就只会匹配第一个开头的字母,然后\U的意思在元字符里的解释是“大写(不是标题首字符)\E 以前的字符”,而\u只是将下一个字符变为大写,注意它们的区别噢。
把URL中的大写字符替换成小写¶
http://www.a.com/aaafkslafjlxcv/fsfa/8/Xxxx.XxXX
sed 's/[A-Z]/\l&/g' <<< 'http://www.a.com/aaafkslafjlxcv/fsfa/8/Xxxx.XxXX'
# 同理\L的意思是使之变为小写。
$RANDOM 的范围是 [0, 32767]¶
第二章¶
判断技巧¶
[ -d /abc ] || mkdir -p /abc
创建随机密码方法¶
echo $RANDOM|md5sum
openssl rand -base64 48
expect mkpasswd
mkpasswd -l 10
date +%N|sha512sum
head /dev/urandom|md5sum
uuidgen|md5sum
- 加密可以使用
md5sum
、sha512sum
等等,加密之前可以添加一个干扰码,例如:echo yjj$RANDOM|md5sum
- 加密可以使用
创建10个随机字母¶
echo $RANDOM|sha512sum|sed 's#[0-9]##g'|cut -c1-10
变量子串使用技巧¶
[root@db ~]# cat pass.sh
#!/bin/bash
# Password will consist of alphanumeric characters.
MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,?@#$%^&*()"
# length of password.
LENGTH="20"
while [ "${n:=1}" -le "$LENGTH" ]
do
PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}"
let n+=1
done
echo "$PASS" # ==> Or, redirect to a file, as desired.
exit 0
read -p "Please enter the amount of recharge: " re
if [ -n "${re//[0-9]/}" ];then
echo "input error"
continue
fi
# 删除数字进行判断,判断是否为空
# ---
# 方法1:将数字以外的字符替换成空,如果跟本身相同,说明用户输入为数字
[root@yjj ~]# [ "`echo 1231|sed -r 's#[^0-9]##g'`" = "1231" ]&&echo 0||echo 1
0
[root@yjj ~]# [ "`echo 123a|sed -r 's#[^0-9]##g'`" = "123a" ]&&echo 0||echo 1
1
# 方法2:使用expr,如果命令返回结果非零,则表示用户输入的不是数字
[root@yjj ~]# expr 1 + a &>/dev/null
[root@yjj ~]# echo $?
2
计算字符串长度¶
string="I love you"
方法一:
echo ${#string}
方法二:
echo ${string}|wc -L
方法三:
expr length "${string}"
方法四:
awk '{print length($0)}' <<<$string
awk '{print length}' <<<$string ## $0可以省略
方法五:
functions内置函数,strstr
# returns OK if $1 contains $2
strstr() {
[ "${1#*$2*}" = "$1" ] && return 1
return 0
}
命令拼接¶
[root@web 11]# chkconfig |awk '$1!~/crond|sshd|sysstat|network|rsyslog/{print "chkconfig",$1,"off"}'|bash
计算1+2+3+4+5+6+7+8+9+10¶
[root@web scripts]# seq 10|awk '{a+=$1;b=b$1"+"}END{sub("+$","",b);print b"="a}'
1+2+3+4+5+6+7+8+9+10=55
打印下面语句中字符数小于6的单词¶
[root@db ~]# echo Whatever is worth doing is worth doing well.
Whatever is worth doing is worth doing well.
[root@db ~]# echo Whatever is worth doing is worth doing well.|awk 'END{for(i=1;i<=NF;i++)if(length($i)<6) print $i }'
is
worth
doing
is
worth
doing
well.
[root@db ~]# awk 'END{for(i=1;i<=NF;i++)if(length($i)<6) print $i }' <<< "Whatever is worth doing is worth doing well."
[root@db ~]# cat test.sh
for n in Whatever is worth doing is worth doing well.
do
if [ ${#n} -lt 6 ];then
echo $n
fi
done
[root@db ~]# cat test1.sh
for n in Whatever is worth doing is worth doing well.
do
if [ "$n" = "${n:0:6}" ];then
echo $n
fi
done
使用eval实现动态变量¶
用变量值作新的变量名
➜ ~ refer_504_09="123"
➜ ~ time=`date +%H`
➜ ~ echo $time
09
➜ ~ echo $refer_504_09
123
➜ ~ a=`eval echo '$refer_504_'"$time"`
➜ ~ echo $a
123
乘法口诀表¶
awk 'BEGIN{for(i=1;i<=9;i++){for(j=1;j<=i;j++){printf j"*"i"="i*j"\t"}printf "\n"}}'
echo -ne "\033[47;30m`awk 'BEGIN{for(i=1;i<=9;i++){for(j=1;j<=i;j++){printf j"*"i"="i*j"\t"}printf "\n"}}'`\033[0m\n"
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("\033[47;30m%dx%d=%d%s", i, NR, i*NR, i==NR?"\033[0m\n":"\t")}'
echo -e "\e[44;37;5m `awk 'BEGIN{for(i=1;i<10;i++) {for(k=1;k<=i;k++) {printf "%d%s%d%s%;}printf "\n"}}'` \e[0m "
for i in {1..9}; do for j in `seq 1 $i`; do echo -ne "\033[47;30m${j}*${i}=$((j*i))\033[0m \t"; done; echo; done
for((i=1;i<10;i++));do for((j=1;j<=i;j++))do printf "$j*$i=$((i*j))\t" ;done;printf "\n";done
圆周率计算¶
➜ ~ echo "scale=1000; a(1)*4" | bc -l
3.141592653589793238462643383279502884197169399375105820974944592307\
81640628620899862803482534211706798214808651328230664709384460955058\
22317253594081284811174502841027019385211055596446229489549303819644\
28810975665933446128475648233786783165271201909145648566923460348610\
45432664821339360726024914127372458700660631558817488152092096282925\
40917153643678925903600113305305488204665213841469519415116094330572\
70365759591953092186117381932611793105118548074462379962749567351885\
75272489122793818301194912983367336244065664308602139494639522473719\
07021798609437027705392171762931767523846748184676694051320005681271\
45263560827785771342757789609173637178721468440901224953430146549585\
37105079227968925892354201995611212902196086403441815981362977477130\
99605187072113499999983729780499510597317328160963185950244594553469\
08302642522308253344685035261931188171010003137838752886587533208381\
42061717766914730359825349042875546873115956286388235378759375195778\
18577805321712268066130019278766111959092164201988
批量修改文件名¶
awk、sed、rename批量修改文件名
源文件名:
stu_102999_1_hello.jpg
stu_102999_2_hello.jpg
stu_102999_3_hello.jpg
stu_102999_4_hello.jpg
stu_102999_5_hello.jpg
修改后:
stu_102999_1_.jpg
stu_102999_2_.jpg
stu_102999_3_.jpg
stu_102999_4_.jpg
stu_102999_5_.jpg
使用awk,gsub
ls|awk '{print "mv "$0,$0}'|awk '{gsub("_hello","",$3)}1'
ls|awk 'BEGIN{ORS=""}{print "mv",$0" ";gsub("_hello","",$0);print $0"\n"}'
ls|awk '{$3=$2=$1;$1="mv";sub("_hello","",$3)}1'
直接使用print拼接
使用sed命令拼接
使用rename批量修改
其他¶
- 环境变量的定义放到 /etc/bashrc #最后执行,不会被覆盖
文件字符集转换方法¶
iconv -f gb2312 -t UTF-8 2.html -o 2.utf8.html
[root@centos scripts]# iconv --help
Usage: iconv [OPTION...] [FILE...]
Convert encoding of given files from one encoding to another.
Input/Output format specification:
-f, --from-code=NAME encoding of original text
-t, --to-code=NAME encoding for output
Information:
-l, --list list all known coded character sets
Output control:
-c omit invalid characters from output
-o, --output=FILE output file
-s, --silent suppress warnings
--verbose print progress information
-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version
Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.
test用法¶
基本格式:¶
test expression
expression为test命令构造的表达式。
这里expression是test命令可以理解的任何有效表达式,该简化格式将是读者可能会踫见的最常用格式
返回值:
test命令或者返回0(真) 或者返回1(假).
test可理解的表达式类型分为四类:
- 表达式判断
- 字符串比较
- 数字比较
- 文件比较
1)判断表达式¶
if test (表达式为真)
if test !表达式为假
test 表达式1 –a 表达式 2 两个表达式都为真
test 表达式1 –o 表达式2 两个表达式有一个为真
2)判断字符串¶
test –n 字符串 字符串的长度非零
test –z 字符串 字符串的长度为零
test 字符串 1 =字符串 2 字符串相等
test 字符串 1 !=字符串 2 字符串不等
3)判断整数,符号只能用于整数,如果用字符串则会报错(此时可以使用字符串判断上述)¶
test 整数 1 –eq 整数2 整数相等
test 整数 1 –ge 整数2 整数1大于等于整数2
test 整数 1 –gt 整数 2 整数1大于整数2
test 整数 1 –le 整数 2 整数1小于等于整数2
test 整数 1 –lt 整数 2 整数1小于整数2
test 整数 1 –ne 整数 2 整数1不等于整数2
if [ "$ANS" = "y" ] || [ "$ANS" = "Y" ];then
echo " "
else #do nothing
fi
4)判断文件¶
test File1 –ef File2 两个文件具有同样的设备号和i结点号
test File1 –nt File2 文件1比文件2 新
test File1 –ot File2 文件1比文件2 旧
test –b File 文件存在并且是块设备文件
test –c File 文件存在并且是字符设备文件
test –d File 文件存在并且是目录
test –e File 文件存在
test –f File 文件存在并且是正规文件
test –g File 文件存在并且是设置了组ID
test –G File 文件存在并且属于有效组ID
test –h File 文件存在并且是一个符号链接(同-L)
test –k File 文件存在并且设置了sticky位
test –b File 文件存在并且是块设备文件
test –L File 文件存在并且是一个符号链接(同-h)
test –o File 文件存在并且属于有效用户ID
test –p File 文件存在并且是一个命名管道
test –r File 文件存在并且可读
test –s File 文件存在并且是一个套接字
test –t FD 文件描述符是在一个终端打开的
test –u File 文件存在并且设置了它的set-user-id位
test –w File 文件存在并且可写
test –x File 文件存在并且可执行
test xxx 可以简写成 [ xxx ] 的形式。
注意:在使用“[”简写test时,左中括号后面的空格和右括号前面的空格是必需的,如果没有空格,Shell不可能辨别表达式何时开始何时结束.
也就是说
test option file
可以全部改写成:
[ option file ]
例如:
test –w File
改写成
[ –w File ]
【示例】
//判断第一个参数是否为空字符串,不空则打印
if test -n "$1"
then
echo "$1"
fi
测试,放到文件当中
#!/bin/sh
if test -n "$1"
then
echo "$1"
fi
执行
chmod +x test.sh
./test.sh abc
shell实例¶
在/home目录中创建一百个目录,目录名称依次为a1……a100¶
for i in `seq 100`; do mkdir /home/a$i; done
编写一个脚本,自动将用户主目录下所有小于5KB的文件打包成XX.tar.gz.(提示:用ls,grep,find等命令,文件一般指普通文件)¶
find ~ -size -5 -type f -maxdepth 1|xargs tar zcvpf backup.tar.gz
写一个程序,可以将/et/passwd的第一列取出,而且每一列都以一行字符串“the 1 account is “root””来显示¶
awk -F':' '{print "the 1 account is "$1}' /etc/passwd
编写一个程序,他的作用是先查看一下/root/test/logical这个名称是否存在,若不存在,则创建一个文件。使用touch来创建,创建完成后离开;如果存在的话,判断该名称是否为文件,若为文件则将之删除后新建一个目录。文件名为loglical,之后离开;如果存在的话,而且该名称为目录,则删除此目录。¶
if [ ! -e "/root/test/logical" ]; then touch "hh"; elif [ -f "/root/test/logical" ];then rm /root/test/logical && mkdir logical&&exit;elif [ -d "/root/test/logical" ];then rm /root/test/logical; fi
编写一个shell脚本,从键盘读入10个数,显示最大值和最小值。¶
#! /bin/bash
printf "Enter 10 number: "
read
biggest=$(echo "$REPLY" | tr ' ' '\n' | sort -rn | head -n1)
smallest=$(echo "$REPLY" | tr ' ' '\n' | sort -rn | tail -n1)
echo "Biggest number: $biggest"
echo "Smallest number: $smallest"
--------------------------------------------------
运行结果
=> sh hh.sh
Enter 10 number: 1 2 3 4 5 6 7 8 9 0
Biggest number: 9
Smallest number: 0
编写一个脚本,打印任何数的乘法表。如输入3则打印¶
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
awk -vstr='3' 'BEGIN{for(i=1;i<=str;i++){for(p=1;p<=i;p++)printf p"*"i"="p*i"\t";printf "\n"}}'
编写一个脚本,输入自己的生日时间(YYYYMMDD),计算还有多少天多少个小时是自己的生日。¶
=> sh hh.sh
Input your birthday(YYYYmmdd):19930302
There is : 325 days 8 hours.
=> cat hh.sh
read -p "Input your birthday(YYYYmmdd):" date1
m=`date --date="$date1" +%m`
d=`date --date="$date1" +%d`
date_now=`date +%s`
y=`date +%Y`
birth=`date --date="$y$m$d" +%s`
internal=$(($birth-$date_now))
if [ "$internal" -lt "0" ]; then
birth=`date --date="$(($y+1))$m$d" +%s`
internal=$(($birth-$date_now))
fi
awk -vinternal=$internal 'BEGIN{d=int(internal/60/60/24);h=int((internal-24*60*60*d)/3600);print "There is : "d" days "h" hours."}'
三剑客¶
abcd(“abcd123”), shanghai,12345; abcd(“eee123”);111111;22222; 我想取出:abcd123 eee123
grep -oP '(?<=abcd\(")[^"]+'
grep -Po '(?<=")\w+(?=")'
[root@centos ~]# echo 'abcd("abcd123"), shanghai,12345; abcd("eee123");111111;22222;'|grep -oP '(?<=abcd\(")[^"]+'
abcd123
eee123
[root@centos ~]# echo 'abcd("abcd123"), shanghai,12345; abcd("eee123");111111;22222;'|grep -Po '(?<=")\w+(?=")'
abcd123
eee123
[root@centos ~]# echo 'abcd("abcd123"), shanghai,12345; abcd("eee123");111111;22222;'|awk '{for(i=1;i<=NF;i++){split($i,xxoo,"\"");print xxoo[2]}}'
abcd123
eee123
[root@centos ~]# echo 'abcd("abcd123"), shanghai,12345; abcd("eee123");111111;22222;'|grep -Po '"\K\w+?(?=")'
abcd123
eee123
题2¶
文件内容(序列码 开始时间 结束时间)如下:
11111 1 9
11111 10 19
22222 25 35
22222 30 40
22222 50 60
33333 30 40
33333 50 60
11111 20 30
11111 29 35
33333 70 80
44444 1 5
55555 3 4
要求:如果第一列重复并且时间有交叉就输出第一次出现的行,否则不输出。输出为:
22222 25 35
11111 1 9
awk '!a[$1]++{f[$1]=1;s[$1]=$0;b[$1][$2]=$3;next}f[$1]==0{next}{for(i in b[$1]){if($3<i||$2>b[$1][i]){b[$1][$2]=$3}else{f[$1]=0;print s[$1]}}}' 1
变量¶
Linux中变量 $#,$@,$0,$1,$2,$*,$$,$?
的含义¶
$# 是传给脚本的参数个数
$0 是脚本本身的名字
$1 是传递给该shell脚本的第一个参数
$2 是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
$* 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个
$$ 是脚本运行的当前进程ID号
$? 是显示最后命令的退出状态,0表示没有错误,其他表示有错误
区别:$@
$*
相同点:都是引用所有参数
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数(分别存储在$1 $2 $3)则"$*" 等价于 “$1 $2 $3"(传递了一个参数);而“$@" 等价于 "$1" "$2" "$3"(传递了三个参数)
后台运行shell¶
使用nohup在后台执行命令¶
使用&符号在后台执行命令或脚本后,如果你退出登录,这个命令就会被自动终止掉。要避免这种情况,你可以使用nohup命令,如下所示:
nohup ./my-shell-script.sh &
使用screen执行命令¶
通过nohup和&符号在后台执行命令后,即使你退出登录,这个命令也会一直执行。但是,你无法重新连接到这个会话,要想重新连接到这个会话,你可以使用screen命令。.
Linux的screen命令提供了分离和重新连接一个会话的功能。当你重新连接这个会话的时候,你的终端和你分离的时候一模一样。
使用定时任务¶
使用at命令,你可以让一个命令在指定的日期和时间运行,例如要在明天上午10点在后台执行备份脚本,执行下面的命令:
at -f backup.sh 10 am tomorrow
在批处理模式下执行某些任务需要启用一些选项。下面的文章会给出详细解释:.
How To Capture Unix Top Command Output to a File in Readable Format
Unix bc Command Line Calculator in Batch Mode
How To Execute SSH and SCP in Batch Mode (Only when Passwordless login is enabled)
解决perl: warning: Setting locale failed. perl: warning: Please check that your locale settings¶
解决办法 在~/.bashrc中添加一句话
export LC_ALL=C
echo "export LC_ALL=C" >> ~/.bashrc
unary operator expected¶
if [ $status == "OK" ]; then
...
fi
只有当变量 status
不为空的时候, 程序才正常, 所以这种错误很隐蔽
可以这样避免
if [ "$status"y == "OK"y ]; then
...
fi
cat <<EOF 与 cat <<-EOF 的区别¶
If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter.
在我们使用cat <<EOF时,我们输入完成后,需要在一个新的一行输入EOF结束stdin的输入。EOF必须顶行写,前面不能用制表符或者空格。
比如,下面的语句就不会出错:
cat <<EOF
Hello,world!
EOF
如果结束分解符EOF前有制表符或者空格,则EOF不会被当做结束分界符,只会继续被当做stdin来输入。而
<<-
就是为了解决这一问题:
cat <<-EOF
Hello,world!
EOF
上面的写法,虽然最后的EOF前面有多个制表符和空格,但仍然会被当做结束分界符,表示stdin的结束。
这就是 <<
和 <<-
的区别。
文件太多, 用 rm 删除报错¶
可以使用 ls |xargs rm -f
删除
查看,修改时区¶
查看当前时区¶
date -R
修改设置时区¶
tzselect
timeconfig
(仅限于RedHat Linux 和 CentOS)dpkg-reconfigure tzdata
(适用于Debian)- 复制相应的时区文件,替换系统时区文件;或者创建链接文件
cp /usr/share/zoneinfo/$主时区/$次时区 /etc/localtime
- 在中国可以使用:
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
Linux使用tzselect设置时区¶
[root@backup ~]# tzselect
Please identify a location so that time zone rules can be set correctly.
Please select a continent or ocean.
1) Africa
2) Americas
3) Antarctica
4) Arctic Ocean
5) Asia
6) Atlantic Ocean
7) Australia
8) Europe
9) Indian Ocean
10) Pacific Ocean
11) none - I want to specify the time zone using the Posix TZ format.
#? 5
Please select a country.
1) Afghanistan 18) Israel 35) Palestine
2) Armenia 19) Japan 36) Philippines
3) Azerbaijan 20) Jordan 37) Qatar
4) Bahrain 21) Kazakhstan 38) Russia
5) Bangladesh 22) Korea (North) 39) Saudi Arabia
6) Bhutan 23) Korea (South) 40) Singapore
7) Brunei 24) Kuwait 41) Sri Lanka
8) Cambodia 25) Kyrgyzstan 42) Syria
9) China 26) Laos 43) Taiwan
10) Cyprus 27) Lebanon 44) Tajikistan
11) East Timor 28) Macau 45) Thailand
12) Georgia 29) Malaysia 46) Turkmenistan
13) Hong Kong 30) Mongolia 47) United Arab Emirates
14) India 31) Myanmar (Burma) 48) Uzbekistan
15) Indonesia 32) Nepal 49) Vietnam
16) Iran 33) Oman 50) Yemen
17) Iraq 34) Pakistan
#? 9
Please select one of the following time zone regions.
1) Beijing Time
2) Xinjiang Time
#? 1
The following information has been given:
China
Beijing Time
Therefore TZ='Asia/Shanghai' will be used.
Local time is now: Tue Oct 18 23:02:51 CST 2016.
Universal Time is now: Tue Oct 18 15:02:51 UTC 2016.
Is the above information OK?
1) Yes
2) No
#? 1
You can make this change permanent for yourself by appending the line
TZ='Asia/Shanghai'; export TZ
to the file '.profile' in your home directory; then log out and log in again.
Here is that TZ value again, this time on standard output so that you
can use the /usr/bin/tzselect command in shell scripts:
Asia/Shanghai
tips¶
Linux挂载NTFS分区¶
安装ntfs-3g¶
sudo apt-get install ntfs-3g # Ubuntu
sudo yum install ntfs-3g # CentOS
fdisk -l查看磁盘设备点,挂载设备¶
mount -t ntfs-3g /dev/sdb1 /ntfs
tips¶
故障集锦¶
能连上机房主机,但是执行命令卡¶
背景:今早用xshell连上线上机器查看日志,能够连接上主机,但是执行命令卡,比如top、ll等命令卡,于是就开始排查,开始以为是工具问题,于是乎就重新安装了xshell,还是一样,另外使用了babun,CRT等远程连接工具,还是一样的效果。用crtl + c也结束不了。以下是效果图:
Connecting to 192.168.2.18:1898...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.
Last login: Sat Jan 7 11:55:54 2017 from 192.168.3.2
root@BJJGSERVER7: ~
# top
网上搜索一两个小时无果,于是请教总监,说了句应该是win mtu值导致的
Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。
C:\WINDOWS\system32>netsh interface ipv4 show subinterfaces
MTU MediaSenseState 传入字节 传出字节 接口
------ --------------- --------- --------- -------------
4294967295 1 0 1043428 Loopback Pseudo-Interface 1
1500 5 0 0 蓝牙网络连接 2
1500 1 5332716443 335378834 WLAN 2
1500 5 0 0 以太网 2
1500 5 0 0 本地连接* 7
1500 5 0 0 本地连接* 8
1500 1 3936 114097 以太网 3
1500 1 0 101166 以太网 4
C:\WINDOWS\system32>netsh interface ipv4 set subinterface "WLAN 2" mtu=1380 store=persistent
确定。
C:\WINDOWS\system32>netsh interface ipv4 show subinterfaces
MTU MediaSenseState 传入字节 传出字节 接口
------ --------------- --------- --------- -------------
4294967295 1 0 1043428 Loopback Pseudo-Interface 1
1500 5 0 0 蓝牙网络连接 2
1380 1 5333274289 335543910 WLAN 2
1500 5 0 0 以太网 2
1500 5 0 0 本地连接* 7
1500 5 0 0 本地连接* 8
1500 1 3936 114901 以太网 3
1500 1 0 101970 以太网 4
Zabbix报警Lack of free swap space¶
报警显示swap不够,是因为重新部署了几个java程序,然后立马把程序放在其他机器,报警几天还是持续有,因为太多程序,所以机器尽量不重启,然后需要分析那些程序占用swap,然后重启程序即可,报警详细截图如下

Alt text

Alt text
以下是脚本是分析占用swap最多的程序的前十个程序
[root@server198 ~]# cat swap.sh
#!/bin/bash
function swapoccupancy ()
{
echo -e "Pid\tSwap\tProgame"
num=0
for pid in `ls -1 /proc|egrep "^[0-9]"`
do
if [[ $pid -lt 20 ]]
then
continue
fi
program=`ps -eo pid,command|grep -w $pid|grep -v grep|awk '{print $2}'`
for swap in `grep Swap /proc/$pid/smaps 2>/dev/null|awk '{print $2}'`
do
let num=$num+$swap
done
echo -e "${pid}\t${num}\t${program}"
num=0
done|sort -nrk2|head
}
swapoccupancy
exit 0
然后执行脚本,可能会导致机器负载升高,取决于机器运行的程序多少,以下是运行结果
[root@server198 ~]# sh swap.sh
Pid Swap Progame
8807 1972956 /data/app/java/bin/java
8884 1117536 /data/app/java/bin/java
8091 486644 /usr/bin/mongod
11231 320180 /data/app/java/bin/java
11869 279472 /data/app/java/bin/java
38591 196480 java
41480 156956 /data/app/java/bin/java
18973 99272 /data/app/java/bin/java
23299 67280 /data/app/java/bin/java
38729 64384 java
然后根据程序pid查看对应的进程,挑选个合适的时间重启服务,把脚本执行的复制到文本,用for循环打印出对应的程序
[root@server198 ~]# vim swap_program.txt
[root@server198 ~]# cat swap_program.txt
Pid Swap Progame
8807 1972956 /data/app/java/bin/java
8884 1117536 /data/app/java/bin/java
8091 486644 /usr/bin/mongod
11231 320180 /data/app/java/bin/java
11869 279472 /data/app/java/bin/java
38591 196480 java
41480 156956 /data/app/java/bin/java
18973 99272 /data/app/java/bin/java
23299 67280 /data/app/java/bin/java
38729 64384 java
[root@server198 ~]# cat swap_program.txt|sed '1d'|awk '{print $1}'
8807
8884
8091
11231
11869
38591
41480
18973
23299
38729
[root@server198 ~]# for n in $(cat swap_program.txt|sed '1d'|awk '{print $1}');do ps -ef|grep $n|grep -v grep;done
参考
ssh加密私钥避免重复输入(keychain)¶
中英文内容不太一样
keychain¶
开机的时候缓存私钥,只需要将如下命令写入到.bash_profile等位置
eval `keychain --eval --agents ssh 123.rsa` &>/dev/null
# 123.rsa为私钥名字,放在.ssh目录下,且需要公钥存在,123.rsa.pub
在第一次使用的时候缓存
alias ssh='eval $(/usr/bin/keychain --eval --agents ssh -Q --quiet ~/.ssh/id_ecdsa) && ssh'
Mac电脑使用keychain¶
- 安装brew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
使用brew安装 keychain
brew install keychain
tips¶
使用 speedtest.net 通过命令行测试带宽¶
wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py
chmod +x speedtest-cli
./speedtest-cli
vscode使用技巧¶
删除文件行尾多余空格

VSCODE
vscode 使用七牛图床插件¶
插件名称 qiniu-upload-image
Install¶
方法一
Ctrl+p (Mac下为command + p) 输入命令: ext install qiniu-upload-image
方法二

vscode-qiniu1-2017221
选择,安装即可
设置¶
F1 -> 输入settings -> 选择Open User Settings (或者Mac直接使用快捷键command + ,)
如果是Workspace Settings,则会在当前目录创建一个隐藏目录,.vscode,目录下面创建一个settings.json,如果使用git托管的话,这个文件会一并存放到仓库,所以如果有如下密钥信息的时候,设置到User Settings更好.
// 七牛图片上传工具开关
"qiniu.enable": true,
// 一个有效的七牛 AccessKey 签名授权。 个人面板 -> 密钥管理
"qiniu.access_key": "",
// 一个有效的七牛 SecretKey 签名授权。
"qiniu.secret_key": "",
// 七牛图片上传空间。 对象存储 -> 空间
"qiniu.bucket": "yangjinjie",
// 七牛图片上传路径,参数化命名。例如:xxx.jpg-20170221
"qiniu.remotePath": "${fileName}-${date}",
// 七牛图床域名。 空间对应的测试域名,或者可以绑定个人域名,类似如下地址
"qiniu.domain": "http://oi480zo5x.bkt.clouddn.com",
使用telnet模拟http请求¶
利用telnet可以与服务器建立http连接,获取网页,实现浏览器的功能
对于需要对http header进行观察和测试的时候很方便
输入 telnet 域名 端口
telnet 127.0.0.1 60000
输入GET / HTTP/1.0
,连续按两次回车
➜ ~ telnet 127.0.0.1 60000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.0
就可以看到http response,包括header和body

telnet-01
注意GET和HTTP必须大写.小写可能会造成连接失败
如果使HTTP1.1的话,还需要加上如下内容
HOST:127.0.0.1
加HOST的原因,是因为很多网站采用的都是虚拟主机的形式,host用来区别于同一服务器的其他虚拟主机
2次回车表示把request发出去,因为http request最后以空行来表示结束
➜ ~ telnet 127.0.0.1 60000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
HOST:127.0.0.1

telnet-02
一些网站会屏蔽不是浏览器的http request
, 这时可以指定客户端
GET / HTTP/1.1
HOST:127.0.0.1
User-Agent: Mozilla/5.0 (Linux; U; Android 4.1.2; zh-tw; GT-I9300 Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
tmp¶
C¶
register¶
register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。
使用register修饰符的限制
- register变量必须是能被CPU所接受的类型。
这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
- 因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
- 只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。
在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。
- 局部静态变量不能定义为寄存器变量。不能写成:
register static int a, b, c;
- 由于寄存器的数量有限(不同的cpu寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
注意
早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定哪些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
Hexo¶
Hexo is a fast, simple and powerful blog framework. You write posts in Markdown (or other languages) and Hexo generates static files with a beautiful theme in seconds.
先决条件¶
依赖
- Node.js
- Git
Install Git¶
Linux (Ubuntu, Debian): sudo apt-get install git-core
Linux (Fedora, Red Hat, CentOS): sudo yum install git-core -y
# MacOS 可以使用brew安装git
# Windows直接下载安装包安装
Install Node.js¶
The best way to install Node.js is with nvm.
cURL:
$ curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | sh
Wget:
$ wget -qO- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | sh
Once nvm is installed, restart the terminal and run the following command to install Node.js.
$ nvm install stable
# 或者直接使用如下命令
sudo yum install npm -y
# Windows直接下载安装包安装
使用Hexo¶
查看帮助¶
# 执行命令 hexo help
hexo --help
Usage: hexo <command>
Commands:
help Get help on a command.
init Create a new Hexo folder.
version Display version information.
Global Options:
--config Specify config file instead of using _config.yml
--cwd Specify the CWD
--debug Display all verbose messages in the terminal
--draft Display draft posts
--safe Disable all plugins and scripts
--silent Hide output on console
For more help, you can use 'hexo help [command]' for the detailed information
or you can check the docs: http://hexo.io/docs/
hexo new "postName" #新建文章
hexo new page "pageName" #新建页面
hexo generate #生成静态页面至public目录
hexo server #开启预览访问端口(默认端口4000,'ctrl + c'关闭server)
hexo deploy #将.deploy目录部署到GitHub
hexo help #查看帮助
hexo version #查看Hexo的版本
快速开始¶
# 在当前目录创建blog目录, 并将其初始化为hexo 仓库
hexo init blog
cd blog
# hexo generate的简写, 会显示大量信息. 这个过程会生成大量博客相关的文件(css, js, html等)
hexo g
# 本地预览
hexo server
显示如下信息
INFO Start processing
INFO Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.
浏览器访问 http://localhost:4000/
以上就是Hexo使用默认主题,
在本地预览的情况,生成的内容在public
目录下,
该目录有整个静态站点需要的内容.
Hexo配置¶
配置文件¶
hexo仓库下 _config.yml
文件
修改主题¶
https://hexo.io/docs/themes.html
作者github上有主题详细信息, 具体请看上述链接
在hexo初始化的blog仓库里面执行如下命令
git clone --branch v5.1.2 https://github.com/iissnan/hexo-theme-next themes/next
修改主题为next, 文件 _config.yml
theme: next
$ git clone https://github.com/tufu9441/maupassant-hexo.git themes/maupassant
$ npm install hexo-renderer-jade --save
$ npm install hexo-renderer-sass --save
# 若安装报错,请使用淘宝NPM镜像进行安装
npm install hexo-renderer-sass --save 报错的话:
[root@hexo blog]# npm uninstall node-sass
[root@hexo blog]# npm install node-sass@latest
Deployment¶
https://hexo.io/docs/deployment.html
部署到GitHub Pages¶
- 注册GitHub账号
- 新建仓库, 命名为
username.github.io
, username为用户名 - 使用ssh方式(需要配置公钥), 或者https方式(部署的时候会提示输入用户名及密码)
npm install hexo-deployer-git --save
Edit settings.
deploy:
type: git
repo: <repository url>
branch: [branch]
message: [message]
Option | Description |
---|---|
repo | GitHub/Bitbucket/Coding/GitLab repositor
y
URL |
branch | Branch name. The deployer will detect the branch automatically if you are using GitHub or GitCafe. |
message | Customize commit message (默认消息为
Update Site: YYYY-MM-DD HH:mm:ss ,
内容为当前时间) |
hexo clean
hexo g -d
# 或者
# hexo d -g
使用七牛云搭建博客¶
利用七牛云搭建Hexo博客
Hexo全静态,所以可以使用七牛融合CDN通过以下方式搭建博客
1. 购买域名
2. 将域名通过CNAME解析到七牛提供的域名
3. 使用七牛云提供的开发者工具qrsbox,将博客内容同步到对象存储
使用 qrsbox 完整同步¶
删除该目录下日志文件 C::raw-latex:`\Users`:raw-latex:`\Administrator`.qrsbox
可以通过everything搜索 qrsbox查找该目录
临时目录¶
npm¶
How to Publish & Update a Package¶
https://docs.npmjs.com/getting-started/publishing-npm-packages
创建用户
在https://www.npmjs.com/创建或使用命令创建
npm adduser
在终端使用 npm login
登录.
测试登录
npm whoami
Check that your username has been added to the registry at https://npmjs.com/~username.
发布
npm publish
svn¶
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun¶
Have you upgraded to OS X El Capitan from App Store ?
Have you suddenly started getting the following error in your project?
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
If yes, then here is the solution
xcode-select --install
Remember, in MAC git is attached to XCode’s Command line tools.
sqlite: database disk image is malformed¶
This has been asked before:
svn cleanup: sqlite: database disk image is malformed
The answer there is:
You do an integrity check on the sqlite database that keeps track of the repository (/.svn/wc.db):
sqlite3 .svn/wc.db "pragma integrity_check"
That should report some errors.
Then you might be able to clean them up by doing:
sqlite3 .svn/wc.db "reindex nodes"
sqlite3 .svn/wc.db "reindex pristine"
If there are still errors after that, you still got the option to check out a fresh copy of the repository to a temporary folder and copy the .svn folder from the fresh copy to the old one. Then the old copy should work again and you can delete the temporary folder.
svn客户端E155037错误, 解决:执行 svn cleanup 即可¶
分布式系统-Raft算法¶
Raft 是一种为了管理复制日志的一致性算法
https://www.jianshu.com/p/4711c4c32aab
过去, Paxos一直是分布式协议的标准,但是Paxos难于理解,更难以实现
Consensus 一致性这,它是指多个服务器在状态达成一致,但是在一个分布式系统中,因为各种意外可能,有的服务器可能会崩溃或变得不可靠,它就不能和其他服务器达成一致状态。这样就需要一种 Consensus协议,一致性协议是为了确保容错性,也就是即使系统中有一两个服务器当机,也不会影响其处理过程。
为了以容错方式达成一致,我们不可能要求所有服务器100%都达成一致状态,只要超过半数的大多数服务器达成一致就可以了,假设有N台服务器,N/2 +1 就超过半数,代表大多数了。
Paxos和Raft都是为了实现Consensus一致性这个目标,这个过程如同选举一样,参选者需要说服大多数选民(服务器)投票给他,一旦选定后就跟随其操作。Paxos和Raft的区别在于选举的具体过程不同。
在Raft中,任何时候一个服务器可以扮演下面角色之一:
- Leader: 处理所有客户端交互,日志复制等,一般一次只有一个Leader.
- Follower: 类似选民,完全被动
- Candidate候选人: 类似Proposer律师,可以被选为一个新的领导人。
Raft阶段分为两个,首先是选举过程,然后在选举出来的领导人带领进行正常操作,比如日志复制等。下面用图示展示这个过程
- 任何一个服务器都可以成为一个候选者Candidate,它向其他服务器Follower发出要求选举自己的请求
- 其他服务器同意了,发出OK。 如果在这个过程中,有一个Follower宕机,没有收到请求选举的要求,因此候选者可以自己选自己,只要达到N/2 + 1 的大多数票,候选人还是可以成为Leader的。
- 这样这个候选者就成为了Leader领导人,它可以向选民也就是Follower们发出指令,比如进行日志复制。
- 以后通过心跳进行日志复制的通知
- 如果一旦这个Leader当机崩溃了,那么Follower中有一个成为候选者,发出邀票选举。
- Follower同意后,其成为Leader,继续承担日志复制等指导工作
正则表达式¶
简介¶
为什么使用正则表达式¶
方便处理文本信息和数据.
通过使用正则表达式,可以:
- 测试字符串内的模式。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。
- 替换文本。
可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。
- 基于模式匹配从字符串中提取子字符串。
可以查找文档内或输入域内特定的文本。
发展历史¶
正则表达式的“祖先”可以一直上溯至对人类神经系统如何工作的早期研究。Warren McCulloch 和 Walter Pitts 这两位神经生理学家研究出一种数学方式来描述这些神经网络。
1956 年, 一位叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了一篇标题为“神经网事件的表示法”的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为“正则集的代数”的表达式,因此采用“正则表达式”这个术语。
随后,发现可以将这一工作应用于使用 Ken Thompson 的计算搜索算法的一些早期研究,Ken Thompson 是 Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的 qed 编辑器。
如他们所说,剩下的就是众所周知的历史了。从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。
语法¶
正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。
- 列目录时, dir *.txt或ls *.txt中的*.txt就不是一个正则表达式,因为这里*与正则式的*的含义是不同的。
- 构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为“元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
普通字符¶
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
非打印字符¶
非打印字符也可以是正则表达式的组成部分。下表列出了表示非打印字符的转义序列:
:raw-latex:`\v |` 匹配一个垂直制表符。等价于 :raw-latex:`\x`0b 和 :raw-latex:`cK`。
特殊字符¶
所谓特殊字符,就是一些有特殊含义的字符,如上面说的“.txt“中的*,简单的说就是表示任何字符串的意思。如果要查找文件名中有的文件,则需要对*进行转义,即在其前加一个。ls *.txt。
许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符“转义”,即,将反斜杠字符 () 放在它们前面。下表列出了正则表达式中的特殊字符:
限定符¶
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有*或+或?或{n}或{n,}或{n,m}共6种。
正则表达式的限定符有:
由于章节编号在大的输入文档中会很可能超过九,所以您需要一种方式来处理两位或三位章节编号。限定符给您这种能力。下面的正则表达式匹配编号为任何位数的章节标题:
/Chapter [1-9][0-9]*/
请注意,限定符出现在范围表达式之后。因此,它应用于整个范围表达式,在本例中,只指定从 0 到 9 的数字(包括 0 和 9)。 这里不使用 + 限定符,因为在第二个位置或后面的位置不一定需要有一个数字。也不使用?字符,因为它将章节编号限制到只有两位数。您需要至少匹配 Chapter 和空格字符后面的一个数字。 如果您知道章节编号被限制为只有 99 章,可以使用下面的表达式来至少指定一位但至多两位数字。
/Chapter [0-9]{1,2}/
上面的表达式的缺点是,大于 99 的章节编号仍只匹配开头两位数字。另一个缺点是 Chapter 0 也将匹配。只匹配两位数字的更好的表达式如下:
/Chapter [1-9][0-9]?/
或
/Chapter [1-9][0-9]{0,1}/
*、+
和?限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个?就可以实现非贪婪或最小匹配。
例如,您可能搜索 HTML 文档,以查找括在 H1 标记内的章节标题。该文本在您的文档中如下:
<H1>Chapter 1 – Introduction to Regular Expressions</H1>
下面的表达式匹配从开始小于符号 (<) 到关闭 H1 标记的大于符号 (>) 之间的所有内容。
/<.*>/
如果您只需要匹配开始 H1 标记,下面的“非贪心”表达式只匹配 <H1>
。
/<.*?>/
通过在 *、+ 或 ?
限定符之后放置
?
,该表达式从“贪心”表达式转换为“非贪心”表达式或者最小匹配。
定位符¶
定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。
定位符用来描述字符串或单词的边界,^和$分别指字符串的开始与结束,:raw-latex:`\b描述单词的前或后边界`,:raw-latex:`\B表示非单词边界`。
正则表达式的限定符有:
注意:不能将限定符与定位点一起使用。由于在紧靠换行或者字边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。
若要匹配一行文本开始处的文本,请在正则表达式的开始使用 ^ 字符。不要将 ^ 的这种用法与中括号表达式内的用法混淆。
若要匹配一行文本的结束处的文本,请在正则表达式的结束处使用 $ 字符。
若要在搜索章节标题时使用定位点,下面的正则表达式匹配一个章节标题,该标题只包含两个尾随数字,并且出现在行首:
/^Chapter [1-9][0-9]{0,1}/
真正的章节标题不仅出现行的开始处,而且它还是该行中仅有的文本。它即出现在行首又出现在同一行的结尾。下面的表达式能确保指定的匹配只匹配章节而不匹配交叉引用。通过创建只匹配一行文本的开始和结尾的正则表达式,就可做到这一点。
/^Chapter [1-9][0-9]{0,1}$/
匹配字边界稍有不同,但向正则表达式添加了很重要的能力。字边界是单词和空格之间的位置。非字边界是任何其他位置。下面的表达式匹配单词 Chapter 的开头三个字符,因为这三个字符出现字边界后面:
/\bCha/
:raw-latex:`b `字符的位置是非常重要的。如果它位于要匹配的字符串的开始,它在单词的开始处查找匹配项。如果它位于字符串的结尾,它在单词的结尾处查找匹配项。例如,下面的表达式匹配单词 Chapter 中的字符串 ter,因为它出现在字边界的前面:
/ter\b/
下面的表达式匹配 Chapter 中的字符串 apt,但不匹配 aptitude 中的字符串 apt:
/\Bapt/
字符串 apt 出现在单词 Chapter 中的非字边界处,但出现在单词 aptitude 中的字边界处。对于 :raw-latex:`B `非字边界运算符,位置并不重要,因为匹配不关心究竟是单词的开头还是结尾。
选择¶
用圆括号将所有选择项括起来,相邻的选择项之间用|分隔。但用圆括号会有一个副作用,是相关的匹配会被缓存,此时可用?:放在第一个选项前来消除这种副作用。
其中?:
是非捕获元之一,还有两个非捕获元是?=和?!,这两个还有更多的含义,前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。
反向引用¶
对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘:raw-latex:`\n`’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
可以使用非捕获元字符 ?:
、?=
或 ?!
来重写捕获,忽略对相关匹配的保存。
反向引用的最简单的、最有用的应用之一,是提供查找文本中两个相同的相邻单词的匹配项的能力。以下面的句子为例:
Is is the cost of of gasoline going up up?
上面的句子很显然有多个重复的单词。如果能设计一种方法定位该句子,而不必查找每个单词的重复出现,那该有多好。下面的正则表达式使用单个子表达式来实现这一点:
/\b([a-z]+) \1\b/gi
捕获的表达式,正如 [a-z]+ 指定的,包括一个或多个字母。正则表达式的第二部分是对以前捕获的子匹配项的引用,即,单词的第二个匹配项正好由括号表达式匹配。1 指定第一个子匹配项。字边界元字符确保只检测整个单词。否则,诸如“is issued”或“this is”之类的词组将不能正确地被此表达式识别。
正则表达式后面的全局标记 (g) 指示,将该表达式应用到输入字符串中能够查找到的尽可能多的匹配。表达式的结尾处的不区分大小写 (i) 标记指定不区分大小写。多行标记指定换行符的两边可能出现潜在的匹配。
反向引用还可以将通用资源指示符 (URI) 分解为其组件。假定您想将下面的 URI 分解为协议(ftp、http 等等)、域地址和页/路径:
http://www.w3cschool.cc:80/html/html-tutorial.html
下面的正则表达式提供该功能:
/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/
第一个括号子表达式捕获 Web 地址的协议部分。该子表达式匹配在冒号和两个正斜杠前面的任何单词。第二个括号子表达式捕获地址的域地址部分。子表达式匹配 / 和 : 之外的一个或多个字符。第三个括号子表达式捕获端口号(如果指定了的话)。该子表达式匹配冒号后面的零个或多个数字。只能重复一次该子表达式。最后,第四个括号子表达式捕获 Web 地址指定的路径和/或页信息。该子表达式能匹配不包括 # 或空格字符的任何字符序列。 将正则表达式应用到上面的 URI,各子匹配项包含下面的内容:
第一个括号子表达式包含"http"
第二个括号子表达式包含"www.w3cschool.cc"
第三个括号子表达式包含":80"
第四个括号子表达式包含"/html/html-tutorial.html"
设计模式¶
设计模式介绍¶
可复用面向对象软件的基础
设计模式分类¶
<> 归纳23种设计模式
- 创建型
- 结构型
- 行为型
创建型模型¶
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 创建者模式(Builder)
- 原形模式(Prototype)
- 单例模式(Singleton)
结构型模型¶
- 外观模式(Facade)
- 适配器模式(Adapter)
- 代理模式(Proxy)
- 装饰模式(Decorator)
- 桥模式(Bridge)
- 组合模式(Composite)
- 享元模式(Flyweight)
行为模式¶
- 模板方法模式(Template Method)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 访问者模式(Visitor)
- 调停模式(Mediator)
- 备忘录模式(Memento)
- 迭代器模式(Iterator)
- 解释器模式(Interpreter)
设计模式六大原则¶
- 开闭原则(Open Close Principle)
- 里氏代换原则(Liskov Substitution Principle)
- 依赖倒转原则(Dependence Inversion Principle)
- 接口隔离原则(Interface Segregation Principle)
- 迪米特法则(最少知道原则)(Demeter Principle)
- 合成复用原则(Composite Reuse Principle)
tools¶
Google¶
Google浏览器使用技巧¶
恢复刚刚误关闭的标签页¶
使用组合键 CMD-Shift-T (Windows: Ctrl-Shift-T) 就可以找回最近被关闭的网页,当然,你也可以连续点击这套组合键,因为 Chrome 会根据历史信息,按被关闭的时间逐次打开。本操作在「隐身」模式下无法完成。
隐身模式¶
使用组合键 CMD-Shift-N (Windows: Ctrl-Shift-N) 开启[隐身」模式。退出「隐身」模式可以逐一关闭每个标签页,也可以点击窗口 X 键,或使用 CMD-Shift-W 组合键快速退出。
快速搜索其实很简单¶
在选中关键字、词、句后,将其拖拽至 Chrome 的地址栏即可通过默认搜索引擎直接搜索。操作步骤非常简单,但还是需要注意,OS X 默认的拖拽和选择手势皆为三指滑动,但在 Chrome 的网页中只能用于选择,无法拖拽,所以此步骤需要重击鼠标左键才行。当然,有类似拖拽插件具有更方便的功能.
提高切换标签页的效率¶
在 Safari 中切换标签页,可以通过组合键 Ctrl-tab 及 Ctrl-Shift-tab 达到顺序或逆序切换,Chrome 也同样如此,只是两者的区别在于,Chrome 可以通过 CMD-数字键 (Windows: Ctrl-数字键) 按标签页的排序进行跳跃切换,并且 CMD-9 (Ctrl-9) 可直接跳转至最后一个标签页。
简易计算器(地址栏)¶
地址栏除了可以输入网址,链接本地文件,在 Chrome 中还可以用于生成简单的运算结果。这完全依赖于 Omnibox(你可以把它认作 Chrome 地址栏的拓展插件)而且,它还可以直接回答你提出的小问题,比如:how many cups are in 2 liters?
视图移动¶
空格键 Space 可以按单位移动至网页的下一视图区域,同时按下 Shift-Space 还可以向上移动视图,这样,浏览网页就能通过键盘完成了。只是,就默认的效果来看,与其说这是滚动,倒不如说是在走格子,视觉效果与 Safari 相比,实在太差强人意了。
固定标签页¶
常用标签,可以使用「固定标签页」功能保留图标本身,既节省标签栏空间,又方便下次打开常用标签。
为扩展程序设置启动快捷键¶
像「保存至 Pocket」这类的操作流程,完全可以通过 Chrome 插件代替,而这些插件也完全可以通过键盘实现,但前提是,你得为它们设置快捷键;当然,被动型的插件是无法通过键盘开启的,比如:Adblock。
地址栏Ctrl键-为关键词补全com后缀¶
举个例子,当我想进入少数派的主页时,可以在地址栏中输入 www.baidu.com
也可以输入 baidu.com 回车,毕竟 http://
和 www.
一般都会自动补充。但直接输入 baidu
回车,会被当作关键词搜索,这样反而增加了操作步骤,怎么办?同时按住 Ctrl
键 可以解决补全 .com
.
被忽视的任务管理器¶
不可避免的是,即使是全球用户量最大的浏览器,也依然伴随着许多问题,其中较为顽固的,甚至都无法通过正常的方式探测 & 解决。所以此时,我们就需要用到一项容易被忽略的功能:任务管理器。这里指的是 Chrome 的内置任务管理器,打开后,所有与 Chrome 相关的系统资源占用率一目了然,万一遇到单方面的故障现象,则可以方便地选择性关闭,而不是终止 Chrome 的所有进程,最大化了用户数据保存概率。
More Tools -> Task Manager
git¶
BFG Repo-Cleaner¶
可以像`git-filter-branch
<http://git-scm.com/docs/git-filter-branch>`__一样清除提交历史,
比如删除无用提交, 删除仓库大文件
GitBook 入门¶
GitBook使用¶
Gitbook有几个主要的文件
这个文件相当于一本Gitbook的简介,最上层(和SUMMARY.md
文件同级)的README.md
文件最终会被用于生成本书的Introduction
。
该文件是书的目录结构,使用Markdown语法,这个文件在使用gitbook命令行工具之前要先写好,以便生成书籍目录.
gitbook editor 实际上就是一个本地应用版的在线编辑器,使用方式和 gitbook在线编辑器类似,而这里的目录及章节是使用编辑器添加生成,同时也会存放到本地之前建立的目录.
导出图书¶
目前为止,Gitbook支持如下格式输出:
- 静态HTML,可以看作一个静态网站
- PDF格式
- eBook格式
- 单个HTML文件
- JSON格式
➜ ops-book git:(master) ls
BOXFISH.md Docker README.md VPN book.json nexus.md 内部
CI ELK SUMMARY.md assets nexus node_modules 监控
➜ ops-book git:(master) gitbook serve .
Live reload server started on port: 35729
Press CTRL+C to quit ...
info: 9 plugins are installed
info: 8 explicitly listed
info: loading plugin "toggle-chapters"... OK
info: loading plugin "splitter"... OK
info: loading plugin "livereload"... OK
info: loading plugin "highlight"... OK
info: loading plugin "search"... OK
info: loading plugin "lunr"... OK
info: loading plugin "fontsettings"... OK
info: loading plugin "theme-default"... OK
info: found 81 pages
info: found 47 asset files
info: >> generation finished with success in 15.4s !
Starting server ...
Serving book on http://localhost:4000
可以在浏览器中打开这个网址:http://localhost:4000
此时目录下面会多出一个 _book
目录,这个目录就是就是生成的静态网站内容.
在图书目录的上层目录使用如下命令生成
➜ notes gitbook build ops-book out_book # ops-book为项目目录名 静态网页将生成在out_book目录下
info: 9 plugins are installed
info: 7 explicitly listed
info: loading plugin "toggle-chapters"... OK
info: loading plugin "splitter"... OK
info: loading plugin "highlight"... OK
info: loading plugin "search"... OK
info: loading plugin "lunr"... OK
info: loading plugin "fontsettings"... OK
info: loading plugin "theme-default"... OK
info: found 81 pages
info: found 47 asset files
info: >> generation finished with success in 17.7s !
$ gitbook pdf ./ ./mybook.pdf
# Generate an ePub file
$ gitbook epub ./ ./mybook.epub
# Generate a Mobi file
$ gitbook mobi ./ ./mybook.mobi
生成 ebooks (epub, mobi, pdf) 依赖 ebook-convert
.
GNU/Linux
安装 Calibre
.
sudo aptitude install calibre
在一些 GNU/Linux 发行版 node
被安装的名字为 nodejs
,
手动创建软链接:
sudo ln -s /usr/bin/nodejs /usr/bin/node
OS X
下载 Calibre application. 安装后创建软链接:
sudo ln -s ~/Applications/calibre.app/Contents/MacOS/ebook-convert /usr/bin
/usr/bin
可以修改为任何在环境变量里面的路径.
Covers are used for all the ebook formats. You can either provide one yourself, or generate one using the autocover plugin.
由于生成pdf epub mobi等文件依赖于ebook-convert,故首先下载安装 Calibre
这一步有一个关键地方:和windows一样要声明环境变量,否则还是会报ebook-convert……
mac的方式是:
sudo ln -s /Applications/calibre.app/Contents/MacOS/ebook-convert /usr/local/bin
也有另外一种:Mac下该工具已包含在安装包中,用户在使用前请执行
export PATH="$PATH:/Applications/calibre.app/Contents/MacOS/" # 将cli tools路径加入系统路径,或将此句加入.bashrc。
export PATH=/Applications/calibre.app/Contents/MacOS:$PATH
➜ ~ echo $PATH
gitbook pdf gitbook ./gitbook入门教程.pdf
然后,你会发现你的目录里多了一个名为gitbook入门教程.pdf的文件,就是它了!
GitBook 进阶¶
个性化配置¶
除了修改书籍的主题外,还可以通过配置 book.json 文件来修改 gitbook 在编译书籍时的行为,例如:修改书籍的名称,显示效果等等。
gitbook 在编译书籍的时候会读取书籍源码顶层目录中的 book.js 或者 book.json
book.json 配置示例¶
{
"author": "Yang",
"description": "notes",
"extension": null,
"generator": "site",
"links": {
"sharing": {
"all": null,
"facebook": null,
"google": null,
"twitter": null,
"weibo": null
},
"sidebar": {
"notes": "https://yangjinjie.github.io"
}
},
"pdf": {
"fontSize": 18,
"footerTemplate": null,
"headerTemplate": null,
"margin": {
"bottom": 36,
"left": 62,
"right": 62,
"top": 36
},
"pageNumbers": false,
"paperSize": "a4"
},
"plugins": [
"toggle-chapters",
"theme-comscore",
"splitter",
"-sharing",
"search"
],
"title": "notes",
"variables": {}
}
插件¶
插件详情 https://toolchain.gitbook.com/plugins/
gitbook 支持许多插件,用户可以从 NPM 上搜索 gitbook 的插件,gitbook 文档 推荐插件的命名方式为:
- gitbook-plugin-X: 插件
- gitbook-theme-X: 主题
GitBook默认带有6个插件:
- font-settings
- highlight
- lunr
- search
- sharing
- theme-default
去除自带插件, 可在插件名前加-
"plugins": [
"-search"
]
方法一:
- 配置book.json 下的 plugins
- 然后执行命令 gitbook install(自动安装插件)
"plugins": [
"toggle-chapters",
"theme-comscore",
"splitter",
"-sharing",
"search"
],
完整配置请查看
方法二:
npm install gitbook-plugin-multipart -g
然后编辑 book.json 添加 multipart 到 plugins 中:
```json
"plugins": [
"multipart"
],
```
"myPlugin@0.3.1"
ComScore 是一个彩色主题,默认的 gitbook 主题是黑白的,也就是标题和正文都是黑色的,而 ComScore 可以为各级标题添加不同的颜色,更容易区分各级标题
- disqus, 集成用户评论系统
- multipart 插件可以将书籍分成几个部分,例如:
- GitBook Basic
- GitBook Advanced
- 对有非常多章节的书籍非常有用,分成两部分后,各个部分的章节都从 1 开始编号。
- toggle-chapters 使左侧的章节目录可以折叠
- Splitter 使侧边栏的宽度可以自由调节
模板¶
https://toolchain.gitbook.com/templating/
忽略特殊的模板标签, 输出为纯文本。
{% raw %}
this will {{ not be processed }}
{% endraw %}
Git¶
名词¶
Workspace:工作区
Index / Stage:暂存区
Repository:仓库区(或本地仓库)
Remote:远程仓库
常用命令速查表¶

git-command
新建代码库¶
在当前目录新建一个Git代码库
git init
新建一个目录, 将其初始化为Git代码库
git init [project-name]
下载一个项目和它的整个代码历史
git clone [url]
配置¶
Git的设置文件为.gitconfig, 它可以在用户主目录下(全局配置), 也可以在项目目录下(项目配置)。
显示当前的Git配置
git config --list
编辑Git配置文件
git config -e [--global]
设置提交代码时的用户信息
git config [--global] user.name "[name]"
git config [--global] user.email "[email address]"
增加/删除文件¶
# 添加指定文件到暂存区
$ git add [file1] [file2] ...
# 添加指定目录到暂存区, 包括子目录
$ git add [dir]
# 添加当前目录的所有文件到暂存区
$ git add .
# 删除工作区文件, 并且将这次删除放入暂存区
$ git rm [file1] [file2] ...
# 停止追踪指定文件, 但该文件会保留在工作区
$ git rm --cached [file]
# 改名文件, 并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]
代码提交¶
# 提交暂存区到仓库区, -m后面输入的是本次提交的说明, 可以输入任意内容, 当然最好是有意义的, 这样你就能从历史记录里方便地找到改动记录
$ git commit -m [message]
# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]
# 提交工作区自上次commit之后的变化, 直接到仓库区
$ git commit -a
# 提交时显示所有diff信息
$ git commit -v
# 使用一次新的commit, 替代上一次提交
# 如果代码没有任何新变化, 则用来改写上一次commit的提交信息
$ git commit --amend -m [message]
# 重做上一次commit, 并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...
分支¶
列出所有本地分支
git branch
列出所有远程分支
git branch -r
列出所有本地分支和远程分支
git branch -a
新建一个分支, 但依然停留在当前分支
git branch [branch-name]
新建一个分支, 并切换到该分支
git checkout -b [branch]
新建一个分支, 指向指定commit
git branch [branch] [commit]
新建一个分支, 与指定的远程分支建立追踪关系
git branch --track [branch] [remote-branch]
切换到指定分支, 并更新工作区
git checkout [branch-name]
建立追踪关系, 在现有分支与指定的远程分支之间
git branch --set-upstream [branch] [remote-branch]
合并指定分支到当前分支
git merge [branch]
选择一个commit, 合并进当前分支
git cherry-pick [commit]
删除分支
git branch -d [branch-name]
删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]
标签¶
列出所有tag
git tag
新建一个tag在当前commit
git tag [tag]
新建一个tag在指定commit
git tag [tag] [commit]
查看tag信息
git show [tag]
提交指定tag
git push [remote] [tag]
提交所有tag
git push [remote] --tags
新建一个分支, 指向某个tag
git checkout -b [branch] [tag]
查看信息¶
显示有变更的文件
git status
显示当前分支的版本历史
git log
git log --pretty=oneline
显示commit历史, 以及每次commit发生变更的文件
git log --stat
显示某个文件的版本历史, 包括文件改名
git log --follow [file]
git whatchanged [file]
显示指定文件相关的每一次diff
git log -p [file]
显示指定文件是什么人在什么时间修改过
git blame [file]
显示暂存区和工作区的差异
git diff
显示暂存区和上一个commit的差异
git diff --cached [file]
显示工作区与当前分支最新commit之间的差异
git diff HEAD
显示两次提交之间的差异
git diff [first-branch]...[second-branch]
显示某次提交的元数据和内容变化
git show [commit]
显示某次提交发生变化的文件
git show --name-only [commit]
显示某次提交时, 某个文件的内容
git show [commit]:[filename]
显示当前分支的最近几次提交
git reflog
远程同步¶
# 下载远程仓库的所有变动
$ git fetch [remote]
# 显示所有远程仓库
$ git remote -v
# 显示某个远程仓库的信息
$ git remote show [remote]
# 增加一个新的远程仓库, 并命名
$ git remote add [shortname] [url]
# 取回远程仓库的变化, 并与本地分支合并
$ git pull [remote] [branch]
# 上传本地指定分支到远程仓库
$ git push [remote] [branch]
# 强行推送当前分支到远程仓库, 即使有冲突
$ git push [remote] --force
# 推送所有分支到远程仓库
$ git push [remote] --all
撤销¶
# 恢复暂存区的指定文件到工作区
$ git checkout [file]
# 恢复某个commit的指定文件到工作区
$ git checkout [commit] [file]
# 恢复上一个commit的所有文件到工作区
$ git checkout .
# 重置暂存区的指定文件, 与上一次commit保持一致, 但工作区不变
$ git reset [file]
重置暂存区与工作区, 与上一次commit保持一致
git reset --hard
回退到上一个版本
git reset --hard HEAD^
上上一个版本就是HEAD^^, 当然往上100个版本写100个^比较容易数不过来, 所以写成HEAD~100
重置当前分支的指针为指定commit, 同时重置暂存区, 但工作区不变
git reset [commit]
重置当前分支的HEAD为指定commit, 同时重置暂存区和工作区, 与指定commit一致
git reset --hard [commit]
重置当前HEAD为指定commit, 但保持暂存区和工作区不变
git reset --keep [commit]
# 新建一个commit, 用来撤销指定commit
后者的所有变化都将被前者抵消, 并且应用到当前分支
git revert [commit]
其他¶
# 生成一个可供发布的压缩包
$ git archive
git add readme.txt
$ git commit -m "wrote a readme file"
[master (root-commit) cf4b2e8] wrote a readme file
1 file changed, 1 insertion(+)
create mode 100644 readme.txt
简单解释一下git commit命令
git commit命令执行成功后会告诉你, 1个文件被改动(我们新添加的readme.txt文件), 插入了两行内容(readme.txt有两行内容)。
为什么Git添加文件需要add, commit一共两步呢?因为commit可以一次提交很多文件, 所以你可以多次add不同的文件, 比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
忽略特殊文件.gitignore¶
有些时候, 你必须把某些文件放到Git工作目录中, 但又不能提交它们, 比如保存了数据库密码的配置文件啦, 等等, 每次git status都会显示Untracked files …, 有强迫症的童鞋心里肯定不爽。
解决办法: 在Git工作区的根目录下创建一个特殊的.gitignore文件, 然后把要忽略的文件名填进去, Git就会自动忽略这些文件。
不需要从头写.gitignore文件, GitHub已经为我们准备了各种配置文件, 只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore
忽略文件的原则是:
- 忽略操作系统自动生成的文件, 比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等, 也就是如果一个文件是通过另一个文件自动生成的, 那自动生成的文件就没必要放进版本库, 比如Java编译产生的.class文件;
- 忽略你自己的带有敏感信息的配置文件, 比如存放口令的配置文件。
编写.gitignore之后,将文件也提交到git,就完成了
有些时候, 你想添加一个文件到Git, 但发现添加不了, 原因是这个文件被.gitignore忽略了
$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.
如果你确实想添加该文件, 可以用-f强制添加到Git:
git add -f App.class
或者你发现, 可能是.gitignore写得有问题, 需要找出来到底哪个规则写错了, 可以用git check-ignore命令检查:
git check-ignore -v App.class
.gitignore:3:*.class App.class
Git会告诉我们, .gitignore的第3行规则忽略了该文件, 于是我们就可以知道应该修订哪个规则。
小结¶
- 初始化一个Git仓库, 使用git init命令。
- 添加文件到Git仓库, 分两步:
- 第一步, 使用命令
git add <file>
, 注意, 可反复多次使用, 添加多个文件; - 第二步, 使用命令
git commit
, 完成
- 第一步, 使用命令
- 忽略某些文件时, 需要编写.gitignore;
- .gitignore文件本身要放到版本库里, 并且可以对.gitignore做版本管理!
部分实例¶
获取远程分支到本地¶
git fetch # 将远程分支信息获取到本地, 再运行
git checkout -b local-branchname origin/remote_branchname # 将远程分支映射到本地命名为local-branchname的分支。
git pull # 拉取分支
忽略特殊文件.gitignore¶
删除错误提交¶
# 彻底回退到某个版本
git reset --hard <commit_id>
# 强制推送
git push origin HEAD --force
删除远程分支¶
# 查看远程分支
git branch -r
删除远程分支
git branch -r -d origin/branch-name
git push origin :branch-name
全局忽略 .DS_Store¶
- 创建
~/.gitignore_global
文件 - 把需要全局忽略的文件类型写到这个文件里。
# .gitignore_global
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
Gists¶
请求地址 https://api.github.com
认证¶
gists 只需要授权
gist
: Grants write access to gists.
设置位置: Settings
-> Developer settings
->
Personal access tokens
curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com/users/technoweenie -I
X-OAuth-Scopes
列出TOKEN授权的范围 authorized.X-Accepted-OAuth-Scopes
列出操作检查的范围.
列出某个用户的 gists¶
列出指定用户的 public gists:
GET /users/:username/gists
# /users/yangjinjie/gists
示例
curl https://api.github.com/users/yangjinjie/gists
创建 gist¶
POST /gists
Input
Name | Type | Description |
---|---|---|
files |
object |
Required. Files that make up this gist. |
description |
string |
A description of the gist. |
public |
boolean |
Indicates whether the
gist is public.
Default: false |
{
"description": "关于此gist的描述",
"public": true,
"files": {
"file1.txt": {
"content": "String file contents"
}
}
}
示例: 使用requests创建
def create_gist(headers):
# headers = {
# "Authorization": "token YOUR-TOKEN"
# }
payload = {
"description": "关于此gist的描述",
"public": "true",
"files": {
"file1.txt": {
"content": "create gist by requests"
}
}
}
r = requests.post(url="https://api.github.com/gists", data=json.dumps(payload), headers=headers)
print(r.text)
编辑 gist¶
PATCH /gists/:id
示例
def edit_gist(headers):
# headers = {
# "Authorization": "token YOUR-TOKEN"
# }
payload = {
"description": "the description for this gist",
"files": {
"new_name1.txt": {
"content": 'xxx'
}
}
}
id = '689019b5c033a931096396cb609f5c49'
r = requests.patch(url="https://api.github.com/gists/{id}".format(id=id), data=json.dumps(payload), headers=headers)
print(r.text)
// 修改文件名, 修改内容
"old_name.txt": {
"filename": "new_name.txt",
"content": "modified contents"
},
其他详见官方开发文档¶
git 将代码推送到多个仓库¶
命令方式添加远程仓库
git remote set-url --add origin git@git.coding.net:ruiruiliu/notes.git
修改项目.git文件下的config文件(提交到两个仓库的相同分支)¶
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
[remote "origin"]
url = ssh://gradle仓库地址
url = http://gitlab仓库地址
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
修改项目.git文件下的config文件(提交到两个仓库的不同分支)¶
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
[remote "gradle"]
url = ssh://gradle仓库地址
fetch = +refs/heads/*:refs/remotes/gradle/*
[remote "gitlab"]
url = http://gitlab仓库地址
fetch = +refs/heads/*:refs/remotes/gitlab/*
[branch "master"]
remote = origin
merge = refs/heads/master
[alias]
publish=!sh -c \"git push gradle master && git push gitlab master:master\"
示例¶
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = git@git.coding.net:ruiruiliu/notes.git
# url = git@git.oschina.net:yangjinjie/notes.git
fetch = +refs/heads/*:refs/remotes/origin/*
# pushurl = git@git.coding.net:ruiruiliu/notes.git # 指定push代码到指定仓库
url = git@git.oschina.net:yangjinjie/notes.git
[branch "master"]
remote = origin
merge = refs/heads/master
[user]
name = yangjinjie
email = 51474159@qq.com
Git问题记录¶
Git Fatal: cannot do a partial commit during a merge¶
在提交单个文件的时候出现这个错误.
意思是不能部分提交代码.
原因是Git认为你有部分代码没有做好提交的准备,比如没有添加
解决方法是
提交全部
git commit -a -m “xxx”
如果不想提交全部,那么可以通过添加 -i 选项
git commit file/to/path -i -m “merge”
上述情况一般出现在解决本地working copy冲突时出现, 本地文件修改(手工merge)完成后,要添加并提交,使得本地版本处于clean的状态.
这样以后git pull就不再会报错.
git多平台换行符问题 LF will be replaced by CRLF¶
warning: LF will be replaced by CRLF in readme.txt.
The file will have its original line endings in your working directory.
git config --global core.autocrlf false
# false 表示按文件原来的样子
刚创建的github版本库,在push代码时出错¶
$ git push -u origin master
To git@github.com:**/Demo.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to ‘git@github.com:**/Demo.git’
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. ‘git pull’)
hint: before pushing again.
hint: See the ‘Note about fast-forwards’ in ‘git push –help’ for details.
网上搜索了下,是因为远程repository和我本地的repository冲突导致的,而我在创建版本库后,在github的版本库页面点击了创建README.md文件的按钮创建了说明文档,但是却没有pull到本地。这样就产生了版本冲突的问题。
有如下几种解决方法:
- 使用强制push的方法:
git push -u origin master -f
# 这样会使远程修改丢失,一般是不可取的,尤其是多人协作开发的时候。
- push前先将远程repository修改pull下来
git pull origin master
git push -u origin master
- 若不想merge远程和本地修改,可以先创建新的分支
git branch [name]
# 然后push
git push -u origin [name]
git pull提示“no tracking information”¶
原因: y本地分支和远程分支的链接关系没有创建,使用如下命令解决
git branch --set-upstream branch-name origin/branch-name
或者:
git branch --set-upstream-to=origin/dev dev
因此,多人协作的工作模式通常是这样:
- 首先,可以试图用git push origin branch-name推送自己的修改;
- 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
Access denied. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists¶
个人ssh-key是在,修改资料里面添加,设置的公钥,拥有所有权限
项目部署key是在项目设置中设置的,是用于部署用,只能clone与pull
Mac升级系统之后,使用git的时候,报如下错误¶
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
解决办法,执行如下命令:
xcode-select --install
git status 中文显示乱码¶
git config --global core.quotepath false
配置多个ssh-key¶
直接指定
# github ysara
# 将 .git/config 下url github.com, 修改成别名 github_ysara
# 比如 原地址是:git@github.com:username/haha.git,替换后应该是:git@github_ysara:username/haha.git.
Host github_ysara
HostName github.com
User xxx@qq.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/ysara
# github yangjinjie
Host github.com
User xx@qq.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa
vim¶
Vim¶
VIM是Linux功能最为强大的编辑器,它是由Unix下传统的文本编辑器VI发展而来的,VI M- Vi IMproved,VI的增强版,有彩色和高亮等特性,VIM和VI的区别可参看vi_diff.txt(:help vi_diff.txt),目前好多版本的Linux中调用VI是链接到VIM,作为Linux学习系统管理和编程基本工具,是要好好学习下VIM的使用基本操作命令,以下整理图书和网络上对于VIM的概述和操作命令的介绍,以备记录和查询之用:
VIM的编辑模式¶
VIM和VI一样都是有模式的编辑器,详细了解VIM模式,在VIM里输入:help mode,主要有如下几种模式:
一进入VIM就是处于命令模式,该模式下所有键盘输入都作为命令来对待,不会输入都文件里,其他任何模式都可以通过Esc键回到命令模式;
在命令模式中输入a、i、A、I、o、O等命令即可进入该模式,此时在状态列会有INSERT字样。在该模式下才能输入文字,按Esc键回到命令模式;
在命令模式中输入“:”(一般命令)、“/”(正向搜索)或“?”(反向搜索)进入该模式。此时屏幕左下角出现一个冒号提示符,等待输入命令,命令行模式下的命令输入完成后按Enter键才会执行,按Esc键回到命令模式;
在命令模式中通过v(按字符选择)、V(按行选择)、Ctrl+V(按块选择)进入该模式,在屏幕底部会有“-VISUAL-”、“-VISUAL LINE-”、“-VISUAL BLOCK-”等提示。在该模式下,通过移动光标选择文本,选中的文本将反白显示。按Esc键回到命令模式;
配置文件¶
~/.vimrc 进入配置文件
如果不知道vimrc文件在哪,可使用 :scriptnames
来查看
可以在配置文件里面写入如下内容
set nu #行号
set tabstop=4 #一个tab为4个空格长度
set ai #设置自动缩进
syntax on #高亮
vi命令¶
vi 进入vi而不读入任何文件
vi filename 进入vi并读入或新建指定名称的文件,并将光标置于第一行首
vi filename....filename 打开多个文件,依次进行编辑
vi +n filename 进入vi打开文件,并将光标置于第n行首。
# 示例
vim +13 filename # 打开文件直接就是 第 13行
vi + filename 进入vi打开文件,并将光标置于最后一行首 。
vi +/pattern filename 打开文件,并将光标置于第一个与pattern匹配的串处
vi -r filename 在上次正用vi编辑时发生系统崩溃,恢复filename
vim -R file 只读方式打开文件
vimdiff fileA fileB # 比较编辑两个不同的文件
保存:wq 或者 :x
存文件,并且退出vi
ZZ 保存并退出 (:x 表示仅在需要时保存,ZZ不需要输入冒号并回车) ZZ = wq
:w 保存当前文件
:w filename 写入指定文件,相当于另存为,但未退出vi(若未指定文件名则为当前工作的文件名)
可带行范围 :n1,n2 w filename
:w /tmp/1
既然没法存盘,不想放弃所做的所有修改,先临时存到/tmp/1
:saveas <path/to/file> 另存为 <path/to/file>
退出 :q
不作任何修改并退出vi (多屏时用qa)
:q! 放弃任何修改并退出vi
ZQ 无条件退出
编辑 :e filename
打开文件filename进行编辑[相当于退出当前的并打开另一个文件]?
:e!
放弃修改文件内容,重新载入该文件编辑
:e
重新载入【类似刷新】
:w !sudo tee %
以普通用户身份打开的vim,修改后以root身份保存
其他 ctrl + z / fg
暂时挂到后台/跳回编辑页面
:f 或 Ctrl-g
显示文件名,当前光标所在行的行号,总的行数,以及当前行所在文件中的百分比和当前光标所在的列的信息
:!command
暂时退出vi并执行shell指令,执行完毕后再回到vi
:r!command
将命令command的输出结果放到当前行【强大】
:sh
暂时退出vi到系统下,结束时按Ctrl + d则回到vi。
vim -x exam.txt
新编辑的文件加密,会提示输入密码
:X
文件加密,会提示输入密码
:map
列出当前已定义的映射
(如何定义映射?)
:jumps
列出关于你曾经跳转过的位置的列表,你最后一个跳转的位置被特别以一个”>”号标记
:marks
列出自定义的标记,包含VIM内部自定义的标识
:%!nl
要对包含空行的所有行进行编号(所有行之前插入行号^I)
:version
显示VIM版本信息,包含特性、编译方式及配置文件目录
:n1,n2 w ! command
将文件中n1行至n2行的内容作为command的输入并执行之,若不指定n1,n2,则表示将整个文件内容作为command的输入【注意空格】例如 :1,4 w! grep tom
:options
打开一个新窗口, 在该窗口的最开头的注释下面是一个选项列表(do what?)
:r filename
在光标所在处插入一个文件的内容(read)
可加入数字 :nr filename
:f filename
改变编辑中的文件名(file)
相当于复制了一个文件,执行这个命令后,新编辑不会应用于原文件
gf
vim特殊打开文件的方法,打开光标所在处的 word 为名的文件,当然,这个文件要在当前目录内,否则会创建新文件
字符 默认1,可加n
空格键 向右移动一格
h 光标左移一个字符[回退键Backspace]
l 光标右移一个字符[空格键Space]
BP: hkjl作为移动光标【important,同方向键,建议使用这四个】
单词 默认1,可加n
w 光标跳到下个word的第一个字母 [常用]
W 移到下一个字的开头,忽略标点符号
b 光标回到上个word的第一个字母
B 移到前一个字的开头,忽略标点符号 BACK
e 光标跳到下个word的最后一个字母
E 移到下一个字的结尾,忽略标点符号 END
行
0
移到当前一行的开始[Home]
数字零,到行头
$ 移到当前一行的最后[End]
^ 命令将光标移动到当前行的第一个非空白字符上
g_ 到本行最后一个不是blank字符的位置
k 光标上移一行Ctrl+p
j 光标下移一行Ctrl+n
Enter 光标下移一行
n+ 光标下移n行【按上档键 数字shift +】
n- 光标上移n行
G 移到文件的最后一行
nG或者:n 移到文件的第n行???
gg 移动到文档的开始
[[ 文件开始位置——开始行
]] 文件结束位置——末尾行
H 光标移至屏幕顶行 HEAD
光标定位在显示屏的第一行
M
移到屏幕的中间行开头 Middle
光标定位在显示屏的中间
L
移到屏幕的最后一行? LAST
光标定位在显示屏的最后一行
( 光标移至句首
) 光标移至句尾
段落
{ 移到段落的开头
} 移到下一个段落的开头
% 匹配括号移动,包括 (, {, [.(陈皓注:你需要把光标先移到括号上)
跳转到与之匹配的括号处
* 和 #
匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词(*是下一个,#是上一个)
zf
折叠(需加方向键)
zo
展开(空格也可以展开)
可以zf进行折叠, 用zo打开折叠,也可以方向键向右打开折叠,zc 关闭折叠(只要在被折叠的块中的任一个语句就行)???
屏幕
Ctrl+u 向文件首翻半屏up
Ctrl+d 向文件尾翻半屏down
Ctrl+f 向文件尾翻一屏 forward (fact整屏去两行)
Ctrl+b 向文件首翻一屏back (fact整屏去两行)
CTRL-] 跳转到当前光标所在单词对应的主题
CTRL-O 回到前一个位置
zz 命令会把当前行置为屏幕正中央(z字取其象形意义模拟一张纸的折叠及变形位置重置)
zt 命令会把当前行置于屏幕顶端(top)
zb 命令会把当前行置于屏幕底端(bottom)
50% 光标定位在文件的中间
` 反引号 跳转到最近光标定位的位置(只能记忆最近两个位置)
i 在光标前开始插入字符 insert
I 在当前行首开始插入字符
a 在光标位置后开始加字 append
A 在光标所在行的最后面开始加字
o 在光标下加一空白行并开始加字 open
O 在光标上加一空白行并开始加字
r 替换当前字符
R 替换当前字符及其后的字符【当前及其后字符被覆盖】
s 默认删除光标所在字符,输入内容插入之
= xi
S 默认删除当前行内容,输入内容作为当前行新内容 = dd+o
字符
nx 删除由光标位置起始后的n个字符(含光标位置)
x =dl(删除当前光标下的字符)
nX 删除由光标位置起始前的n个字符(含光标位置)
X =dh(删除当前光标左边的字符)
d0 删至行首(数字零)
d$ 删至行尾
dfa 表示删除从当前光标到光标后面的第一个a字符之间的内容
D 代表d$(删除到行尾的内容)
C 代表c$(修改到行尾的内容)
单词
ndw 删除光标处开始及其后的n-1个字
ndb 删除光标处开始及其前的n-1个字
diw 删除当前光标所在的word(不包括空白字符),意为Delete Inner Word 两个符号之间的单词
daw 删除当前光标所在的word(包括空白字符),意为Delete A Word
行
ndd 删除当前行及其后n-1行
:n1,n2 d 将 n1行到n2行之间的内容删除
dG 删除当前行至文件尾的内容
dgg 删除当前行至文件头的内容
d回车 删除2行【包括光标一行】
【删除就是剪切,它会覆盖之前复制的内容】 删除并进入输入模式
cw 删除当前字,并进入输入模式 【很好用,快速更改一个单词】 相当于dw+i
ncw 删除当前字及其后的n-1个字,并进入输入模式\
修改指定数目的字
cc 删除当前行,并进入输入模式
ncc 删除当前行及其后的n-1行,并进入输入模式
guw 光标下的单词变为小写
gUw 光标下的单词变为大写
xp 左右交换光标处两字符的位置
ga 显示光标下的字符在当前使用的encoding下的内码
复制
nyl 复制n个字符(也可nyh)
yw 复制一个单词
y0 表示拷贝从当前光标到光标所在行首的内容
y$ 复制从当前位置到行尾
yf a表示拷贝从当前光标到光标后面的第一个a字符之间的内容
yG 复制从所在行到最后一行
nyy 将光标所在位置开始的n行数据复制暂存
ctrl + v 方向 y 复制选择的很多行:先使用V进入visual模式,然后j向下移动到你想复制的行为止,然后y
黏贴
p复制暂存数据在光标的下一行
P复制暂存数据在光标的上一行
:n1,n2 co n3将n1行到n2行之间的内容拷贝到第n3+1行【n3行的下一行】
:n1,n2 m n3将n1行到n2行之间的内容移至到第n3行下
J 把下一行的数据连接到本行之后,多一空格
~ 改变当前光标下字符的大小写
/pattern 从光标开始处向文件尾搜索pattern
?pattern 从光标开始处向文件首搜索pattern
n 在同一方向重复上一次搜索命令
N 在反方向上重复上一次搜索命令??
# 向上完整匹配光标下的单词,相当于?word
* 向下完整匹配光标下的单词,相当于/word
% 查找对应的( [ {匹配
nfx 在当前行查找光标后第n个x(一般直接fx)
:s/p1/p2/g
将当前行中所有p1均用p2替代
无g,则只替换第一个
:s/p1/p2/c
查找替换要求确认
:n1,n2s/p1/p2/g
将第n1至n2行中所有p1均用p2替代
:%s/p1/p2/g
全局,使用p2替换p1
:%s/p1/p2/gc
替换前询问
:n,$s/vivian/sky/
替换第n行开始到最后一行中每一行的第一个vivian为sky,n为数字
:.,$s/vivian/sky/g
替换当前行开始到最后一行中每一行所有vivian为sky
:s/vivian\//sky\//
替换当前行第一个vivian/为sky/,可以使用\作为转义符
:1,$s/^/some string/
在文件的第一行至最后一行的行首前插入some string
:%s/$/some string/g
在整个文件每一行的行尾添加some string
:%s/\s\+$//
去掉所有的行尾空格,“\s”表示空白字符(空格和制表符),“\+”对前面的字符匹配一次或多次(越多越好),“$”匹配行尾(使用“\$”表示单纯的“$”字符)
:%s/\(\s*\n\)\+/\r/
去掉所有的空白行,“\(”和“\)”对表达式进行分组,使其被视作一个不可分割的整体
:%s!\s*//.*!!
去掉所有的“//”注释
:%s!\s*/\*\_.\{-}\*/\s*!!g
去掉所有的“/* */”注释
:%s= *$==
将所有行尾多余的空格删除(没看懂)
:g/^\s*$/d
将所有不包含字符(空格也不包含)的空行删除
r 替换当前字符
R 替换当前字符及其后的字符,直至按ESC键
命令说明:
:[range]s/pattern/string/[c,e,g,i]
? range 指的是範圍,1,7指從第一行至第七行,1,$ 指從第一行 至最後一行,也就是整篇文章,也可以% 代表 (目前編輯的文章)。
? pattern 就是要被替換掉的字串,可以用regexp 來表示。
? string: 將pattern 由 string 所取代。
? c: confirm,每次替换前会询问。
? e: 不显示error。
? g: globe,不詢問,整行替換。
? i: ignore 不分大小写。
? g 大概都是要加的,否則只会替换每一行的第一个符合字串。
? 可以合起來用
其中s为substitute,%表示所有行,g表示globa
:r filename
将指定文件的内容读入光标所在行下
:args
显示编辑名单中的各个文件名列表
:n
切换到下一个文件
:N
切换到上一个文件
:rew
回到首个文件
:e#
读入编辑名单内的前一个文件
:e file
读入另一个文件进vi(此文件可不在编辑名单内),若原文件经修改还没有存档,则应先以: w 存档。
:e! file
强迫读入另一个文件进入vi,原文件不作存档动作。
:e
放弃当前一切修改,重新载入文件
:bn 和 :bp
你可以同时打开很多文件,使用这两个命令来切换下一个或上一个文件
:files或 :buffers或 :ls
会列出目前 buffer 中的所有文件
. 重复前一指令
u
取消前一指令undo
:u也行,一般不用,操作太多
Ctrl + r
恢复【只对u有效】redo
Ctrl + l
刷新屏幕显示
Ctrl+v
然后 ctrl+A是^A
Ctrl+I是\t
输入特殊字符
Ctrl+v
然后用j、k、l、h或方向键上下选中多列,之后 I I a A r x等,最后按esc,生效
Vim列操作
V
进入visual模式【ESC】退出
多行选中模式
【set可以简写为se】
附录:set的所有选项
all
列出所有选项设置情况
常用
:se number
简写 :se nonum
显示文件的行号
简写:se nu se nonu
:se list
显示制表位(Ctrl+I)和行尾标志($) se nolist
:se wrap
将超出屏幕行分多行显示 se nowrap
打开/关闭换行
:se paste
vim贴代码的时候,格式乱掉
先之后,黏贴,就不会乱掉
:set ignorecase
设置在搜索中忽略大小写
:set hlsearch
高亮显示搜索结果
:nohls
可以取消高亮
其他
term
设置终端类型
report
显示由面向行的命令修改过的数目
terse
显示简短的警告信息
warn
在转到别的文件时若没保存当前文件则显示NO write信息
nomagic
允许在搜索模式中,使用前面不带“\”的特殊字符
nowrapscan
禁止vi在搜索到达文件两端时,又从另一端开始
mesg
允许vi显示其他用户用write写到自己终端上的信息
:set autowrite
设置自动存盘
:set backup
设置备份
:set backupext=.bak
设置备份文件名后辍,默认原文件名~
:syntax enable
打开彩色
:set autoindent
让Vim在开始一个新行时对该行施以上一行的缩进方式
:set showcmd
在Vim窗口的右下角显示一个完整的命令已经完成的部分
:set key=
去掉文件加密
:set ai
设置每行起始位置(以光标当前位置为起始)
:set noai?
取消行起始位置设定
:set incsearch
键入目标字符串的过程中Vim就同时开始了搜索工作
:set ts=4
:set expandtab
一个Tab自动转换成4个空格
分屏启动Vim
vim -On file1 file2 ...
使用大写的O参数来垂直分屏
vim -on file1 file2 ...
使用小写的o参数来水平分屏。
注释: n是数字,表示分成几个屏【只会出现n屏,后面跟多了文件不会显示】
使用VIM的帮助 :help split
分屏
Ctrl+W s
上下分割当前打开的文件
Ctrl+W v
左右分割当前打开的文件
:sp filename
上下分割,并打开一个新的文件
:vsp filename
左右分割,并打开一个新的文件
关闭分屏
Ctrl+W c
关闭当前窗口【关闭不了最后一个】
Ctrl+W q
关闭当前窗口,如果只剩最后一个了,则退出Vim
:qa
退出vim,并关闭所有屏
:wqa
保存所有分屏并关闭
移动光标
Ctrl+W l
把光标移到右边的屏
Ctrl+W h
把光标移到左边的屏中
Ctrl+W k
把光标移到上边的屏中
Ctrl+W j
把光标移到下边的屏中
Ctrl+W w
把光标移到下一个的屏中
要在各个屏间切换,只需要先按一下Ctrl+W
<C-w><dir> : dir就是方向,可以是 hjkl 或是 ←↓↑→中的一个,其用来切换分屏
移动分屏
Ctrl+W L
向右移动
Ctrl+W H
向左移动
Ctrl+W K
向上移动
Ctrl+W J
向下移动
这个功能还是使用了Vim的光标键,只不过都是大写。当然了,如果你的分屏很乱很复杂的话,这个功能可能会出现一些非常奇怪的症状。
屏幕尺寸
下面是改变尺寸的一些操作,主要是高度,对于宽度你可以使用Ctrl+W <或是>,但这可能需要最新的版本才支持
当同时打开几个文件时,按 _ 使当前窗口最大化
Ctrl+W =
让所有的屏都有一样的高度
Ctrl+W +【shift +=键】
增加高度
<C-w>+ (或 <C-w>-) 增加尺寸
扩大窗口
Ctrl+W -【shift –键】
减少高度
缩小窗口
<C-w>_ (或 <C-w>|)
最大化尺寸 (<C-w>| 垂直分屏)
关闭分割窗口可以用:close 其实用:q也行
Vim拥有一个细致全面的在线帮助系统,进入帮助
启动帮助
<HELP>键 (如果键盘上有的话)
<F1>键(如果键盘上有的话)
:help<回车>
:help command<回车>
例如:help w <回车>
:help insert-index <回车>
退出帮助
:q <回车>
常见问题及应用技巧
1) 在一个新文件中读/etc/passwd中的内容,取出用户名部分
vi file
:r /etc/passwd 在打开的文件file中光标所在处读入/etc/passwd
:%s/:.*//g 删除/etc/passwd中用户名后面的从冒号开始直到行尾的所有部分
:3r /etc/passwd 这是在指定的行号后面读入文件内容
另外一种方法删掉文件中所有的空行及以#开始的注释行
#cat squid.conf.default | grep -v '^ | grep -v '^#'
2) 在打开一个文件编辑后才知道登录的用户对该文件没有写权,不能存盘
vi file
:w /tmp/1 既然没法存盘,不想放弃所做的所有修改,先临时存到/tmp/1
:20,59w /tmp/1 或者仅仅把第20到59行之间的内容存盘成文件/tmp/1
3) 用VI编辑一个文件,但需要删除大段大段的内容
vi file
Ctrl+G 把光标移到需要删除的行的处按ctrl+G显示行号,再到结尾处再按Ctrl+G.
:23,1045d 假定两次行号为23和1045,则把这几间的内容全删除
也可以在开始和结束两行中用ma,mb命令标记后用:'a,'bd删除.
4) 在整个文件或某几行中在行首或行尾加一些字符串
vi file
:3,$s/^/some string / 在文件的第一行至最后一行的行首前插入some string
:%s/$/ some string/g 在整个文件每一行的行尾添加 some string
:%s/string1/string2/g 在整个文件中替换string1成string2
:3,7s/string1/string2/ 仅替换文件中的第三到七行中的string1成string2
Note: s为substitute,%表示所有行,g表示global
5) 同时编辑两个文件,在两个文件中拷贝剪贴文本
vi file1 file2
yy 同时打开两个文件,在文件1的光标所在处拷贝所在行
:n 切换到文件2 (n=next)
p 在文件2的光标所在处粘贴所拷贝的行
:N 切换回文件1
6) 替换文件中的路径
:%s#/usr/bin#/bin#g 把文件中所有路径/usr/bin换成/bin
或者用
:%s//usr/bin//bin/g 在'/'前用符号指出'/'是真的单个字符'/'
7) 用 vi 多行注释
如果要给多行程序作注释,一个笨办法就是 插入 # ,然后用 j 跳到下一行用 . 命令,重复上个命令。如果要注释几百行,这样的方法恐怕太愚蠢了。一个聪明的办法是:
:.,+499 s/^/#/g
若需全文的行首插入可用以下命令
:%s/^/#/g
- 快速移动
- 不要两次键入同样的东西
- 错误修复
- 经常需要编辑不止一个文件
- 协同作业
- 文本是结构化的
- 养成习惯
1. 快速移动
在文本中随意漫游是非常常见的操作。所以高效编辑的第一要义是学习如何能够在文本中快速移动,准确定位。有三个步骤可以使你学到你需要的技巧:
当你编辑文件的时侯,留意一下你经常要重复进行的操作是什么;
练习使用这些命令,直到你的手指可以不假思索地运用自如;
浏览一下参考手册你就会发现关于tag的主题。文档会告诉你如何使用这一功能跳转到函数的定义处。
2. 不要两次键入同样的东西
借助":s"替换命令或者"."重复命令等快捷操作代替重复操作。
3. 错误修复
缩写功能来纠正。一些例子是::abbr Lnuix Linux。
语法高亮机制。
4. 经常需要编辑不止一个文件
创建tag文件。
将光标定位在你要查看其原型的函数名上,然后按下"[I"命令, Vim将会显示include文件中匹配这个函数名的一个清单。
预览标签机制会打开一个特殊的预览窗口,并且使光标仍然停留在你当前所在的位置,在预览窗口中的文本列出了当前光标所在处的函数的声明(有些可能不是声明)将当前光标移动到另一个函数名上, 停留几秒钟, 预览窗口中的内容就会变成是关于新函数名的声明。
5. 协同作业
Unix的哲学是使用独立的小程序,每个小程序做一项专门的任务,并且把它作好,将它们的工作整合到一起来完成一个复杂的任务。
Vim的做法是将一些分散的小程序整合起来,产生强大的处理能力,在这方面Vim还有待在将来进一步提高。
在编辑器领域, emacs是这方面的一个典范(有人甚至说它是一个能编辑文本的操作系统)
6.文本是结构化的
加速你的编辑-编译-修改的周期。
":make" 命令编译程序项目,捕获编译的错误/警告并允许你直接跳转到引起这一错误/警告的程序行上去;
`errorformat'选项告诉Vim你的编译器将生成何种格式的错误信息,以便于它能识别;
写一些自定义的宏,如要跳转到manual 帮助文档, 这对于查看交叉索引是一种简捷有效的办法;
使用上面的三项原则可以对付任何形式的结构化文本。
7. 养成习惯
绝大多数人只需要学习其中的10-20%的命令就足以应付它们的工作了。
建立适合自己的命令集,不时地回顾以往所做的事, 看看是不是可以自动完成一些重复的工作。
不断地重复练习你的解决方案直到你的手指可以条件反射地自动完成,从而达到你所期望的境界。
不要一次尝试太多的东西,一次做一件事并多做几次会好得多。
对于不经常的操作,最好记下你的处理步骤以备将来不时之需。
不管怎样,只要目标明确。你就能找到让你的编辑变得更加高效的办法。
Vim图解¶

vim图解
vim技巧¶
vim配置¶
k-vim
https://github.com/wklken/k-vim
vim 编辑器自动注释,可以参考k-vim中vimrc的配置,示例如下
call append(line("."), "\##############################################")
call append(line(".")+1, "\# File Name: ".expand("%"))
call append(line(".")+2, "\# Author: yjj")
call append(line(".")+3, "\# qq: 493535459")
call append(line(".")+4, "\# Created Time: ".strftime("%c"))
call append(line(".")+5, "\###########################################")
vim常用技巧¶
:g/^\s*$/d 删除空行
:set nu :set nonu
G 移动到文件的最后一行
gg 移动到文件的第一行(首行)
ngg 移动到文件的第n行
yy 复制当前整行
p 粘贴
np 粘贴n次,n次数
dd 剪切当前一行
ndd 剪切接下来的多少行,包括 光标所在行
dG shanchu 剪切当前行到文件结尾
D/d$ 剪切光标后位置到行尾
x 删除光标位置字符
u 撤销上一次操作
:noh 消除高亮
ctrl x 减光标所在位置的数字
ctrl a 加光标所在位置的数字
vim打开文件乱码处理方式¶
Linux下wget中文编码导致的乱码现象,由于所打开的文件采用的汉字编码方式不同,一般有utf-8 和gb2312两种编码方式
修改系统的配置文件/etc/vimrc即可:
vim /etc/vimrc
#加入下面语句即可:
set fileencodings=utf-8,gb2312,gbk,gb18030 //支持中文编码
set termencoding=utf-8
set fileformats=unix
set encoding=prc
HTTPie¶
Jupyter¶
python3
python3 -m pip install --upgrade pip
python3 -m pip install jupyter
运行 notebook
jupyter notebook
PPTP和OpenVPN有什么区别¶
tools¶
Visual Studio Code¶
常用插件¶
Visual Studio Code Settings Sync¶
使用插件同步vscode配置
其实就是借助的GitHub Gist
,
Gist可以设置为public
和secret
,
该插件会创建一个secret
的gist, 所以设置里面有key之类的,
也不是很要紧
使用技巧¶
文件默认换行符设置¶
在vscode设置, 文件默认换行符号
// The default end of line character. Use \n for LF and \r\n for CRLF.
// LF 为 "\n"
// Windows 使用 CRLF 作为换行符
"files.eol": "\n"
设置默认使用的 terminal¶
# windows下默认为 powershell
# 设置示例
"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",
扩展Visual Studio Code¶
Yo Code - Extension Generator¶
npm install -g yo generator-code
官方镜像速度慢, 可以使用淘宝的NPM镜像
添加别名
alias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"
# 可以添加到 .bashrc 或 .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc
使用cnpm安装
cnpm install -g yo generator-code
yo code
第一个扩展插件¶
yo code
选择 TypeScript
类型插件, 按要求输入信息.
File
->Open Folder
- 按
F5
或者,点击Debug
, 点击Start
- 会自动打开一个新的
VS Code
窗口, 并支持该扩展插件 - 按
⇧⌘P
运行命令Hello World.
- 此时你的扩展插件会显示一条
Hello World
的信息
插件目录结构
运行之后, 插件目录结构如下
.
├── .gitignore
├── .vscode // VS Code integration
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .vscodeignore
├── README.md
├── src // sources
│ └── extension.ts // extension.js, in case of JavaScript extension
├── test // tests folder
│ ├── extension.test.ts // extension.test.js, in case of JavaScript extension
│ └── index.ts // index.js, in case of JavaScript extension
├── node_modules
│ ├── vscode // language services
│ └── typescript // compiler for typescript (TypeScript only)
├── out // compilation output (TypeScript only)
│ ├── src
│ | ├── extension.js
│ | └── extension.js.map
│ └── test
│ ├── extension.test.js
│ ├── extension.test.js.map
│ ├── index.js
│ └── index.js.map
├── package.json // extension's manifest
├── tsconfig.json // jsconfig.json, in case of JavaScript extension
└── vsc-extension-quickstart.md // extension development quick start
- extension manifest reference
- contribution points
- 每一个VS Code插件都有一个
package.json
文件描述自身及功能 - VS Code在启动的时候会读取该文件, 并立刻启用每一个
contributes
示例
{
"name": "myFirstExtension",
"description": "",
"version": "0.0.1",
"publisher": "",
"engines": {
"vscode": "^1.5.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:extension.sayHello"
],
"main": "./out/src/extension",
"contributes": {
"commands": [{
"command": "extension.sayHello",
"title": "Hello World"
}]
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"typescript": "^2.0.3",
"vscode": "^1.5.0",
"mocha": "^2.3.3",
"@types/node": "^6.0.40",
"@types/mocha": "^2.2.32"
}
}
注意: JavaScript扩展插件不需要scripts
, 因为不需要编译.
这个特殊文件描述了
- 一个命令面板的入口, 标签为
"Hello World"
, 它会调用"extension.sayHello"
- 当
"extension.sayHello"
被调用的时候, 会请求加载活动事件 - 该事件是使用JavaScript编写的
"./out/src/extension.js"
文件
注意: VS Code不会在启动的时候就加载扩展代码. 扩展插件必须描述, 通过activationEvents
性能, 在某些条件下被激活(加载).
生成的扩展插件代码在extension.ts
(或 extension.js
,
JavaScript扩展插件)里面
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "my-first-extension" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
var disposable = vscode.commands.registerCommand('extension.sayHello', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World!');
});
context.subscriptions.push(disposable);
}
- 每个插件都需要有一个
activate()
函数, VS Code将在package.json
里面描述的activationEvents
发生的时候, 调用一次. - 如果插件使用OS资源, 可以定义一个
deactivate()
函数, 当VS Code关闭的时候, 会调用该函数, 执行清扫工作. - 这个插件导入了
vscode
API, 并注册了一个命令, 调用命令"extension.sayHello"
, 显示一条"Hello World"
的信息.
注意:package.json
里面的contributes
会在命令面板增加一个入口, 在.ts/.js
里面实现该命令"extension.sayHello"
注意: 对于TypeScript
插件, VS Code会在每一次执行的时候加载生成的out/src/extension.js
文件.
打包发布¶
zsh¶
Mac 升级zsh¶
Mac 系统自带了 Zsh, 一般不是最新版,如果需要最新版可通过 Homebrew 来安装
brew install zsh
可通过 zsh --version 命令查看 zsh 的版本
修改默认 Shell
在 /etc/shells 文件中加入如下一行
/usr/local/bin/zsh
然后运行命令
chsh -s /usr/local/bin/zsh
插件¶
autojump
git
colored-man
colorize
copydir
command-not-found
history
sublime
brew
配置方法:
➜ ~ grep "^plugins" ~/.zshrc
plugins=(git autojump colored-man colorize copydir history command-not-found)
使用 ** 作为下级目录的通配符¶
ls **/*.pyc
foo.pyc bar.pyc lib/wibble.pyc
rm **/*.pyc
ls **/*.pyc
在文件筛选中使用匹配模式¶
ls *.(py|sh)
在文件筛选中使用修饰符,如:(@) 限制只匹配符号链接:¶
ls -l *(@)
使用制表符TAB来自动完成,如man的时候:¶
man zsh[TAB]
也可以用制表符来自动补全命令选项:¶
python -[TAB]
或者在kill的时候自动完成:¶
kill Dock[TAB]
在制表符自动补全时,可以使用光标键(上下左右等):¶
man zsh[TAB][DOWN][RIGHT][LEFT][UP]
自动修改错误的输入:¶
$ pythn -V
zsh: correct 'pythn' to 'python' [nyae]? y
Python 2.7.1
使用r来重复上一条命令,可以带替换方式!¶
touch foo.htm bar.htm
mv foo.htm foo.html
r foo=bar
mv bar.htm bar.html
高可定制性的提示符,使用RPROMPT居然可以设置提示符在右边!¶
RPROMPT="%t"