Notes

由于 GitBook 生成 html 太慢, 放弃使用 GitBook , 文档使用 reStructuredText 编写, 使用 Sphinx 生成静态文件.

在线阅读

最新文档

注意

原GitHub Pages(使用GitBook生成)可继续访问, 但不再更新, 地址 https://yangjinjie.github.iohttps://docs.yangjinjie.xyz

离线阅读

本地使用Sphinx生成静态文档

# 前提, 安装Sphinx
git clone --depth=1 https://github.com/yangjinjie/notes.git notes_notes
cd notes_notes
make html
# 访问生成的html即可

项目简介

所有内容可在 GitHub notes 仓库中搜索

tree
.
notes
├── bigdata
├── ci
│   ├── TeamCity
│   └── jenkins
├── cloud
│   ├── ~~~
├── db
│   ├── ~~~
├── html,css,JavaScript
├── microservice
├── network
├── os
│   ├── 各系统相关
│   └── ...
├── python
├── ruby
├── service
│   ├── 各类服务
│   └── ...
├── shell
├── tips
├── tmp
│   └── 未分类
└── tools
    ├── 各类工具, 比如vim,git
    └── ...

Table of content

HTML,CSS,JavaScript

ExtJS

Ext JS

项目管理 创建项目:指定SDK,指定仅生成Classic项目

sencha -sdk ~/ext-6.2.0/ generate app classic BeApp ./BeApp

项目结构

bootstrap.* 仅开发环境使用,微加载文件
ext/        仅开发环境使用,库文件夹
build/      仅开发环境使用,构建文件夹

以上文件不需要进行代码版本管理,可以通过install + build命令重建。

…. code-block:: shell

sencha app install –framework=~/ext-6.2.0/ sencha app build sencha app upgrade ~/ext-6.2.0

项目编辑 添加模块: 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

预览地址:http://localhost:1841/

构建项目: 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

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 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。

HTML

总结
  1. 一套规则,浏览器认识的规则
  2. 开发者
    • 学习HTML规则
    • 开发后台程序
      • 写HTML文件(充当模板的作用) *****
      • 数据库获取数据,然后替换到HTML文件的指定位置(web框架)
  3. 本地测试
    • 找到文件路径,直接使用浏览器打开
    • PyCharm打开测试
  4. 编写HTML文件
    • doctype对应关系
    • html标签,标签内部可以写属性 (整个html文件只有一个html标签)
    • 注释:
  5. 标签分类
    • 自闭合标签( <meta charset="UTF-8"> ) 自闭合标签可以使用 <br/><br> 推荐在>前面写/
    • 主动闭合标签( <title>长廊月</title> )
  6. head标签中
    • <meta> -> 编码,跳转,刷新,关键字,描述,IE兼容
    • <meta http-equiv="X-UA-Compatible" content="IE=IE9;IE=IE8;"/>
  7. 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>
网页特殊符号
&nbsp; 空格
&gt;
&lt;
标签
meta

参考链接

提供有关页面的元信息,例如: 页面编码,刷新,跳转,针对搜索引擎和更新频度的描述和关键词

页面编码

指定编码

<meta http-equiv="content-type" content="text/html";charset="UTF-8">
刷新和跳转
<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">
描述

例如 : 作者

X-UA-Compatible
<meta http-equiv="X-UA-Compatible" content="IE=IE9;IE=IE8;"/>
Title

网页头部信息

style
script
p

表示段落,默认段落之间是有间隔的

br

换行 <br/>

自闭合标签,后面位置 > 可以写上 / 推荐写

a
  1. 锚 href=“#某个标签的ID” 标签的ID不允许重复
  2. target 属性,_black表示在新的页面打开
  3. 菜单跳转
<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

html-03-a

使用title属性,当鼠标悬停图片上的时候,会显示title属性

<img src="1.jpg" style="height: 200px;width: 200px;" alt="风景" title="大风景">
html-04-a

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

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>
checkbox
<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>
select 下拉框

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

html-02

textarea
<textarea name="" id="" cols="30" rows="10"></textarea>

多行文本,name属性

列表
  • 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

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

html-06-li

表格
  • table
    • thead
      • tr
        • th
    • tbody
      • tr
        • td
  • 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有 theadtbody

<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

html-07-table

label

用于点击文件,使得关联的标签获取光标

<body>
    <label for="username">用户名: </label>
    <input id="username" type="text" name="user">
</body>
html-08-label

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">&nbsp;&nbsp;&nbsp;&nbsp;: </label>
        <input id="pwd" type="password" name="pwd"/>
    </filedset>
</body>

CSS

总结
  1. 在标签上设置style属性
    • background-color: #2459a2;
    • height: 48px;
  2. 编写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,绝对生效)
  3. css样式也可以写在单独的文件中
    • <link rel="stylesheet" href="xxx.css"/>
  4. 注释
    • /*   */

设置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-选择器

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-标签选择器

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-层级选择器

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-属性选择器

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

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

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

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

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

css-11-opacity

z-index

层级顺序,看opacity中的示例

数字越大越在上层

overflow: hidden,auto
  • hidden 内容会被修剪,并且其余内容是不可见的。
  • auto 如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。
<body>
    <!-- 内容被修剪,浏览器会显示滚动条以便查看其余的内容 -->
    <div style="height: 200px;width: 200px; overflow: auto;">
        <img src="1.jpg"/>
    </div>

    <div style="height: 200px;width: 200px;overflow: hidden;">
        <img src="1.jpg"/>
    </div>
</body>
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-返回顶端

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)
查找元素
直接查找
  • 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-03-get

js-04-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实现的一些效果较慢.
  • 注释
    • 单行注释 // 注释内容
    • 多行注释 /* 注释内容 */
  • 基本数据类型
    • 数字
    • 字符串
      • 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
布尔类型
  • true
  • false
== 值相等
!=

=== 值和类型都相等
!==
&&  and
||  or
数组

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的编码和解码方法抛出
eval

JavaScript中的eval是Python中eval和exec的合集,既可以编译代码也可以获取返回值。

  • eval()
  • EvalError 执行字符串中的JavaScript代码
正则表达式
定义正则表达式
  • /…/ 用于定义正则表达式
  • /…/g 表示全局匹配
  • /…/i 表示不区分大小写
  • /…/m 表示多行匹配

JS正则匹配时本身就是支持多行,此处多行匹配只是影响正则表达式^和$,m模式也会使用^$来匹配换行的内容)

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个组内容;
                                          $&:当前匹配的内容;
                                          $`:位于匹配子串左侧的文本;
                                          $':位于匹配子串右侧的文本
                                          $$:直接量$符号
setInterval

定时执行某个函数

<script>
    // 每3秒触发一次弹窗
    setInterval("alert(123);",3000)
</script>
滚动字幕
<body>
    <ul>
        <li id="l1">欢迎xxx莅临指导</li>
    </ul>
</body>
js-01-滚动字幕

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

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引擎“预编译”时进行。

更多:http://www.cnblogs.com/wupeiqi/p/5649402.html

jQuery

JavaScript库

jQuery

jQuery API 速查表

总结
  • 调用(两种方式)
    • 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')
筛选
children
$('#i1').children()
parent

$(this).parent()
siblings

兄弟

$(this).siblings()
find

子子孙孙中查找

$(this).find()
操作元素
样式操作
addClass
removeClass
toggleClass
属性操作
attr
  • 传一个参数,获取属性
  • 传两个参数,设置属性
$(..).attr('n')
$(..).attr('n','v')
removeAttr
$(..).removeAttr('n')
prop

专门用于CheckBox,radio

$(':checkbox').prop('checked');       // 获取值
$(':checkbox').prop('checked',false); // 设置值
文档处理
append
prepend
after
before
remove
empty
clone
技巧
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')

JS

require

javascript module.exports

book

看过的一些书籍的翻译或笔记

Linux From Scratch

Linux From Scratch (LFS), 从源代码一步一步构建自己的Linux系统.

版本更新情况

记录时, 本书最新版本

LFS Stable Version 8.1 Release
    Bruce Dubbs - 2017/09/01

Version 8.1

II 构建准备
宿主系统准备
Introduction

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被设计为在一个会话中构建, 整个过程中不会关机. 这并不意味着必须一次完成, 存在的问题是从不同的位置重新开始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文件

在分区上创建文件系统

ext4:

mkfs -v -t ext4 /dev/<xxx>

swap:

mkswap /dev/<yyy>
设置 $LFS 变量

本书中, 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 /
添加LFS用户
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
关于 SBUs

Standard Build Unit (SBU), 标准构建单元, 用来测量构建安装单个包的时间.

SBU测量工作如下, 本书第五章第一个包编译的是Binutils, 编译该包所花的时间将作为SBU, 其他包编译所需时间可以根据该包得出一个相对值.

比如, 考虑到一个包编译时间为 4.5 SBUs. 这意味着, 如果系统花了10分钟编译这个包, 那么编译示例里面的包将需要45分钟, 幸运的是. 大多数其他包的编译时间少于Binutils.

一般来说, SBUs 并不完全准确, 因为编译时间会受很多因素影响, 包括宿主机 GCC 版本.

针对多核计算机, 编译的时候, 可以指定多进程编译, 使用

export MAKEFLAGS='-j 2'

或者, 编译的时候使用

make -j2
关于测试组件

很多包会提供对应测试组件, 运行测试组件, 可以知道包的编译情况. 有一些包编译之后, 进行测试是很有必要的, 比如: GCC, Binutils, Glibc等, 测试过程可能会花费不少时间, 但是强烈推荐进行测试.

构建一个临时系统
介绍
通用编译指令

对包的构建, 做一些约定.

有一些包在构建之前, 需要打补丁, 但只有要规避某些问题的时候才需要打补丁. 补丁通常在本章和下章使用. 因此, 不用担心指令执行之后, 似乎下载的补丁不见了. 在应用补丁的时候, 出现的警告消息也不用担心, 补丁仍然应用成功.

大多数软件编译过程中, 屏幕上会不断滚动一些警告信息, 这些信息可以忽略, 这些警告一般都是提示某些标准将被弃用等, 但并不是无效. C标准经常变化, 而有些包还是使用的旧的标准. 这不是问题, 只是一些警告的提示.

再次检查 LFS 环境变量:

echo $LFS

两个重要项

构建指令假设宿主机依赖, 包括软链接都设置正确:

  • 使用的shell为bash
  • sh 链接到 bash
  • /usr/bin/awk 链接到 gawk
  • /usr/bin/yacc 链接到 bison 或执行 bison 的脚本.

强调构建过程

  1. 将所有源代码和补丁放置在chroot环境可访问的目录, 例如 /mnt/lfs/sources/. 不要将源代码放在 /mnt/lfs/tools/.
  2. 切换到源代码路径
  3. 对于每个包
    1. 使用tar命令, 解压, 在第5章, 解压的时候确保你使用的是lfs用户
    2. 切换到解压后的包路径
    3. 跟随本书的指令, 进行编译
    4. 返回源代码路径
    5. 除非有其他指示, 否咋删除解压目录
Binutils-2.29 - Pass 1

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.”

GCC-7.2.0 - Pass 1

包含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.”

Linux-4.12.7 API Headers

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.”

Glibc-2.26

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++-7.2.0

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 the gcc-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.”

Binutils-2.29 - Pass 2

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 the LIB_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
安装基本系统软件
Linux-4.12.7 API Headers

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, 进入到解压后的目录

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 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
Contents of Linux API Headers

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
Man-pages-4.12

The Man-pages package contains over 2,200 man pages.

Approximate build time: less than 0.1 SBU Required disk space: 27 MB

Installation of Man-pages

Install Man-pages by running:

make install
Contents of Man-pages

Installed files: various man pages

Short Descriptions

man pages Describe C programming language functions, important device files, and significant configuration files

问题记录
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)

Previous Releases Downloads

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

使用Docker

镜像地址

https://hub.docker.com/u/jetbrains/

快速开始
TeamCity Web

访问 8111 端口(TeamCity默认端口),如果访问不了,可能因为防火墙阻隔

修改端口,配置文件: /usr/local/TeamCity/conf/server.xml

<Connector port="8111" ...
teamcity-01-setup

teamcity-01-setup

TeamCity将构建历史,用户信息,构建结果等存放在数据库中,初次使用,我们选择默认配置就行Internal(HSQLDB).

创建的过程会需要一些时间

接下来: 接受协议 -> 创建管理账号 -> 进入个人界面,OK

个人界面,具体信息可填可不填

创建项目

创建项目的时候,可以根据需求,选择手动配置,还是指向仓库地址等等

Administration -> Projects -> Create project -> Manually

teamcity-06-project-01

teamcity-06-project-01

一个项目包含如下内容

teamcity-07-project-02

teamcity-07-project-02

  • 通用设置
    • 构建配置,构建配置定义项目如何获取和构建源代码。一个项目可以有多个构建配置,构建配置下又有多项配置
      • 通用设置,包含该构建配置的名称,ID,描述,文件路径等等
      • 版本控制设置
      • 构建步骤
      • 触发器
      • 参数
      • 等等
  • VCS
  • Parameters
  • 等等
帮助中心

帮助中心

TeamCity

TeamCity is a Java-based build management and continuous integration server from JetBrains.

teamcity 插件安装

JetBrains Plugin repository

官方 Installing Additional Plugins

两种方法

  1. web界面上传,重启teamcity
  2. 手动上传zip包到数据目录,重启teamcity

重启teamcity之后,在插件界面可以看到是否安装成功,以及相关信息.

通过web界面上传
teamcity-05-plugins

teamcity-05-plugins

手动上传到数据目录

比如: /root/.BuildServer/plugins

重启teamcity

teamcity 构建参数
teamcity-web-parameters

从web服务填充可选动态参数

GitHub teamcity-web-parameters

动态构建参数
teamcity-02-parameter

teamcity-02-parameter

teamcity-03-parameter-02

teamcity-03-parameter-02

保存

teamcity-04-parameter-03

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是一个用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
安装Jenkins

Installing Jenkins

本文使用Docker方式使用Jenkins

OS X
  1. 使用包安装
  2. 使用brew安装

使用brew安装

Install the latest release version

brew install jenkins

Install the LTS version

brew install jenkins-lts
Docker

下载镜像

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"

浏览器访问

http://localhost:49001/

Ubuntu/Debian
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

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-02-Create-Admin-User

配置完成

jenkins-03-Jenkins-is-ready

jenkins-03-Jenkins-is-ready

使用Jenkins
添加 Slave 节点(如有需要)

持续集成的环境应当尽量保持独立,当多个用户共用同一个 Jenkins Master 节点的时候,很容易因为一个成员改变了机器配置而对另一个构建造成影响。

所以,能用 Slave 做的事情尽量用 Slave 去做, 何况 Docker 里创建一个 Slave 是非常容易的事情。

jenkins-04-Manage-Nodes

jenkins-04-Manage-Nodes

jenkins-05-New-Node

jenkins-05-New-Node

jenkins-06-Node-Name

jenkins-06-Node-Name

jenkins-07-Node-2

jenkins-07-Node-2

jenkins-08-Node-3

jenkins-08-Node-3

jenkins-09-Node-4

jenkins-09-Node-4

-secret后面跟的随机密码很重要,下面一步只有 secretname 都一致才能连接成功。

需要下载镜像 jenkinsci/jnlp-slave
docker run --link jenkins -d jenkinsci/jnlp-slave -url  http://jenkins:8080 ab96387cc533b8be663ccfc57fce9f0e41b11cd6b07f96941e2b51164832f610 slave01

启动容器后刷新 Jenkins 的节点列表, 很快 slave01 节点就变成可用的啦。

搭建Node.js环境
通过Install from nodejs.org方式
  • 选择版本(没用通过Extract *.zip/*.tar.gz方式之前,有可能出现没有版本可供选择的情况,可能是网络原因)
  • 设置全局安装的包,比如 gitbook-cli@2.3.0 gitbook@3.2.2
  • 保存
jenkins-14-Install-from-nodejs

jenkins-14-Install-from-nodejs

通过 Extract *.zip/*.tar.gz 方式
  1. 安装和配置 NodeJS Plugin 管理多个版本的 Node.js
  2. 新建 Pipeline 项目,验证 Node.js 安装
安装配置 NodeJS Plugin

使用NodeJS Plugin插件来安装Node.js

NodeJS Plugin

插件管理中,安装 NodeJS Plugin 插件

Manage Jenkins -> Manage Plugins -> Available -> NodeJS Plugin -> Install  without restart

Manage Jenkins -> Global Tool Configuration -> NodeJS

jenkins-10-NodeJS-01

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 版本
新建 Pipeline 项目

New Item -> Enter an item name -> Pipeline -> OK

配置 Pipeline demo

jenkins-11-Pipeline-demo

jenkins-11-Pipeline-demo

代码内容(如果不安装GitBook,可以删除后面两个状态)

node ('master') {
   stage '安装 Node'
   tool name: 'v7.8.0', type: 'jenkins.plugins.nodejs.tools.NodeJSInstallation'
   env.PATH = "${tool 'v7.8.0'}/node-v7.8.0-linux-x64/bin:${env.PATH}"
   stage '验证 Node'
   sh "node -v"
   sh "npm -v"
   stage '安装 GitBook'
   sh "npm install gitbook-cli gitbook -g"
   stage '验证 GitBook'
   sh "gitbook -V"
}

后面构建如果报错,命令不存在的话,可以在构建执行的命令里面,添加环境变量

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,否则会提示找不到nodenpm命令

点击保存,然后立即构建,等待完成.Node.JS环境就搭建成功

jenkins-12-demo-status

jenkins-12-demo-status

jenkins-13-demo-console-output

jenkins-13-demo-console-output

常用插件

EnvInject 环境变量

使用 Jenkins 持续集成 GitBook
准备:

系统管理 -> 系统设置 -> 浏览器搜索“Publish over SSH” -> 配置 “SSH Servers” ,准备部署的服务器地址“Remote Directory” 即拷贝到远程主机的目标目录

插件:

  • NodeJS Plugin
  • Publish Over SSH
配置:
  1. jenkins 创建一个自由风格Project
  2. 源码管理 配置页面往下拉 -> “源码管理” -> 选择“Git” -> 在“Repository URL” 填入“项目地址” -> 点击“Add” 加入用户名密码 -> “Branches to build” 选择分支
  3. 构建触发器

勾选“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

  1. 构建环境

勾选“Provide Node & npm bin/ folder to PATH” 默认就选中 NodeJS(这个只有用到才需要选,gitbook 需要用npm 安装)

  1. 构建

点击“增加构建步骤” -> 选中 “Execute shell” -> “Command” 内容为“gitbook build .”

  1. 构建后操作

点击“增加构建后操作步骤” -> 选中“Send build artifacts over SSH” -> “Name” 选中在准备步骤中配置的主机 -> “Sourve files” 填入“_book” -> “Remove prefix” 填入“__book” -> “点击保存”

  1. jenkins 配置完成
  2. 为了实现当项目 master 分支有提交时,jenkins自动触发构建操作,需要在gitlab 配置“Webhooks”

在gitlab 项目页面点击“Webhooks” ,把配置步骤的第三小步中出现的URL 地址填到本页面的“URL”,其它默认,点击“Add Webhooks” 完成配置

  1. 现在提交一下代码到项目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
(本邮件是程序自动下发的,请勿回复!)
项目名称:$PROJECT_NAME
构建编号:$BUILD_NUMBER
git版本号:${GIT_REVISION}
构建状态:$BUILD_STATUS
触发原因:${CAUSE}
构建日志地址:${BUILD_URL}console
构建地址:$BUILD_URL

点击 “Apply” -> “保存“

配置项目(只需要在发送邮件通知的项目配置)

增加一个构建后操作选择“Email Extension Plugin”

具体配置参数:

Project Recipient List

493535459@qq.com,xxx@qq.com (收件人,是一个列表,逗号分割)

点击“Advanced Settins…”

点击“Add Trigger”分别选择 “Success”,“Failure - Any”, “Not Built” 3个触发器(成功,失败,都会发邮件)

jenkins通过GitBook生成HTML推送到GitHub Pages
步骤
  1. 从coding等私有仓库拉取md文件
  2. 生成HTML
  3. 发布到GitHub Pages
jenkins配置
  • 搭建Node.js环境
  • 创建,配置自由风格项目

New Item -> Enter an item name -- Freestyle project

配置内容:

Project name

配置项目名称

Source Code Management
jenkins-15-SCM

jenkins-15-SCM

Build Triggers

由于在本机使用的Jenkins,没有公网IP,所以使用如下触发器

表示每五分钟检查SCM是否有新的变更,如果有则构建,没有不构建

jenkins-16-Build-Triggers

jenkins-16-Build-Triggers

Build Environment

由于上面配置的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-19-nodejs-env

继续配置构建环境

jenkins-17-Build-Environment

jenkins-17-Build-Environment

Build
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 .
附: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了

问题记录
ERROR: Timeout after 10 minutes

错误内容:

> git fetch --tags --progress git@192.168.15.120:dev/FUQIN-BANK.git +refs/heads/*:refs/remotes/origin/*
ERROR: Timeout after 10 minutes
ERROR: Error cloning remote repo 'origin'
hudson.plugins.git.GitException: Command "git fetch --tags --progress git@192.168.15.120:dev/FUQIN-BANK.git +refs/heads/*:refs/remotes/origin/*" returned status code 143:

解决办法:

# 1.方法一
Source Code Management -> Git -> Additional Behaviours -> Add ->
Timeout(in minutes) for clone and fetch operations 添加一个时间
# 2. 方法二
# 切换到jenkins用户,进入到相应的workspace,执行
git config remote.origin.url git@gitlab.com:laiye-shanghai-tech/nike-backend.git

# 3
# 调整启动参数
文件/etc/sysconfig/jenkins
JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true -Dorg.jenkinsci.plugins.gitclient.Git.timeOut=120 "
DNS日志刷爆磁盘

# 方法一, Manage Jenkins -> System Log -> Log Levels (on the left) Name: javax.jmdns Level: off

# 方法二,调整参数,重启 JENKINS_JAVA_OPTIONS=”-Djava.awt.headless=true -Dhudson.DNSMultiCast.disabled=true “

持续集成(Continuous integration)

摘自百度百科

定义

持续集成是一种软件开发实践 每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

价值
  1. 减少风险
  2. 减少重复过程
  3. 任何时间、任何地点生成可部署的软件
  4. 增强项目的可见性
    • 有效决策
    • 注意到趋势
  5. 建立团队开发产品的信心
要素
  1. 统一的代码库
  2. 自动构建
  3. 自动测试
  4. 每个人每天都要向代码库主干提交代码
  5. 每次代码递交后都会在持续集成服务器上出发一次构建
  6. 保证快速构建
  7. 模拟生产环境的自动测试
  8. 每个人都可以很容易的获取最新可执行的应用程序
  9. 每个人都清楚正在发生的状况
  10. 自动化的部署
原则
  1. 所有的开发人员需要在本地机器上做本地构建,然后再提交到版本控制库中,从而确保他们的变更不会导致持续集成失败。
  2. 开发人员每天至少向版本控制库中提交一次代码。
  3. 开发人员每天至少需要从版本控制库中更新一次代码到本地机器。
  4. 需要有专门的服务器来执行集成构建,每天要执行多次构建。
  5. 每次构建都要100%通过。
  6. 每次构建都可以生成可发布的产品。
  7. 修复失败的构建是优先级最高的事情。
  8. 测试是未来,未来是测试。
常用构建工具
  • Jenkins
  • Travis
  • Codeship
  • Strider
  • TeamCity

travis

Getting started

Personal access tokens

新建一个token,配置如下权限即可(token只显示一次)

github-travis-token-setting

github-travis-token-setting

安装 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

travis-setting

db

SQL Server

SQL Server

GitHub pyodbc

端口

sqlserver-01-port

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.

Install FreeTDS and unixODBC

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
Edit the freetds.conf configuration file

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.

Edit the odbcinst.ini and odbc.ini configuration files

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.

Connect with pyodbc

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快速入门
安装

mongodb install

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()方法,仅返回一个文档

RDBMS Where子句等效于MongoDB

查询文档在一些条件的基础上,可以使用下面的操作

操作: 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

docker部署MongoDB分片集群.rst
注意事项
  1. 配置节点 需要部署集群
  2. 权限问题,根据具体情况路由需要绑定IP,绑定所有,使用参数 --bind_ip_all
使用docker部署MongoDB分片集群

MONGO_VERSION=4.0.4

  1. 启动所有容器
  2. 初始化分片副本集
  3. 初始化配置副本集
  4. 配置分片
配置副本集
# 初始化 分片副本集1
//连接到rs1_svr1
mongo <宿主ip>:21117
//配置副本集
rs.initiate();
rs.add("<宿主ip>:21217");
rs.add("<宿主ip>:21317");
rs.status();
Fix hostname of primary.
cfg = rs.conf();
cfg.members[0].host = "<宿主ip>:21117";
rs.reconfig(cfg);
rs.status();
//以上命令一个一个执行

# 初始化 分片副本集2
# 连接到rs2_svr1
# 同上

# 初始化 配置服务副本集
# 连接到configsrv
# 同上
配置分片
登陆mongos

添加分片

sh.addShard("rs1/172.17.104.247:27020,172.17.104.247:27021")
sh.addShard("rs2/172.17.104.247:27023,172.17.104.247:27024")
sh.status();
mongodb yaml配置文件详解

Configuration File Options

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 alidayusms

创建了一个数据库,如果什么都不操作离开的话,库就会被系统删除.所以要创建一个集合(表)

db.test.insert({'name':'test'});
删除当前使用数据库:
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"]})

mongodb用户相关

监控
mongostat -h 192.168.2.199 -u system -p BJjg10661898 --authenticationDatabase=admin
慢查询分析:
db.setProfilingLevel()

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 日志切割

每一次重启,日志也会重新生成

mongodb Rotate Log Files

四种方法

  1. Default Log Rotation Behavior
  2. Log Rotation with –logRotate reopen
  3. Syslog Log Rotation
  4. 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

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@testdb1testuser@testdb2

在哪个库下面创建用户,这个用户就是哪个库的

角色概念

查看所有用户show roles

Mongodb的授权采用了角色授权的方法,每个角色包括一组权限。

Mongodb已经定义好了的角色叫内建角色,我们也可以自定义角色。

Built-In Roles

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

内置角色详细权限

__system相关官方文档:

配置认证环境和认证登录
  1. Mongodb默认不开启认证

启动mongodb之后,创建一个管理用户

use admin
db.createUser({user:'yang',pwd:'yang',roles:[{ "role" : "root", "db" : "admin" }]});
  1. 关闭mongodb

    db.shutdownServer()

  2. 启用mongodb授权认证

    1. 以–auth 启动mongod
    2. 旧版本在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将允许本地直接访问

  1. 启动Mongodb

    sudo service mongod start

  2. 认证登录

连接的时候认证

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:

  1. $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

  1. 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.

  1. 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语句
查询所有数据库占用磁盘空间大小的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;
查询单个库中所有表磁盘占用大小的SQL语句:
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初始化故障排错集锦
错误示例1:warning:The host ‘mysql’ could not be looked up with resolve ip

需修改主机名的解析,使其和uname -n一样

错误示例2:error:1004 can’t create file ‘/tmp/#sql300e_1_0.frm’(errno: 13)

错误示例2下面也是初始化数据库时可能会遇到的错误,原因是/tmp目录的权限问题:

解决办法

查看/tmp目录权限

修改权限 chmod -R 1777 /tmp/

sql语句错误, 导致slave sql线程出错, 主从不一致

可以手动告诉从库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使用GTID主从复制错误问题

主从网络中断,或主服务器重启,或从服务器重启,从会根据配置文件中的时间,默认1分钟,去自动重连主服务器,直到网络和服务均可正常连接,连接正常后可自动继续同步之前文件,不需要任何人工干预.

当主从因为人为原因出现不同步的时候,可以用下面命令进行同步:

LOAD DATA FROM MASTER;
LOAD TABLE TBLNAME FROM MASTER;

注意,上面命令会对主数据库进行锁操作,如果数据库极大,建议在停机的时候进行,或者用短锁备份查看 show master status; 后,拷贝数据库的方式进行.

当 BIN-LOG 里面出现 SQL 级别错误导致主从不能同步的时候,可以用下面方法掠过该错误语句行,继续同步:

stop slave;
set global sql_slave_skip_counter=1;
start slave;

执行当 set global sql_slave_skip_counter=1; 是可能会出现以下错误:

ERROR 1858 (HY000): sql_slave_skip_counter can not be set when the server is running with GTID_MODE = ON.
Instead, for each transaction that you want to skip, generate an empty transaction with the same GTID as the transaction

解决:

show slave status\G
# 记录 Executed_Gtid_Set

reset master;
stop slave;
reset slave;
# 使用上面的 Executed_Gtid_Set + 1,根据具体情况调整
set global gtid_purged='2a378de7-bf79-11e7-b8c1-7cd30ac474ca:1-7899,47f27a52-bf79-11e7-b8c2-7cd30ae01298:1-153820893';
CHANGE MASTER TO MASTER_HOST='host', MASTER_PORT=3306, MASTER_USER='user', MASTER_PASSWORD='password', MASTER_AUTO_POSITION=1, MASTER_DELAY = 3600;
START SLAVE;

show slave status\G
innobackupex

1.XtraBackup 有两个工具:xtrabackup 和 innobackupex:

  • xtrabackup 本身只能备份 InnoDB 和 XtraDB ,不能备份 MyISAM;
  • innobackupex 本身是 Hot Backup 脚本修改而来,同时可以备份 MyISAM 和 InnoDB,但是备份 MyISAM 需要加读锁。

2.一般情况下,在备份完成后,数据尚且不能用于恢复操作,因为备份的数据中可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务。因此,此时数据文件仍处理不一致状态。“准备”的主要作用正是通过回滚未提交的事务及同步已经提交的事务至数据文件也使得数据文件处于一致性状态。

innobackupex –apply-log /backup-path

3.在实现“准备”的过程中,innobackupex 通常还可以使用 --use-memory 选项来指定其可以使用的内存的大小,默认通常为 100M。如果有足够的内存可用,可以多划分一些内存给 prepare 的过程,以提高其完成速度。

4.用 innobackupex 备份数据时,–apply-log 处理过的备份数据里有两个文件说明该备份数据对应的 binlog 的文件名和位置。但有时这俩文件说明的位置可能会不同:

对于纯 InnoDB 操作,备份出来的数据中上述两个文件的内容是一致的。
对于 InnoDB 和非事务存储引擎混合操作,xtrabackup_binlog_info 中所示的 position 应该会比 xtrabackup_pos_innodb 所示的数值大。此时应以 xtrabackup_binlog_info 为准;而后者和 apply-log 时 InnoDB recovery log 中显示的内容是一致的,只针对 InnoDB 这部分数据
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 5.7官网手册

MySQL设置root密码
SQL命令行set 修改
[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;
shell 命令行mysqladmin修改
#当没有密码时设置新密码为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>
SQL命令行update语句修改
[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;

ERROR 1820 (HY000)

ERROR 1819 (HY000)

ERROR 1054 (42S22)

官方文档

忘记密码时,登录修改

Linux环境下mysql的root密码忘记解决方法

修改my.cnf重启进程跳过授权表改密码
[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>
使用mysqld_safe跳过授权表改密码
[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主从相关
主从复制忽略指定表
# 忽略一张表
replicate-ignore-table = db.table1
# 忽略多张表
replicate-ignore-table = db.table2
replicate-ignore-table = db.table3
安全删除binlog日志

有三种解决方法:

  1. 关闭mysql主从,关闭binlog
  2. 开启mysql主从,设置expire_logs_days
  3. 手动清除binlog文件
重启mysql,开启mysql主从,设置expire_logs_days
# vim /etc/my.cnf  //修改expire_logs_days,x是自动删除的天数,一般将x设置为短点,如10
expire_logs_days = x  //二进制日志自动删除的天数。默认值为0,表示“没有自动删除”
三种情况下会做 flush logs 操作
1. 重启
2. BINLOG文件大小达到参数max_binlog_size限制,max_binlog_size默认为1G
3. 手工执行命令。

此方法需要重启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数据库启动和关闭操作
登陆

root登陆之后切换到oracle用户上,输入

su - oracle
连接

在oracle用户下,输入

sqlplus /nolog
使用管理员权限
输入 connect /as sysdba
启动/关闭服务
输入 startup
startup参数

不带参数,启动数据库实例并打开数据库,以便用户使用数据库,在多数情况下,使用这种方式!

nomount,只启动数据库实例,但不打开数据库,在你希望创建一个新的数据库时使用,或者在你需要这样的时候使用!

mount,在进行数据库更名的时候采用。这个时候数据库就打开并可以使用了!

shutdown的参数
Normal 需要等待所有的用户断开连接
Immediate 等待用户完成当前的语句
Transactional 等待用户完成当前的事务
Abort 不做任何等待,直接关闭数据库
normal 需要在所有连接用户断开后才执行关闭数据库任务,所以有的时候看起来好象命令没有运行一样!在执行这个命令后不允许新的连接
immediate 在用户执行完正在执行的语句后就断开用户连接,并不允许新用户连接。
transactional 在拥护执行完当前事物后断开连接,并不允许新的用户连接数据库。
abort 执行强行断开连接并直接关闭数据库。
如果是启动服务,要开启监听
退出sqlplus模式
输入 lsnrctl start

redis

Redis
什么是Redis?
  • Redis是一个开源的使用ANSI C语言编写的Key-Value 内存数据库
  • 读写性能强,支持多种数据类型
  • 把数据存储在内存中的高速缓存
  • 作者Salvatore Sanfilippo
Redis简介
特点
* 速度快
* 支持多种数据结构(string、list、hash、set、storted set)
* 持久化
* 主从复制(集群)
* 支持过期时间
* 支持事务
* 消息订阅
* 官方不支持WINDOWS,但是有第三方版本
Redis与Memcached的对比
项目 Redis Memcached
过期策略 支持 支持
数据类型 五种数据类型 单一数据类型
持久化 支持 不支持
主从复制 支持 不支持
虚拟内存 支持 不支持
性能 性能
Redis应用场景
数据缓存
提高访问性能,使用的方式与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"
Redis数据存储
redis1-20161230

redis1-20161230

持久化
  • RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
  • AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。
  • Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下,当 Redis 重启时,它会优先使用AOF 文件来还原数据集,因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。
  • 你甚至可以关闭持久化功能,让数据只在服务器运行时存在。
持久化策略
日志文件 appendonly yes/no
save 900 1       ## 900秒(15分钟)内有一个更改,存盘
save 300 10      ## 300秒(5分钟)内有10个更改,存盘
save 60 10000    ## 60秒内有10000个更改,即将数据写入磁盘
压缩
dbcompression yes

指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
同步
appendfsync everysec
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折衷,默认值)
核心实践
数据类型
redis2-20161230

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
Hash(哈希)
  • 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 中的一个或多个指定域
LIST(列表)
  • 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"
SET
  • 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"
SortedSet(有序集合)
  • 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

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

订阅发布实例-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

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"
基于keepalived的自动故障切换
Redis Sentinel
Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。
功能
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
配置记录,详细操作见下文
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"
sentinel命令
  • 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 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。
  • Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。
  • Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
  • 将数据自动切分(split)到多个节点的能力。
  • 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
Redis 集群数据共享
  • 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 Cluster-20161230

运行机制
  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
  • 节点的fail是通过集群中超过半数的master节点检测失效时才生效.
  • 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • 把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key
配置cluster
需要安装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
PHP使用redis
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");
?>
Python连接redis
pip install redis

>>> import redis
>>> r = redis.StrictRedis(host='localhost', port=6379, db=0)
>>> r.set('foo', 'bar')
True
>>> r.get('foo')
'bar'

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)
ID自增
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
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)
用SQL命令查看Mysql数据库大小
1、进入information_schema 数据库(存放了其他的数据库的信息)
use information_schema;

2、查询所有数据的大小:
select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables;

3、查看指定数据库的大小:
select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables where table_schema='home';

4、查看指定数据库的某个表的大小
比如查看数据库home中 members 表的大小
select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables where table_schema='home' and table_name='members';

docker

Docker Compose

Docker Compose

Compose是定义和运行多容器Docker应用程序的工具.你可以使用Compose文件来定义你的应用服务.然后,使用单个命令,您可以从配置创建并启动所有服务.

Compose使用于开发,测试和分期环境以及CI工作流.

使用Compose基本上是如下三个流程:

  1. 使用Dockerfile定义你的应用的环境,这样你可以在任何地方重新生成你的应用环境
  2. docker-compose.yml文件中定义组成你应用的服务,以便于它们可以在孤立的环境中一起运行
  3. 最后,执行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有用于管理整个应用生命周期的命令

  • 启动,停止和重新构建服务
  • 查看运行服务的状态
  • 输出运行服务的日志
  • 运行一次性命令
Install Docker Compose

Install Docker Compose

Linux系统安装 docker-compose

# 下载
sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.22.0, build 1719ceb

docker

Docker is the world’s leading software container platform.

marathon

它是一个mesos框架,能够支持运行长服务,比如web应用等。是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat Play等等,可以集群的多进程管理。也是一种私有的Pass,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。

docker
自动安装
# 阿里云镜像
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
手动安装
系统要求
  • Ubuntu 14.04、16.04
  • Debian 7.7、8.0
  • CentOS 7.X
  • Fedora 20、21、22
  • OracleLinux 6、7
Ubuntu

…. code-block:: shell

sudo apt-get remove docker docker-engine docker.io

sudo apt-get update

sudo apt-get install
apt-transport-https ca-certificates curl software-properties-common

sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable”

sudo apt-get update

sudo apt-get install docker-ce

CentOS

…. code-block:: shell

#!/bin/bash sudo yum remove -y docker

docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine

sudo yum install -y yum-utils device-mapper-persistent-data lvm2 epel-release

sudo yum-config-manager
–add-repo https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install -y docker-ce docker-compose

sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-‘EOF’ { “storage-driver”: “overlay”, “registry-mirrors”: [“https://haha.mirror.aliyuncs.com”] } EOF sudo usermod -G docker $1 sudo systemctl daemon-reload sudo systemctl restart docker sudo systemctl enable docker

安装后操作

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-docker-compose

docs-docker-compose

由于GitHub的访问问题,Linux系统的Docker Compose下载也不稳定,所以可以从阿里云镜像站下载

阿里云docker-toolbox

下载对应系统即可

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 exec 容器id sh -c "echo  haha"
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镜像

因为我们只是启动了一个bash,所以当我们退出的时候,镜像也停止了。

如果想容器不退出,可以使用 Ctrl+P+Q进行, 使用bash run的容器也可以不退出

[root@65b6cf94133f /]# exit
查看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自带命令进入容器

使用docker自带的命令进入容器,可以跟名字,或者CONTAINER ID登录

[root@localhost ~]# docker attach stoic_mayer

docker attach 是Docker自带的命令,但是使用 attach 命令,开多个窗口同时,所有窗口都会同步显示操作。当某个窗口因命令阻塞时,其他窗口也无法执行操作了,退出时如果使用exit或者ctrl+c也会关闭docker容器,使用快捷键先按ctrl+p,再按ctrl+q。

使用exec进入容器
docker exec -it 22fa39e2bb08 bash
使用nsenter命令

查询是否安装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信息
使用新提交的镜像启动docker容器
[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 来创建镜像
创建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"]

说明

FROM:指令告诉 Docker 使用哪个镜像作为基础
MAINTAINER:维护者的信息
RUN:要执行的操作
EXPOSE:向外部开放端口
CMD:命令来描述容器启动后运行的程序
生成镜像
[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
删除

如果强制删除使用-f参数

删除镜像
docker rmi 镜像ID
删除容器
docker rm 容器id
导入导出
导出镜像

查看有哪些镜像

[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" ]
网络

网络模式

https://www.cnblogs.com/gispathfinder/p/5871043.html

端口映射

把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!"
FROM

第一条指令必须为 FROM 指令,用来指定使用的镜像,#号开头的为注释。

FROM centos:6
MAINTAINER(deprecated)

MAINTAINER (deprecated)

推荐使用LABEL

指定维护者信息。

MAINTAINER  yy
LABEL

LABEL maintainer=“SvenDowideit@home.org.au

RUN

RUN 指令对镜像执行跟随的命令。

RUN echo "yy" > /opt/author
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
VOLUME

VOLUME命令用于让你的容器访问宿主机上的目录。

VOLUME ["/my_files"]
EXPOSE

EXPOSE用来指定端口,使容器内的应用可以通过端口和外界交互。

EXPOSE 80
ENV

用来设置环境变量

ENV LANG en_US.UTF-8

ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
WORKDIR

相当于CD命令,指定之后的RUN命令的运行目录

WORKDIR /a

WORKDIR b

WORKDIR c

RUN pwd
ADD

将源文件拷贝到容器对应的路径

ADD <src> <dest>

可以是Dockerfile所在目录的一个相对路径,也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。

overlayfs

https://www.jianshu.com/p/959e8e3da4b2

概念

一种联合文件系统,设计简单,速度更快。overlayfs在linux主机上只有两层,一个目录在下层,用来保存镜像(docker),另外一个目录在上层,用来存储容器信息。

在overlayfs中,底层的目录叫做lowerdir,顶层的目录称之为upperdir,对外提供统一的文件系统为merged。

当需要修改一个文件时,使用CoW将文件从只读的Lower复制到可写的Upper进行修改,结果也保存在Upper层。在Docker中,底下的只读层就是image,可写层就是Container。

优劣
  1. OverlayFS支持页缓存共享,多个容器访问同一个文件能共享一个页缓存,以此提高内存使用
  2. OverlayFS消耗inode,随着镜像和容器增加,inode会遇到瓶颈。Overlay2能解决这个问题。在Overlay下,为了解决inode问题,可以考虑将/var/lib/docker挂在单独的文件系统上,或者增加系统inode设置。
docker tips
修改仓库为阿里云镜像仓库
1.阿里云docker仓库 https://dev.aliyun.com/search.html
2.注册账号,点击自己的管理中心
3.然后进入镜像库可以看到自己专有的镜像地址
4.使用命令 vi /etc/docker/daemon.json 添加如下
{
    "registry-mirrors": ["https://xxxxx.mirror.aliyuncs.com"]
}
5.systemctl daemon-reload
6.systemctl restart docker
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

GitHub dockerfile

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>.
问题记录
free data blocks which is less than…

报错信息:

devmapper: Thin Pool has 82984 free data blocks which is less than minimum required 163840 free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior

解决

Cleanup exited processes:

docker rm $(docker ps -q -f status=exited)

Cleanup dangling volumes:

docker volume rm $(docker volume ls -qf dangling=true)

Cleanup dangling images:

docker rmi $(docker images --filter "dangling=true" -q --no-trunc)
msg=”[graphdriver] prior storage driver overlay2 failed: driver not supported

可能是因为docker版本变化,导致出现该问题

可以尝试

…. code-block:: shell

mv /var/lib/docker /var/lib/docker.old 再启动
阿里云docker镜像库

阿里云容器HUB服务

pull示例 docker pull registry.cn-hangzhou.aliyuncs.com/acs-sample/gitlab-sameersbn

参考链接

使用阿里云容器Hub加速Docker镜像下载

kubernetes

Kubernetes集群
简单创建及测试

对于初学或者简单验证测试的用户, 可以使用下面几种方法

minikube

minikube

安装

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.
Requirements
  • 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

驱动插件安装

Hyperkit driver

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'.
play-with-k8s

play-with-k8s

Katacoda playground

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。

katacoda courses kubernetes

Kubernetes部署

Kubernetes 部署指南

Kubeadm

Kubeadm解决TLS加密配置问题, 部署核心Kubernetes组件并确保新增节点可以很容易的加入集群. 集群通过RBAC等机制确保安全.

更多细节参考 https://github.com/kubernetes/kubeadm

安装

参考 Alibaba open source mirror site

Debian / Ubuntu
apt-get update && apt-get install -y apt-transport-https
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
CentOS / RHEL / Fedora
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
初始化Master
kubectl常用命令
  • kubectl get nodes
  • kubectl describe node test-kube
  • kubectl get svc
  • kubectl get rc
  • kubectl create -f xxx.yaml (不建议使用,无法更新,必须先delete)
  • kubectl apply -f xxx.yaml (创建+更新,可以重复使用)
问题记录

Troubleshooting kubeadm

运行minikube时报错
zsh: exec format error: minikube

安装的包有问题,重新下载

crictl包丢失
VERSION="v1.11.1"
wget https://github.com/kubernetes-incubator/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz
sudo tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin
rm -f crictl-$VERSION-linux-amd64.tar.gz
nodePort无法开放80端口,提示不在30000-32767范围内

修改/etc/kubernetes/manifests/kube-apiserver.yaml(有些版本也可能是json)文件,修改其中的 `` - –service-node-port-range=80-32767 `` 将range从30000-32767修改为80-32767。如果没有这句话,则按照格式添加一句。

查看服务错误日志
journalctl -u -f
1 node(s) had taints that the pod didn’t tolerate.

有时候一个pod创建之后一直是pending,没有日志,也没有pull镜像,describe的时候发现里面有一句话: 1 node(s) had taints that the pod didn’t tolerate.

直译意思是节点有了污点无法容忍,执行 kubectl get no -o yaml | grep taint -A 5 之后发现该节点是不可调度的。这是因为kubernetes出于安全考虑默认情况下无法在master节点上部署pod,于是用下面方法解决:

kubectl taint nodes --all node-role.kubernetes.io/master-
kubectl命令报错
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")

这个错误的原因是执行 kubeadm init 之后没有关注到控制台的输出,其中有一段话:

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

意思是需要首先执行上面三行脚本之后才可以继续使用集群

Unable to update cni config: No networks found in /etc/cni/net

错误如下:

Unable to update cni config: No networks found in /etc/cni/net
Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message

解决方法是安装flannel:

sysctl net.bridge.bridge-nf-call-iptables=1
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.10.0/Documentation/kube-flannel.yml
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).

What is Kubernetes?

设计架构
架构图

架构图

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 提供集群日志采集, 存储与查询

go

基础

基础语法

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。以下 GO 语句由 6 个标记组成:

fmt.Println("Hello, World!")
关键字
break       default func    interface       select
case        defer   go      map     struct
chan        else    goto    package switch
const       fallthrough     if      range   type
continue    for     import  return  var

36 个预定义标识符:

append      bool    byte    cap     close   complex complex64       complex128      uint16
copy        false   float32 float64 imag    int     int8    int16   uint32
int32       int64   iota    len     make    new     nil     panic   uint64
print       println real    recover string  true    uint    uint8   uintptr
程序一般结构
// 当前程序的包名
package main

// 导入其他包
import . "fmt"

// 常量定义
const PI = 3.14

// 全局变量的声明和赋值
var name = "gopher"

// 一般类型声明
type newType int

// 结构的声明
type gopher struct{}

// 接口的声明
type golang interface{}

// 由main函数作为程序入口点启动
func main() {
    Println("Hello World!")
}

tips

golang开发环境mac下编译linux环境文件
CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build ...

java

Java基础

快速开始
HelloWorld
创建项目
编辑HelloWorld.java

内容如下

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()不会增加换行符
关键字

上面代码中的public, class, static等都是Java中的关键字(保留字)

关键字是编程语言保留这些单词用作特殊目的, 它们构成了编程语言语法的基本元素.

Java关键字

标识符

类名, 变量名, 方法名, 方法参数名等都被成为标识符. 比如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来创建一个新的对象。创建对象需要以下三步:

  1. 声明:声明一个对象, 包括对象名称和对象类型。
  2. 实例化:使用关键字new来创建一个对象。
  3. 初始化:使用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
  • byte 数据类型是8位、有符号的, 以二进制补码表示的整数;
  • 最小值是 -128(-2^7)
  • 最大值是 127(2^7-1)
  • 默认值是 0;
  • byte 类型用在大型数组中节约空间, 主要代替整数, 因为 byte 变量占用的空间只有 int 类型的四分之一;
  • 例子:byte a = 100, byte b = -50。
short
  • short 数据类型是 16 位、有符号的以二进制补码表示的整数
  • 最小值是 -32768(-2^15)
  • 最大值是 32767(2^15 - 1)
  • Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是 0;
  • 例子:short s = 1000, short r = -20000
int
  • int 数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是 -2,147,483,648(-2^31)
  • 最大值是 2,147,483,647(2^31 - 1)
  • 一般地整型变量默认为 int 类型;
  • 默认值是 0 ;
  • 例子:int a = 100000, int b = -200000
long
  • 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
  • float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float 在储存大型浮点数组的时候可节省内存空间;
  • 默认值是 0.0f
  • 浮点数不能用来表示精确的值, 如货币;
  • 例子:float f1 = 234.5f
double
  • double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
  • 浮点数的默认类型为double类型;
  • double类型同样不能表示精确的值, 如货币;
  • 默认值是 0.0d
  • 例子:double d1 = 123.4
boolean

boolean数据类型表示一位的信息; 只有两个取值:true 和 false; 这种类型只作为一种标志来记录 true/false 情况; 默认值是 false; 例子:boolean one = true

char

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++的指针。引用类型指向一个对象, 指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型, 比如 EmployeePuppy 等。变量一旦声明后, 类型就不能被改变了。
  • 对象、数组都是引用数据类型。
  • 所有引用类型的默认值都是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支持一些特殊的转义字符序列

自动转换类型

整型, 实型(常量), 字符型数据可以混合运算. 运算中, 不同类型的数据线转换为同一类型, 然后进行运算

转换从低级到高级

低  ------------------------------------>  高

byte,short,char—> int —> long—> float —> double

数据类型转换必须满足如下规则

  1. 不能对boolean类型进行类型转换。
  2. 不能把对象类型转换成不相关类的对象。
  3. 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
  4. 转换过程中可能导致溢出或损失精度 java  int i =128;  byte b = (byte)i;  // 因为byte类型时8位, 最大值为127, 所以当强制转换为int类型值128时候就会导致溢出。
  5. 浮点数到整数的转换是通过舍弃小数得到, 而不是四舍五入 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。

强制类型转换
  1. 条件是转换的数据类型必须是兼容的。
  2. 格式:(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);
    }
}
隐含强制类型转换
  1. 整数的默认类型是 int。
  2. 浮点型不存在这种情况, 因为在定义 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, finalstatic类型的变量。常量初始化后不可改变。
  • 静态变量储存在静态存储区。经常被声明为常量, 很少单独使用static声明变量。
  • 静态变量在程序开始时创建, 在程序结束时销毁。
  • 与实例变量具有相似的可见性。但为了对类的使用者可见, 大多数静态变量声明为public类型。
  • 默认值和实例变量相似。数值型变量默认值是0, 布尔型默认值是false, 引用类型默认值是null。变量的值可以在声明的时候指定, 也可以在构造方法中指定。此外, 静态变量还可以在静态语句块中初始化。
  • 静态变量可以通过:ClassName.VariableName的方式访问。
  • 类变量被声明为public static final类型时, 类变量名称一般建议使用大写字母。如果静态变量不是publicfinal类型, 其命名方式与实例变量以及局部变量的命名方式一致。
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
default

不使用任何关键字

使用默认访问修饰符声明的变量和方法, 对同一个包内的类是可见的。接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。

实例, 变量和方法的声明可以不使用任何修饰符。

String version = "1.5.1";
boolean processOrder() {
   return true;
}
private

私有访问修饰符是最严格的访问级别, 所以被声明为 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 类分布在不同的包中, 则需要导入相应 public 类所在的包。由于类的继承性, 类所有的公有方法和变量都能被其子类继承。

// Java 程序的 main() 方法必须设置成公有的, 否则, Java 解释器将不能运行该类。
public static void main(String[] arguments) {
   // ...
}
protected

被声明为 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 修饰符, 用来创建抽象类和抽象方法。

synchronizedvolatile 修饰符, 主要用于线程的编程。

static
  • 静态变量: static关键字用来声明独立于对象的静态变量, 无论一个类实例化多少对象, 它的静态变量只有一份拷贝. 静态变量也称为类变量. 局部变量不能被声明为static变量
  • 静态方法: static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据, 然后计算这些数据。

对类变量和方法的访问可以直接使用 classname.variablenameclassname.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 对象里的数据可以被改变。也就是说 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 方法的主要目的是防止该方法的内容被修改。

如下所示, 使用 final 修饰符声明方法。

public class Test{
    public final void changeName(){
       // 方法体
    }
}
final 类

final 类不能被继承, 没有类能够继承 final 类的任何特性。

实例

public final class Test {
   // 类体
}
abstract

抽象类

抽象类不能用来实例化对象, 声明抽象类的唯一目的是为了将来对该类进行扩充。

一个类不能同时被 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 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

public synchronized void showDetails(){
.......
}
transient

序列化的对象包含被 transient 修饰的实例变量时, java 虚拟机(JVM)跳过该特定的变量。

该修饰符包含在定义变量的语句中, 用来预处理类和变量的数据类型。

public transient int limit = 55;   // 不会持久化
public int b; // 持久化
volatile

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, 所以该循环会停止。

运算符
  • 算术运算符
  • 关系运算符
  • 位运算符
  • 逻辑运算符
  • 赋值运算符
  • 其他运算符

Java运算符

算术运算符

变量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
自增自减
  1. 自增(++)自减(–)运算符是一种特殊的算术运算符, 在算术运算符中需要两个操作数来进行运算, 而自增自减运算符是一个操作数。
  2. 前缀自增自减法(++a,–a): 先进行自增或者自减运算, 再进行表达式运算。
  3. 后缀自增自减法(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(",");
        }
    }
}
break

break 主要用在循环语句或者 switch 语句中, 用来跳出整个语句块。

break 跳出最里层的循环, 并且继续执行该循环下面的语句。

continue

continue 适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。

for 循环中, continue 语句使程序立即跳转到更新语句。

while 或者 do…while 循环中, 程序立即跳转到布尔表达式的判断语句。

分支结构
  • if 语句
  • switch 语句

可以嵌套

if语句

一个 if 语句包含一个布尔表达式和一条或多条语句。

if(布尔表达式)
{
   //如果布尔表达式为true将执行的语句
}
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++ 风格
对象数组*

数组既可以存储基本数据类型,也可以存储引用类型。它存储引用类型的时候的数组就叫对象数组。

案例

用数组存储5个学生对象,并遍历数组。

日期时间

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
掌握
  1. IO流操作中大部分都是对文件的操作,所以Java就提供了File类供我们来操作文件
  2. 构造方法
    • 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");
  3. File类的功能(自己补齐)
    • 创建功能
    • 删除功能
    • 重命名功能
    • 判断功能
    • 获取功能
    • 高级获取功能
    • 过滤器功能
  4. 案例:
    • 输出指定目录下指定后缀名的文件名称
      • 先获取所有的,在遍历的时候判断,再输出
      • 先判断,再获取,最后直接遍历输出即可
    • 批量修改文件名称
FileInputStream
FileOutputStream

实例

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 类来获取用户的输入。

异常
异常体系
  1. 程序出现的不正常的情况.
  2. 异常的体系
    • Throwable
      • |–Error 严重问题, 我们不处理.
      • |–Exception
        • |–RuntimeException 运行期异常, 我们需要修正代码
        • |–非RuntimeException 编译期异常, 必须处理的, 否则程序编译不通过
内置异常
异常处理
  • JVM的默认处理
    • 把异常的名称,原因,位置等信息输出在控制台, 但是呢程序不能继续执行了.
  • 自己处理
    • a:try…catch…finally
      • 自己编写处理代码,后面的程序可以继续执行
    • b:throws
      • 把自己处理不了的, 在方法上声明, 告诉调用者, 这里有问题
捕获异常

使用 trycatch 关键字可以捕获异常. 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
}

一个方法可以声明抛出多个异常, 多个异常之间用逗号隔开.

例如, 下面的方法声明抛出 RemoteExceptionInsufficientFundsException

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环境安装与配置

Oracle官网

MacOS安装JDK
  1. Oracle官网
  2. 下载适用于macOS的JDK,(dmg)
  3. 安装dmg
  4. 安装完成会将系统默认的Java版本更新为对应的安装版本
  5. 终端验证java -version
Linux
使用tar.gz包安装
  1. Oracle官网
  2. 下载适用于Linux的二进制包
  3. 解压到想要安装的位置
  4. 配置环境变量
  5. 终端验证java -version
Ubuntu使用apt-get安装

在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版本更新为对应的安装版本。

Windows
  1. 下载, 安装
  2. 配置环境变量
  3. 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的系统调用, 完成最终的程序功能。

开发工具

面向对象

Java为面向对象语言, 有以下基本概念

  • 多态
  • 继承
  • 封装
  • 抽象
  • 对象
  • 实例
  • 方法
  • 重载
继承
类的继承格式

Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的, 一般形式如下

class 父类 {
}

class 子类 extends 父类 {
}
继承的特性
  • 子类拥有父类非private的属性, 方法。
  • 子类可以拥有自己的属性和方法, 即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。
  • Java的继承是单继承, 但是可以多重继承, 单继承就是一个子类只能继承一个父类, 多重继承就是, 例如A类继承B类, B类继承C类, 所以按照关系就是C类是B类的父类, B类是A类的父类, 这是java继承区别于C++继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点, 耦合度高就会造成代码之间的联系)
继承关键字

继承可以使用 extendsimplements 这两个关键字来实现继承, 而且所有的类都是继承于 java.lang.Object, 当一个类没有继承的两个关键字, 则默认继承object(这个类在 java.lang 包中, 所以不需要 import)祖先类。

extends关键字

在 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

使用 implements 关键字可以变相的使java具有多继承的特性, 使用范围为类继承接口的情况, 可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

public interface A {
    public void eat();
    public void sleep();
}

public interface B {
    public void show();
}

public class C implements A,B {
}
super 与 this
  • 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 关键字声明类可以把类定义为不能继承的, 即最终类;或者用于修饰方法, 该方法不能被子类重写:

声明类
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多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

多态

多态是同一个行为具有多个不同表现形式或形态的能力.

多态就是同一个接口, 使用不同的实例而执行不同操作

多态性是对象多种表现形式的体现

多态的优点
  1. 消除类型之间的耦合关系
  2. 可替换性
  3. 可扩充性
  4. 接口性
  5. 灵活性
  6. 简化性
多态存在的三个必要条件
  • 继承
  • 重写
  • 父类引用指向子类对象

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

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;
   }

   //其余代码
}
抽象类总结规定
  1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
  4. 构造方法,类方法(用static修饰的方法)不能声明为抽象方法。
  5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

封装的优点

  1. 良好的封装能够减少耦合。
  2. 类内部的结构可以自由修改。
  3. 可以对成员变量进行更精确的控制。
  4. 隐藏信息,实现细节。
实现Java封装的步骤
  1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:
public class Person {
    private String name;
    private int age;
}

这段代码中,将 nameage 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。

  1. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:
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结尾的文件中
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中
接口与类的区别
  • 接口不能用于实例化对象
  • 接口没有构造方法
  • 接口中所有的方法必须是抽象方法
  • 接口不能包含成员变量, 除了staticfinal变量
  • 接口不是被类继承, 而是要被类实现
  • 接口支持多继承
接口特性
  • 接口中每一个方法也是隐式抽象的, 接口中的方法会被隐式的指定为public abstract(只能是``public abstract``, 其他修饰符都会报错)
  • 接口中可以含有变量, 但是接口中的变量会被隐式的指定为public static final变量(并且只能是public, 用private修饰会报编译错误)
  • 接口中的方法是不能再接口中实现的, 只能由实现接口的类来实现接口中的方法
抽象类和接口的区别
  1. 抽象类中的方法可以有方法体, 就是能实现方法的具体功能, 但是接口中的方法不行
  2. 抽象类中的成员变量可以是各种类型的, 而接口中的成员变量只能是public static final类型的
  3. 接口总不能含有静态代码以及静态方法(用static修饰的方法), 而抽象类是可以有静态代码块和静态方法
  4. 一个类只能继承一个抽象类, 而一个类却可以实现多个接口
接口的声明

语法格式

[可见度] 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接口被HockeyFootball接口继承:

// 文件名: 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提供了包机制, 用于区别类名的命名空间

包的作用
  1. 把功能相似或相关的类或接口组织在同一个包中, 方便类的查找和使用
  2. 如同文件夹一样, 包也采用树形目录的存储方式. 同一个包中的类名字是不同的, 不同的包中的类的名字是可以相同的, 当同时调用两个不同包中相同类名的类时, 应该加上包名加以区别. 因此, 包可以避免名字冲突.
  3. 包也限定了访问权限, 拥有包访问权限的类才能访问某个包中的类

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
  • 路径名 -> vehicleCar.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

你可以像下面这样来导入所有comrunoobtest中定义的类、接口等:

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>\classesclass pathpackage 名字是 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
tmp
向上造型

子类的对象可以向上造型为父类的类型。即父类引用子类对象,这种方式被称为向上造型。

高级教程

数据结构

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

这里通过实例列出IteratorlistIterator接口提供的所有方法。

遍历 ArrayList

实例

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集合,第三种方法是采用迭代器的方法,该方法可以不用担心在遍历的过程中会超出集合的长度。

遍历 Map

实例

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 中引入的一个新特性, 泛型提供了编译时类型安全检测机制, 该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数。

假定我们有这样一个需求:写一个排序方法, 能够对整型数组、字符串数组甚至其他任何类型的数组进行排序, 该如何实现?
答案是可以使用 Java 泛型。
使用 Java 泛型的概念, 我们可以写一个泛型方法来对一个对象数组排序。然后, 调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。
泛型方法

你可以写一个泛型方法, 该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型, 编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔), 该类型参数声明部分在方法返回类型之前(在下面例子中的<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

字符串为 :菜鸟教程
类型通配符
通配符T,E,K,V

这些全都属于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
ee
  1. 类型通配符一般是使用?代替具体的类型参数。例如 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都可以作为这个方法的实参, 这就是通配符的作用

  1. 类型通配符上限通过形如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是不在这个范围之内, 所以会报错

  1. 类型通配符下限通过形如 List<? super Number>来定义, 表示类型只能接受Number及其三层父类类型, 如Object类型的实例。
序列化

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。

整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

ObjectInputStreamObjectOutputStream 是高层次的数据流,它们包含序列化和反序列化对象的方法。

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 编程时比较有用的方法:

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.jaractivation.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

001

  • 新建状态:
    • 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态:
    • 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态:
    • 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态:
    • 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
      • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
      • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
      • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:
    • 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的优先级

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。


创建一个线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。
通过实现 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 创建线程

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 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;
    }
}
创建线程的三种方式的对比
  1. 采用实现 Runnable、Callable 接口的方式创见多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  2. 使用继承 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)
案例

ArrayList集合存储自定义对象的排序

模拟斗地主洗牌和发牌

模拟斗地主洗牌和发牌并对牌进行排序

Collection集合总结
  • Collection
    • |–List 有序,可重复
      • |–ArrayList
        • 底层数据结构是数组,查询快,增删慢。
        • 线程不安全,效率高
      • |–Vector
        • 底层数据结构是数组,查询快,增删慢。
        • 线程安全,效率低
      • |–LinkedList
        • 底层数据结构是链表,查询慢,增删快。
        • 线程不安全,效率高
    • |–Set 无序,唯一
      • |–HashSet
        • 底层数据结构是哈希表。
        • 如何保证元素唯一性的呢?
          • 依赖两个方法:hashCode()和equals()
          • 开发中自动生成这两个方法即可
      • |–LinkedHashSet
        • 底层数据结构是链表和哈希表
        • 由链表保证元素有序
        • 由哈希表保证元素唯一
      • |–TreeSet
        • 底层数据结构是红黑树。
        • 如何保证元素排序的呢?
          • 自然排序
          • 比较器排序
        • 如何保证元素唯一性的呢?
          • 根据比较的返回值是否是0来决定
在集合中常见的数据结构(掌握)
  • ArrayXxx:底层数据结构是数组,查询快,增删慢
  • LinkedXxx:底层数据结构是链表,查询慢,增删快
  • HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()equals()
  • TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序
针对Collection集合我们到底使用谁
  • 是否唯一
    • 是:Set
      • 排序吗?
        • 是:TreeSet
        • 否:HashSet
      • 如果你知道是Set,但是不知道是哪个Set,就用HashSet。
    • 否:List
      • 要安全吗?
        • 是:Vector
        • 否:ArrayList或者LinkedList
          • 查询多:ArrayList
          • 增删多:LinkedList
      • 如果你知道是List,但是不知道是哪个List,就用ArrayList。
  • 如果你知道是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>
案例
统计一个字符串中每个字符出现的次数
集合的嵌套遍历
  • HashMap嵌套HashMap
  • HashMap嵌套ArrayList
  • ArrayList嵌套HashMap
  • 多层嵌套
Set集合(理解)
Set集合的特点

无序,唯一

HashSet集合(掌握)
  • 底层数据结构是哈希表(是一个元素为链表的数组)
  • 哈希表底层依赖两个方法:hashCode()和equals()
    • 执行顺序:
      • 首先比较哈希值是否相同
        • 相同:继续执行equals()方法
          • 返回true:元素重复了,不添加
          • 返回false:直接把元素添加到集合
        • 不同:就直接把元素添加到集合
  • 如何保证元素唯一性的呢? - 由hashCode()和equals()保证的
  • 开发的时候,代码非常的简单,自动生成即可。
  • HashSet存储字符串并遍历
  • HashSet存储自定义对象并遍历(对象的成员变量值相同即为同一个元素)
TreeSet集合
  • 底层数据结构是红黑树(是一个自平衡的二叉树)
  • 保证元素的排序方式
    • 自然排序(元素具备比较性)
      • 让元素所属的类实现Comparable接口
    • 比较器排序(集合具备比较性)
      • 让集合构造方法接收Comparator的实现类对象
  • 把我们讲过的代码看一遍即可
案例
  • 获取无重复的随机数
  • 键盘录入学生按照总分从高到底输出
高级教程

Spring

Spring
  1. 使用POJO进行轻量级和最小侵入式开发
  2. 通过依赖注入和基于接口编程实现松耦合
  3. 通过AOP和默认习惯进行声明式编程
  4. 使用AOP和模板(template)减少模式化代码
Swagger2构建RESTful API
tmp
访问mysql
前提
  • JDK 1.8或更高
  • Gradle 2.3+ or Maven 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设置为updatenone, 当你想修改表结构的时候, 使用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

你可以在命令行使用 GradleMaven. 或者你可以构建成一个包含所有依赖, 类, 资源文件的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"}]
异常处理

http://www.jianshu.com/p/12e1a752974d

使用加强Controller做全局异常处理。

模板引擎
静态资源访问

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。路径可修改

Thymeleaf

http://www.thymeleaf.org/

示例

<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

Lombok

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again. Early access to future java features such as val, and much more.

https://projectlombok.org/

https://github.com/rzwitserloot/lombok

@Log

直接查看官方的概述

Overview

lib

tmp

  • feign
Java 正则
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public static boolean isContainChinese(String str) {
    # 字符串里面是否有中文
    Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
    Matcher m = p.matcher(str);
    if (m.find()) {
        return true;
    }
    return false;
}

测试题

final,finally,finalize的区别
如果在catch里面有return,请问finally还执行吗?如果执行,在return前还是后

会,前。

  1. finally语句在try和catch语句中的return执行后、返回前执行;
  2. 若finally语句中没有return, 则其执行结果不影响try和catch中已确定的返回值;
  3. 若finally语句中有return,则其执行后的结果会直接返回。

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系统目录结构类似),它的顶点也是根(“.”),只不过这个根是用点来表示的,而不是目录的根斜线。

递归和迭代
简单的讲
递归是重复调用模块自身实现循环。
迭代是函数内某段代码实现循环。

OID获取

http://www.mamicode.com/info-detail-1391074.html

华为

S5700-28C-EI

找到对应版本号

V200R003C00SPC300

S5700-01

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

  1. 找到MIB Locator,并点进去
Cisco-MIB

Cisco-MIB

  1. 选择SNMP Object Navigator这一项,并登陆思科账号

  2. 在SNMP Object Navigator里,选择MIB SUPPORT-SW ,将要查找OID 的交换机的IOS 名称填写进image-name框中,点击search,会出来交换机所有的MIB 库

  3. 根据所使用的snmp版本选择对应的v1或v2,查找相对应的OID 库,这里我以环境OID 为例。

  4. 找到CISCO-ENVMON-MIB,点击后面的V2

  5. 按CTRL+F,查找Temperature,copy ciscoEnvMonTemperatureStatusValue,注意要找值一定是在OBJECT-TYPE前面

  6. 在SNMP Object Navigator里,选择TRANSLATE/BROWSE,将刚刚复制的值粘贴到object name里面,点击Translate,得出相应的OID值为1.3.6.1.4.1.9.9.13.1.3.1.3

  7. 在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配置

tips

命令前几位唯一的时候, 可以使用缩写

例如, display可以使用简写dis

帮助信息

输入 ? 会直接显示帮助信息

示例:

<S5700-28C-EI_1>?
<S5700-28C-EI_1>dis?

<S5700-28C-EI_1>display ?
常用命令
查看Mac地址
display mac-address

查看Mac及对应IP,ARP缓存表

display arp
查看当前生效的配置信息
display current-configuration

对于正在生效的缺省配置,此命令不予显示。

进入系统视图
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

Acess端口收报文

收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有则直接丢弃(缺省)

Acess端口发报文

将报文的VLAN信息剥离,直接发送出去

trunk

trunk端口收报文

收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有判断该trunk端口是否允许该 VLAN的数据进入:如果可以则转发,否则丢弃

trunk端口发报文

比较端口的PVID和将要发送报文的VLAN信息,如果两者相等则剥离VLAN信息,再发送,如果不相等则直接发送

hybrid

hybrid端口收报文

收到一个报文,判断是否有VLAN信息:如果没有则打上端口的PVID,并进行交换转发,如果有则判断该hybrid端口是否允许该VLAN的数据进入:如果可以则转发,否则丢弃(此时端口上的untag配置是不用考虑的,untag配置只对发送报文时起作用)

hybrid端口发报文

  1. 判断该VLAN在本端口的属性(disp interface 即可看到该端口对哪些VLAN是untag, 哪些VLAN是tag)
  2. 如果是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
  1. 执行命令 system-view ,进入系统视图。
  2. (可选)执行命令 snmp-agent ,启动SNMP Agent服务。

缺省情况下,没有启动SNMP Agent服务。执行任意snmp-agent的配置命令(无论是否含参数)都可以触发SNMP Agent服务启动,故该步骤可选。

  1. 执行命令 snmp-agent sys-info version v2c ,配置SNMP的协议版本为SNMPv2c。缺省情况下,使能SNMPv3。

说明:

使用SNMPv2c存在安全风险,建议您使用SNMPv3管理设备。

  1. 执行命令 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。

  1. 执行命令 snmp-agent target-host trap-paramsname paramsname v2c securityname securityname [ binding-private-value ] [ private-netmanager ] ,配置设备发送Trap报文的参数信息。
  2. 执行命令 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端口号更改为非知名端口号,保证网管和被管理设备的正常通信。

  1. (可选)执行命令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 cudis cu 等都可以执行此命令,但不能输入 d cdis 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命令行一般用来恢复缺省情况,禁用某个功能或者删除某项配置.几乎每条配置命令都有对应的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命令均支持管道符

  1. | begin regular-expression:输出以匹配指定正则表达式的行开始的所有行。 即过滤掉所有待输出字符串,直到出现指定的字符串(此字符串区分大小写)为止,其后的所有字符串都会显示到界面上。
  2. | exclude regular-expression:输出不匹配指定正则表达式的所有行。 即待输出的字符串中没有包含指定的字符串(此字符串区分大小写),则会显示到界面上;否则过滤不显示。
  3. | include regular-expression:只输出匹配指定正则表达式的所有行。 即待输出的字符串中如果包含指定的字符串(此字符串区分大小写),则会显示到界面上;否则过滤不显示。
首次登陆系统

当用户需要为第一次上电的设备进行配置时,可以通过Console口或MiniUSB口登录设备。 设备提供一个Console口和一个MiniUSB口。用户终端的串行口可以与设备Console口直接连接或者将用户终端的USB口与设备MiniUSB口直接连接,实现对设备的本地配置。

说明:

  1. 在通过MiniUSB口登录设备前,需要在用户终端安装MiniUSB口的驱动程序。
  2. 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
CentOS7
使用cobbler
使用cobbler自动化安装,配置内核参数net.ifnames=0 biosdevname=0,安装即可
CentOS-7 全新安装,直接修改网卡为eth0
光盘安装的时候,在安装界面做如下操作:
点击Tab,打开kernel启动选项后,增加net.ifnames=0 biosdevname=0,如下图所示。
CentOS7-1

CentOS7-1

CentOS7-2

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
修改grub
[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之间的差异
启动方式的差异
SystemV init
特点
1. 系统第1个进程(pid=1)为init
2. init进程是所有进程的祖先,kill不了,只能自杀
3. 大多数Linux发行版的init系统是和SystemV相兼容的,被称为sysvinit
4. 代表系统:CentOS6
应用场景
应用于服务器和PC机的时代。
优点&缺点
SysV init运行非常良好,概念简单清晰。
它主要依赖于Shell脚本,这就决定了它的最大弱点:启动太慢
未来的趋势
移动平台,需要启动快的系统
应运而生的技术,Upstart技术
因竞争对手太强,被淘汰 代表系统:Ubuntu14,从Ubuntu15开始使用systemd
Systemd技术
  • 新系统都会采用的技术(RedHat7,CentOS7,Ubuntu15等);
  • 设计目标是客服sysvinit固有的缺点,提高系统的启动速度;
  • 和Sysvinit兼容,降低迁移成本;
  • 最主要有点:并行启动。
三种启动技术对比
start-technology

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区别
/etc/inittab
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命令区别:

+-----------------+-------------------------+------------------------------------+
|  Sysvinit 命令  |      Systemd 命令       |                备注                |
+=================+=========================+====================================+
| service foo     | systemctl start         | 用来启动一个服务                   |
| start           | foo.service             | (并不会重启现有的)                 |
+-----------------+-------------------------+------------------------------------+
| service foo     | systemctl stop          | 用来停止一个服务                   |
| stop            | foo.service             | (并不会重启现有的)。               |
+-----------------+-------------------------+------------------------------------+
| service foo     | systemctl restart       | 用来停止并启动一个服务。           |
| restart         | foo.service             |                                    |
+-----------------+-------------------------+------------------------------------+
| service foo     | systemctl reload        | 当支持时,重新装载配置文件而       |
| reload          | foo.service             | 不中断等待操作。                   |
+-----------------+-------------------------+------------------------------------+
| service foo     | systemctl condrestart   | 如果服务正在运行那么重启它。       |
| condrestart     | foo.service             |                                    |
+-----------------+-------------------------+------------------------------------+
| service foo     | systemctl status        | 汇报服务是否正在运行。             |
| status          | foo.service             |                                    |
+-----------------+-------------------------+------------------------------------+
| ls              | systemctl               | 用来列出可以启动或停止的服务列表。 |
| /etc/rc.d/init. | list-unit-files         |                                    |
| d/              | –type=service           |                                    |
+-----------------+-------------------------+------------------------------------+
| chkconfig foo   | systemctl enable        | 在下次启动时或满足其他             |
| on              | foo.service             | 触发条件时设置服务为启用           |
+-----------------+-------------------------+------------------------------------+
| chkconfig foo   | systemctl disable       | 在下次启动时或满足其他触发条件时设 |
| off             | foo.service             | 置服务为禁用                       |
+-----------------+-------------------------+------------------------------------+
| chkconfig foo   | systemctl is-enabled    | 用来检查一个服务在当前环境下被配置 |
|                 | foo.service             | 为启用还是禁用。                   |
+-----------------+-------------------------+------------------------------------+
| chkconfig –list | systemctl               | 输出在各个运行级别下服务的启用和禁 |
|                 | list-unit-files         | 用情况                             |
|                 | –type=service           |                                    |
+-----------------+-------------------------+------------------------------------+
| chkconfig foo   | ls                      | 用来列出该服务在哪些运行           |
| –list           | /etc/systemd/system     | 级别下启用和禁用。                 |
|                 | /\*.wants/foo.service |                                    |
+-----------------+-------------------------+------------------------------------+
| chkconfig foo   | systemctl daemon-reload | 当您创建新服务文件或者             |
| –add            |                         | 变更设置时使用。                   |
+-----------------+-------------------------+------------------------------------+
| telinit 3       | systemctl isolate       | 改变至多用户运行级别。             |
|                 | multi-user.target (OR   |                                    |
|                 | systemctl isolate       |                                    |
|                 | runlevel3.target OR     |                                    |
|                 | telinit3)               |                                    |
+-----------------+-------------------------+------------------------------------+
命令补全

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
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使用Python3一直报UnicodeEncodeError

UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 34-42: ordinal not in range(128)

解决方法:

修改文件/etc/locale.conf,增加如下配置项:
LC_ALL="en_US.UTF-8"
LC_CTYPE="en_US.UTF-8"
LC_ALL和LC_CTYPE具体的值视情况,可以配置为zh_CN.UTF-8,或者 locale -a 命令返回的字符集
在centos7下安装libiconv-1.14.tar.gz时遇到如下错误:
./stdio.h:1010:1: error: 'gets' undeclared here (not in a function)
解决方法如下:

vi  srclib/stdio.h

找到

/* It is very rare that the developer ever has full control of stdin,
so any use of gets warrants an unconditional warning.  Assume it is
always declared, since it is required by C89.  */
_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");

然后去掉:
_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");

改成:
#if defined(__GLIBC__) && !defined(__UCLIBC__) && !__GLIBC_PREREQ(2, 16)
_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
#endif

重新 make && make install, 问题解决。
安装了错误的源
  • 需要移除源 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

mac

MacBook系统安装
系统安装

制作U盘启动盘

  1. 重启, 听到一声响之后, 按住Options键
  2. 选择安装系统
  3. 进入系统安装界面之后, 先格式化原系统盘(如需要, 先连接WiFi)
  4. 安装, 重启
MacBook安装win10双系统

keyword: Mac双系统

BootCamp 6.x安装双系统不需要U盘

打开BootCamp按照步骤走就行,很简单

  1. 选择iso镜像
  2. 设置分区大小
  3. 下载Windows支持软件(根据网速快慢,可能会需要很长时间)
  4. 下载完成后会自动进行磁盘分区,拷贝Windows文件等等
  5. 格式化BOOTCAMP分区,正常流程安装Windows即可
  6. 安装OSXRESERVED下相关驱动,找到BootCamp,setup即可
  7. 重启之后,右下角Boot Camp控制面板可以设置触摸板等等
  8. 如需要删除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

MacOS-5

Mac 快捷键

Control-Command-F 全屏

Option-Command-D 显示或隐藏 Dock

Command-P 显示“打印”对话框

Option-Command-esc 强制退出

MacOS-6

MacOS-6

Safari
快捷键
标签(tab)操作
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:进入阅读器模式
书签(bookmarks)操作
control+command+1: 显示书签边栏
command+D:添加到收藏夹(书签栏)
option(alt)+command+B:管理/编辑书签页
shift+command+N:新建书签文件夹
阅读列表(Read it later list)
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+,:偏好设置->扩展
禁用Dashboard

关闭Dashboard

System Preferences -> Mission Control -> 设置Dashboard即可

终端执行

defaults write com.apple.dashboard mcx-disabled -boolean YES && killall Dock

重新开启

defaults write com.apple.dashboard mcx-disabled -boolean NO && killall Dock

空间释放
完全删除GarageBand

参考

sudo rm -rf /Library/Application\ Support/GarageBand/
sudo rm -rf /Library/Application\ Support/Logic
sudo rm -rf /Library/Audio/Apple\ Loops
sudo rm -rf /Applications/GarageBand.app
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下命令行工具
gnu-sed
brew install gnu-sed --with-default-names
终端查看Mac系统版本

sw_vers

➜  ~ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G31
查看端口情况
netstat -nat |grep LISTEN

lsof -n -P -i TCP -s TCP:LISTEN

# lsof命令可以列出当前的所有网络情况, 此命令的解释如下:
# -n 表示主机以ip地址显示
# -P 表示端口以数字形式显示,默认为端口名称
# -i 意义较多,具体 man lsof, 主要是用来过滤lsof的输出结果
# -s 和 -i 配合使用,用于过滤输出
mtr
Windows

WinMTR

mtr命令使用
➜  ~ 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
TextEdit打开文件中文乱码

Mac查看txt中文乱码

解决办法:

MacOS-TextEdit-01

MacOS-TextEdit-01

Mac压缩包解压后乱码

使用解压软件 The Unarchiver 来解压 Zip 格式的文件。

App Store The Unarchiver

在压缩包上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, 听到开机声音响四声后再松开。一定要同时按! 然后可能就可以用了。

破解密码
  1. 开机瞬间按住command + R
  2. 出现苹果logo进度条后, 会进入恢复界面
  3. 恢复界面工具栏选择 实用工具 -> 终端
  4. 终端界面输入 resetpassword
  5. 弹出重置密码界面, 选择登录用户, 下一步
  6. 设置开机密码, 重启电脑

重启后, 如果Mac提示输入钥匙串密码, 可以到实用工具->钥匙串(点击左上角锁锁住, 再点开, 再点锁住, 会提示重设钥匙串密码)

设置好新钥匙串密码后, 可以在顶部菜单选择解锁所有程序的钥匙串, 不解锁的话, 以后进入程序都会提示输入密码.

Mac安装jdk
下载

下载页面

MacOS-jdk-02

MacOS-jdk-02

MacOS-jdk

MacOS-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备份
  1. 私钥公钥
  2. 相关软件配置文件
    1. iTerm2
Mac恢复
命令行恢复

oh-my-zsh

# 安装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)"
iTerm2恢复

解压配置文件到如下目录,进入到iTerm2配置,勾选配置

ITerm2-save history to disk-2

ITerm2-save history to disk-2

鼠标放置屏幕右上角锁屏
MacOS-1

MacOS-1

MacOS-2

MacOS-2

触摸板配置
MacOS-3

MacOS-3

三指拖动选择

MacOS-4

MacOS-4

vscode 恢复

可以直接使用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",
}
搜狗输入法,使用QQ登录

皮肤: 书写优雅

常用软件
  • 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模式
  1. 手动进入DFU模式:开机状态,用数据线跟电脑连接好。先按住关机键2秒,然后,同时按住关机键和Home键8-10秒;最后,只按住Home键15秒。
  2. 检查否正确进入DFU模式:在DFU模式下,手机是黑屏的。如果没有成功,请重新进行第一步。
使用iTunes下载固件并恢复

直接点击恢复按钮,等待下载完成,并等待完成恢复即可

使用已经下载的固件恢复
iPhone-01

iPhone-01

iPhone-02

iPhone-02

iPhone-03

iPhone-03

等待手机完成

退出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,看个人习惯了
其他快捷键2
选择即复制 + 鼠标中键粘贴,这个很实用
⌘ + f 所查找的内容会被自动复制
输入开头命令后 按 ⌘ + ; 会自动列出输入过的命令
⌘ + shift + h 会列出剪切板历史
可以在 Preferences > keys 设置全局快捷键调出 iterm,这个也可以用过 Alfred 实现
高亮当前鼠标的位置

一个标签页中开的窗口太多,有时候会找不到当前的鼠标,cmd + / 找到它。

Linux 下好用的组合
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,则自动将查找内容的左边选中并复制。
路径重复

在新Tab中自动使用前一Tab路径,如此设置

iTerm2-Working Directory-2017221

iTerm2-Working Directory-2017221

系统热键*****

如下图,设置好系统热键之后,将在正常的浏览器或者编辑器等窗口的上面,以半透明窗口形式直接调出iterm2 shell。

按下同样的系统热键之后,将自动隐藏。这样非常有利于随时随地处理。

iTerm2-Hotkey-2017221

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

ITerm2-save history to disk-2017221

全屏切换
command+enter 进入  返回全屏模式
Expose所有Tab(全屏展示所有的 tab)
command+option+e,并且可以搜索
保存当前快照
Window > Save Window Arrangement.
恢复快照:
Window > Restore Window Arrangement
    可以在Preferences > General > Open saved window arrangement.设置自动恢复快照
一些实用功能
Utilities Package

You will also have these commands:

imgcat filename

Displays the image inline.

it2dl filename

Downloads the specified file, saving it in your Downloads folder.

Broadcast Input(对多会话同时操作)
ITerm2-Broadcast Input

ITerm2-Broadcast Input

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

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

windows相关笔记

搜狗输入法皮肤,自己制作

搜狗输入法皮肤下载

Shadowsocks

Shadowsocks-3.3.6

Office365卸载工具

微软官方Office+卸载工具

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

Installing Java 8 on Ubuntu

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安装C/C++编译器

安装C/C++编译器

apt-get install gcc

gcc安装相关构建工具

apt-get install build-essential
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
查看系统信息
  1. cat /etc/issue
  2. cat /proc/version
  3. uname -a
  4. lsb_release -a
  5. cat /etc/lsb-release
Ubuntu网络
ubuntu 网卡配置文件
root@ubuntu:~# vim /etc/network/interfaces
重启网卡
root@ubuntu:~# /etc/init.d/networking restart
获取IP
dhclient eth1
APT
配置文件路径
/etc/apt/
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
16.04
网易源
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-get

常用参数

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源里面的包
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
问题记录
系统重启后resolv.conf被清空

自定义nameserver, 具体相关信息运行命令man resolvconf

  1. 在网卡的配置文件里面加
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
  1. 修改 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命令发送邮件
安装heirloom-mailx

mail命令在Ubuntu下是需要安装的

sudo apt-get install heirloom-mailx
配置

修改如下文件

  • 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
  1. 交互形式发送邮件

    mail + 邮箱地址,回车 -> 填写主题 -> 填写内容 -> ctrl + d 结束输入 -> cc代表抄送,回车完成发送

  2. 使用管道发送

    echo “邮件内容” | mail -s “主题” 邮箱地址

  3. 读取文件发送

    mail -s “主题” “邮箱地址” < “path/filename”

有些敏感的内容,可能会被屏蔽,换内容继续尝试

Ubuntu使用zsh
安装zsh
apt-get install zsh

安装完后,需要将zsh替换为你的默认shell, 输入下面命令修改默认终端

chsh -s /bin/zsh

重新打开终端即可

安装oh-my-zsh

官网

GitHub地址

详细信息可以查看GitHub

通过curl安装
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
通过wget安装
sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
报错
chsh: PAM: Authentication failure
sudo vim /etc/pam.d/chsh

auth required pam_shells.so 注释,再次执行 sudo chsh -s which 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进入单用模式方法
  1. 按任意键停留在grub菜单
  2. 选择 Advanced options for Ubuntu
  3. 再选择:recovery mode 按e进入编辑状态
  4. 将linux开头那行的“ro recovery nomodeset”改为 “rw single init=/bin/bash” (注意:ro 是只读模式,rw是读写模式。) 然后按Ctrl + x, 进入单用户模式

windows

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”,那么配置文件的相应路径是“DataPackagesNil-ThemeNil.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系统出现中文乱码?

这是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,
Windows
Windows 10 svchost.exe一直占用网速

关闭并禁用 Background Intelligent Transfer Service 服务

window 查看系统版本
  1. WIN+R -> cmd -> slmgr/dlv
  2. WIN+R -> dxdiag
  3. WIN+R -> winver
windows 远程链接远程windows服务器

WIN+R —> mstsc

windows cmd 过滤字符
systeminfo |findstr 401
netstat -ano|findstr "1433"

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文件,就执行它,在这些文件中又可能调用了其他配置文件,所有的配置文件执行后,各种环境变量也设好了,这时会出现大家熟悉的命令行提示符,至此整个启动过程就结束了。

os

Linux kernel map
Linux-kernel-map

Linux-kernel-map

系统信息
mac
system_profiler命令:显示Mac的硬件和软件信息
sw_vers命令:OSX系统版本
uname命令:显示操作系统名
Linux

查看内核版本

uname -a

cat /proc/version

查看Linux版本

lsb_release -a

/etc/issue
/etc/system-release
/etc/redhat-release

/etc/centos-release

# ubuntu
/etc/os-release

python

参考链接

basis

Python基础

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
简单入门
Hello World
命令行输入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!
指定python解释器
1. #!/usr/bin/python       ## 告诉shell使用/usr/bin/python执行
2. #!/usr/bin/env python   ## 操作系统环境不同的情况下指定执行这个脚本用python来解释
#!/usr/bin/env python3
执行python文件
  1. python hello.py
  2. chmod +x hello.py && ./hello.py
指定字符编码
  1. # _*_ coding:utf-8 _*_
  2. # -*- coding:utf-8 -*-
  3. # coding:utf-8
代码注释
单行注释
# 只需要在代码前面加上 '#' 号
多行注释

多行注释用三个单引号或者三个双引号

"""
注释内容
"""
print 输出多行
➜  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.
变量

命名规则

  1. 变量只能包含数字、字母、下划线
  2. 不能以数字开头
  3. 变量名不能使用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中变量工作方式

  1. 变量在它第一次赋值时创建;
  2. 变量在表达式中使用时将被替换成它们所定义的值;
  3. 变量在表达式中使用时必须已经被赋值,否则会报name 'xxx is not defined';
  4. 变量像对象一样不需要在一开始进行声明.
动态类型模型
>>> 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'

上述实例,可以理解垃圾回收机制是如何工作的

  1. 创建一个变量name,值通过指针指向yjj的内存地址;
  2. 如果yjj这个值之前没有在内存中创建,那么现在创建他,并让这个内存地址的引用数+1,此时等于1
  3. 然后对变量name重新赋值,让其指针指向zt的内存地址;
  4. 那么此时yjj的值的引用数就变成0,当python检测到某个内存地址的引用数等于0时,就会把这个内存地址给删掉,从而释放内存;
  5. 最后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
列表

创建列表

name_list = ['yang', 'six', 'liu']

name_list = list(['yang', 'six', 'liu'])
元组(不可变列表)

创建元组

ages = (11, 22, 33, 44)

ages = tuple((11, 22, 33, 44))
字典(无序)

创建字典

person = {"name": "yang", "age": "18"}

或者

person = dict({"name": "yang", "age": "18"})
流程控制
if语句
单条件
➜  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
while循环

定义一个变量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...
break

跳出当前循环体

➜  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...
continue

跳出本次循环,继续下一次循环

➜  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" "))
# 强制字符串转换
练习
使用while循环输出1 2 3 4 5 6 8 9 10
#!/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...
求1-100的所有数的和

思路:定义两个变量,分别是countnum,利用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-100 内的所有奇数

思路: 利用%整数相除的余,如果余数是1那么当前的count就是奇数,如果余0,那么当前的count就是偶数。

代码

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_

count = 1
while count <= 100:
    if count % 2 == 1:
        print(count)
    count += 1
输出 1-100 内的所有偶数

代码

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_

count = 1
while count <= 100:
    if count % 2 == 0:
        print(count)
    count += 1
求1-2+3-4+5 … 99的所有数的和
#!/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))

账号或密码连续三次输入错误则退出程序,并且每次提醒用户剩余多少次登陆的机会。

其他知识
bytes类型
三元运算
result = 值1 if 条件 else 值2

如果条件为真: result = 值1

如果条件为假: result = 值2

进制
  • 二进制,01
  • 八进制,01234567
  • 十进制,0123456789
  • 十六进制,0123456789ABCDEF
一切皆对象

对于python,一切事物都是对象,对象基于类创建

python数据类型分类
  • 不可变类型
    • 数字
    • 字符串
    • 元组
    • 不可变集合
  • 可变类型
    • 列表
    • 字典
    • 可变集合

我们学习的数据类型,只是在学习每一个类型所提供的API,我们所需要的大部分功能,python都已经帮我们封装好了,不需要担心效率的问题。

所有的数据类型所具备的方法都在相应的类里面.

对象是基于类的,也就是说如果我定义一个数据类型是字符串类型的,那么类型字符串就是,而定义的变量就是对象,对象所拥有的功能都是从类里面去拿的。

  1. 数字
    • int(整型)
    • long(长整型,python3.x里面都是int,没有long)
    • float(浮点型)
    • complex(复数)
  2. 布尔型(bool)
  3. 字符串(str)
  4. 列表(list)
  5. 元组(tuple)(不可变列表)
  6. 字典(dict)(无序)
  7. 集合(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

通过上的结果可以看到 var1var2 的内存地址是相同的,就代表它们的值是使用的同一块空间,当我们把 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位,可以存储从-2147483648214483647的整数。

一个长整(long)型会占用更多的空间,64位的可以存储-922372036854775808922372036854775808的整数。

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

内置的方法还有 denominatorfrom_bytesnumeratorrealto_bytes ,不常用,可以通过 help(int.numerator) 类似方法查看相关的帮助信息。

混合类型

所谓混合类型就是浮点数和整数进行运算

>>> 3.1415926 + 10
13.1415926

浮点数如何和一个正整数相加?python会把两个值转换为其中最复杂的那个对象的类型,然后再对相同类型运算。

数字类型的复杂度

整数比浮点数简单、浮点数比复数简单。

取整的方法
向下取整
>>> a = 4.75
>>> int(a)
4
向上取整
>>> import math
>>> math.ceil(2.32)
3
四舍五入
>>> round(3.35)
3
>>> round(3.67)
4
分别取整数部分和小数部分
>>> import math
>>> math.modf(3.25)
(0.25, 3.0)
>>> math.modf(3.75)
(0.75, 3.0)
>>> math.modf(4.2)
(0.20000000000000018, 4.0)

最后一个输出,涉及到了另一个问题,
即浮点数在计算机中的表示,在计算机中是无法精确的表示小数的,至少目前的计算机做不到这一点。
上例中最后的输出结果只是 0.2 在计算中的近似表示。
Python 和 C 一样, 采用 IEEE 754 规范来存储浮点数。
布尔类型(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'}
集合所提供的方法
add

往集合内添加元素

>>> se = {11,22,33}
>>> se
{33, 11, 22}
>>> se.add(44)
>>> se
{33, 11, 44, 22}
clear

清除集合内容

>>> se = {11,22,33}
>>>
>>>
>>> se
{33, 11, 22}
>>> se.clear()
>>> se
set()
copy浅拷贝
var = se.copy()
# 返回集合的浅copy
difference,差集

集合var1中存在,var2中不存在的元素

>>> var1 = {11,22,33}
>>> var2 = {22,55}
>>> var1.difference(var2)
{33, 11}
>>> var2.difference(var1)
{55}
difference_update

寻找集合var1中存在,var2中不存在的元素,并把查找出来的元素重新赋值给var1

>>> var1
{33, 11, 22}
>>> var2
{22, 55}
>>> var1.difference_update(var2)
>>> var1
{33, 11}
discard

移除指定元素,不存在不报错

>>> var1 = {11,22,33}
>>> var1.discard(11)
>>> var1
{33, 22}
>>> var1.discard(112111)
>>> var1
{33, 22}
remove

移除指定元素,不存在报错

>>> 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
intersection,交集

交集,查找元素中都存在的值

>>> var1 = {11,22,33}
>>> var2 = {22,55,77}
>>> var1.intersection(var2)
{22}
intersection_update

取交集并更新到var1中

>>> var1 = {11,22,33}
>>> var2 = {22,55,77}
>>> var1.intersection_update(var2)
>>> var1
{22}
isdisjoint

判断有没有交集,如果有返回False,否则返回True

>>> var1 = {11,22,33}
>>> var2 = {22,55,77}
>>> var1.isdisjoint(var2)
False
>>> var2 = {66,44,55}
>>> var1.isdisjoint(var2)
True
issubset

是否是子序列,也就是说如果var2的所有元素都被var1所包含,那么var2就是var1的子序列

>>> var1 = {11,22,33,44}
>>> var2 = {11,22}
>>> var2.issubset(var1)
True
issuperset

是否是父序列

>>> var1 = {11,22,33}
>>> var2 = {22,44,55}
>>> var1.issuperset(var2)
False
>>> var2 = {11,22}
>>> var1.issuperset(var2)
True
pop

移除一个元素,并显示移除的元素,移除时是无序的

>>> var1 = {11,22,33,44}
>>> var1.pop()
33
>>> var1
{11, 44, 22}
>>> var1.pop()
11
>>> var1
{44, 22}
symmetric_difference

对称交集,把var1存在且var2不存在和var2存在且var1不存在的元素合在一起

>>> var1 = {11,22,33,44}
>>> var2 = {11,55,66,44}

>>> var1.symmetric_difference(var2)
{33, 66, 22, 55}
symmetric_difference_update

对称交集,并更新到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}
union,并集

并集,把两个集合中的所有元素放在一起,如果有重复的则只存放一个

>>> var1 = {11,22,33,44}
>>> var2 = {11,55,66,44}
>>> var1.union(var2)
{33, 66, 11, 44, 22, 55}
update

更新,把一个集合中的元素更新到另一个集合中

>>> 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()
...
capitalize
首字母变大写
>>> name = "yang"
>>> name.capitalize()
'Yang'
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符号(\t)转为空格,tab符号(\t)默认的空格数是8

expandtabs(self,tabsize=None)

检测字符串中是否包含字符串str,如果指定beg(开始)和end(结束)范围,则检查是否包含在指定范围内,如果包含子字符串返回开始的索引值,否则返回-1

元组(tuple)

元组(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

  1. count(self, value) 查看元组中元素出现的次数
>>> ages = tuple((11,22,33,44,55))
>>> ages
(11, 22, 33, 44, 55)
>>> ages.count(11)
1
  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)

列表(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)在基本的数据类型中使用频率也是相当高的,而且它的访问方式是通过键来获取到对应的值,当然存储的方式也是键值对了,属于可变类型

特性:

  • 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
循环dict
# 遍历字典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)
setdefault
赋值与运算符
赋值
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)
流程控制
is 与 == 的区别
  • is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同。
  • == 比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了。默认会调用对象的 __eq__() 方法。
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

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

python-010-copy

浅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-010-deepcopy

总结
  • Python中对象的赋值都是进行对象引用(内存地址)传递
  • 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
  • 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
  • 对于非容器类型(如数字、字符串、和其他’原子’类型的对象)没有被拷贝一说
  • 如果元组变量只包含原子类型对象,则不能深拷贝,看下面的例子
函数

定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需要调用其函数名即可**

特性

  1. 减少重复代码
  2. 使程序变得可扩展
  3. 使程序变得易维护

定义函数

# x为函数的参数
def sum(x,y): # x,y为形参  # 函数名
    print(x+y)

sum(1,2)  # 1,2 实参  # 调用函数
函数的返回值

函数的返回值需要使用到return这个关键字,获取函数的执行结果.

注意:

  1. 函数在执行过程中只要遇到return语句,就会停止执行并返回结果
  2. 如果未在函数中指定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括号内制定的参数的值,那么就不需要按照顺序来了

如果给函数创建了默认值,那么有默认值的这个参数必须在最后面定义,不能够在没有默认参数的值的前面.

动态参数(非固定参数)

动态参数把接收过来的实际参数当做一个元组,每一个参数都是元组中的一个元素.

非固定参数 (*args)

定义第一种动态参数需要在参数前面加上一个*

>>> def ret(*args):  # *args 会把传入的参数变成一个元组形式
...   print(args,type(args))
...
>>> ret(11,22,33)
(11, 22, 33) <class 'tuple'>
非固定关键字参数 (**kwargs)

定义非固定关键字参数需要在参数前面加上两个 * 号,给参数传参的时候是一个key对应一个value, 相当于一个字典的键值对,而且返回的类型就是 字典类型.

使用两个星号可以将参数收集到一个字典中,参数的名字是字典的键,对应参数的值是字典的值.

>>> def ret(**kwargs):
...   print(kwargs,type(kwargs))
...
>>> ret(k1=123,k2=456)
{'k1': 123, 'k2': 456} <class 'dict'>
(*args, **kwargs)

万能动态参数,可接受所有传参

>>> 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
递归

在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数.

递归特性

  1. 必须有一个明确的结束条件
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用时通过栈(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

python-011-lambda

嵌套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)
测试题
简述参数区别
简述普通参数,指定参数,默认参数,动态参数的区别

普通参数即是用户在调用函数时填入的参数,且参数位置必须与参数保持一致

指定参数即在用户调用函数的时候不需要按照函数中参数的位置所填写,指定参数需要指定参数对应的值

默认参数可以写在定义参数的后面,如果用户调用函数时没有指定参数,那么就会用默认参数,如果用户指定了参数,那么用户指定的参数就会代替默认参数

动态参数可以接受用户输入的任何参数,包括字典,列表,元组等数据类型

练习2
计算传入字符串中数字,字母,空格以及其他字符的个数
➜  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)
练习3
写函数,判断用户传入的对象(字符串,列表,元组)长度是否大于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)
练习4
写函数,判断用户传入的对象(字符串,列表,元组)的每一个元素是否含有空内容,如果有空就返回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
练习5
写函数,检查传入列表的长度,如果大于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
练习6
写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者
#!/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]
练习7
写函数,检查传入字典的每一个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.

官方介绍

内置函数详解
abs(x)
返回数字的绝对值,参数可以是整数或浮点数,如果参数是复数,则返回其大小
>>> abs(-517)
517
all(iterable)
all()会循环括号内的每一个元素,如果括号内的所有元素都是真的,或者如果iterable为空,则返回True,如果有一个为假,那么久返回False
>>> all([])
True
>>> all([1,2,3])
True
>>> all([1,2,""])
False
>>> all([1,2,None])
False

假的参数有:False、0、None、“”、[]、()、{}等,查看一个元素是否为假可以使用bool进行查看。

any(iterable)
循环元素,如果有一个元素为真,那么久返回True,否则就返回False
>>> any([0,1])
True
>>> any([0])
False
ascii(object)
在对象的类中寻找__repr__方法,获取返回值
>>> class Foo:
...   def __repr_(self):
...     return "hello"
...
>>> obj = Foo()
>>> r = ascii(obj)
>>> print(r)
<__main__.Foo object at 0x101a9fe80>
# 返回的是一个可迭代的对象
bin(x)
将整数x转换为二进制字符串,如果x不为python中int类型,x必须包含方法__index__()并且返回值为integer
# 返回一个整数的二进制
>>> bin(999)
'0b1111100111'
>>> class myType:
...   def __index__(self):
...     return 35
...
>>> myvar = myType()
>>> bin(myvar)
'0b100011'
bool([x])
查看一个元素的布尔值,非真即假
>>> bool(0)
False
>>> bool(1)
True
>>> bool([1])
True
bytearray()

bytearray([source[,encoding[,errors]]])

返回一个byte数组,Bytearray类型是一个可变的序列,并且序列中的元素的取值范围为[0,255]

source参数:

  1. 如果source为整数,则返回一个长度为source的初始化数组;
  2. 如果source为字符串,则按照指定encoding将字符串转换为字节序列;
  3. 如果source为可迭代类型,则元素必须为[0,255]中的整数;
  4. 如果source为与buffer接口一致的对象,则此对象也可以被用于初始化bytearray.
>>> bytearray(3)
bytearray(b'\x00\x00\x00')
bytes()
bytes([source[,encoding[,errors]]])
>>> bytes("yjj",encoding="utf-8")
b'yjj'
callable(object)
返回一个对象是否可以被执行
>>> def func():
...   return 123
...
>>> callable(func)
True
>>> func = 123
>>> callable(func)
False
chr(i)
返回一个数字在ASCII编码中对应的字符,取值范围256
>>> chr(66)
'B'
>>> chr(5)
'\x05'
>>> chr(65)
'A'
classmethod(function)
返回函数的类方法
compile()

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()

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)
delattr(object,name)
删除对象的属性值
>>> 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'
>>>
dict (**kwarg)
创建一个数据类型为字典
>>> dic = dict({"k1":"123","k2":"456"})
>>> dic
{'k1': '123', 'k2': '456'}
dir(object)
返回一个对象中的所有方法
>>> 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']
divmod(a,b)
返回的是a//b(除法取整)以及a对b的余数,返回结果类型为tuple
>>> divmod(10,3)
(3, 1)
enumerate(iterable,start=0)
为元素生成下标
>>> li = ["a","b","c"]
>>> for n,k in enumerate(li):
...   print(n,k)
...
0 a
1 b
2 c
eval()

eval(expression,globals=None,locals=None)

把一个字符串当做一个表达式去执行
>>> string = "1+3"
>>> string
'1+3'
>>> eval(string)
4
exec()

exec(object[,globals[,locals]])

把字符串当做python代码执行
>>> exec("for n in range(5):print(n)")
0
1
2
3
4
filter(function,iterable)
筛选过滤,循环可迭代的对象,把迭代的对象当做函数的参数,如果符合条件就返回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([x])
将整数和字符串转换成浮点数
>>> float("123")
123.0
>>> float("123.45")
123.45
>>> float("-123.45")
-123.45
format()

format(value,[format_spec])

字符串格式化

详解

frozenset([iterable])
frozenset是冻结的集合,它是不可改变的,存在哈希值,好处是它可以作为字典的key,也可以作为其他集合的元素.缺点是一旦创建便不能更改,没有add,remove方法.
getattr(object,name[,default])
返回对象的命名属性的值,name必须是字符串,如果字符串是对象属性之一的名称,则结果是该属性的值.
globals
获取或修改当前文件内的全局变量
>>> 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'}
hasattr(object,name)
参数是一个对象和一个字符串,如果字符串是对象的某个属性的名称,则结果为True,否则为False
hash(object)
返回一个对象的hash值
>>> a = "afdafasd"
>>> hash(a)
2216836390023867832
help(object)
查看一个类的所有详细方法,或者查看某个方法的使用详细信息
>>> 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).
 ......
hex(x)
获取一个数的十六进制
>>> hex(19)
'0x13'
id(object)
返回一个对象的内存地址
>>> a = 123
>>> id(a)
4297558784
input([prompt])
交互式输入
>>> name = input("Please input your name: ")
Please input your name: yang
>>> print(name)
yang
int(x,base=10)
获取一个数的十进制
>>> int("51")
51
也可以做进制转换
>>> int(10)
10
>>> int('0b11',base=2)
3
>>> int('0xe',base=16)
14
isinstance(object,classinfo)
判断对象是否是这个类创建的
>>> li = [11,22,33]
>>> isinstance(li,list)
True
issubclass(class,classinfo)

issubclass()

查看一个对象是否为子类
iter(object[,sentinel])
创建一个可迭代的对象
>>> obj = iter([11,22,33,44])
>>> obj
<list_iterator object at 0x101bfe048>
>>> for n in obj:
...   print(n)
...
11
22
33
44
len(s)
查看一个对象的长度
>>> url="yang"
>>> len(url)
4
list([iterable])
创建一个数据类型为列表
>>> li = list([11,22,44])
>>> li
[11, 22, 44]
locals()
返回当前作用域的局部变量,以字典形式输出
>>> def func():
...   name="yang"
...   print(locals())
...
>>> func()
{'name': 'yang'}
map(function,iterable,…)
对一个序列中的每一个元素都传到函数中执行并返回
>>> list(map((lambda x : x + 10),[11,22,33,44]))
[21, 32, 43, 54]
max()

max(iterable,*[,key,default])

max(arg1,arg2,*args[,key])

取一个对象中的最大值
>>> li = [11,22,33,44]
>>> li
[11, 22, 33, 44]
>>> max(li)
44
memoryview(obj)
返回对象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()

min(iterable,*[,key,default])

min(arg1,arg2,*args[,key])

取一个对象中的最小值
>>> li = list([11,22,33])
>>> min(li)
11
object
返回一个新的无特征对象
oct(x)
获取一个字符串的八进制
>>> oct(15)
'0o17'
open()

open(file,mode=‘r’,buffering=-1,encoding=None,errors=None,newline=None,closefd=True,opener=None)

文件操作的函数,用来做文件操作
>>> f = open("a.txt","r")
ord(c)
把一个字母转换为ASCII对应表中的数字
>>> ord("a")
97
pow(x,y[,z])
返回一个数的N次方
>>> pow(2,10)
1024
>>> pow(2,20)
1048576
print()
print(*objects,sep='',end=`\n`,file=sys.stdout,flush=False)

打印输出
>>> print("Hello World")
Hello World
properyt()

properyt(fget=None,fset=None,fdel=None,doc=None)

range(start,stop[,step])
生成一个序列
>>> range(10)
range(0, 10)
>>> for n in range(5):
...   print(n)
...
0
1
2
3
4
repr(object)
返回一个包含对象的可打印表示的字符串
>>> repr(1110)
'1110'
>>> repr(111.11)
'111.11'
reversed(seq)
对一个对象的元素进行反转
>>> li = [11,22,33,44]
>>> reversed(li)
<list_reverseiterator object at 0x101e96518>
>>> for n in reversed(li):
...   print(n)
...
44
33
22
11
round(number[,ndigits])
四舍五入
>>> round(3.3)
3
>>> round(3.7)
4
set([iterable])
创建一个数据类型为集合
>>> var = set([11,22,33])
>>> type(var)
<class 'set'>
setattr()

setattr(object,name,value)

为某个对象设置一个属性
slice()

slice(start,stop[,step])

元素的切片操作都是调用的这个方法
sorted(iterable[,key][,reverse])
为一个对象的元素进行排序
>>> sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]
staticmethod(function)
返回函数的静态方法
str()

str(object=b’‘,encoding=’utf-8’,errors=‘strict’)

字符串
>>> a = str(111)
>>> type(a)
<class 'str'>
sum()

sum(iterable[,start])

求和
>>> sum([11,22,33])
66
super()

super([type[,object-or-type]])

执行父类的构造函数
tuple([iterable])
创建一个对象,数据类型为元组
>>> tup = tuple([11,22,33,44])
>>> type(tup)
<class 'tuple'>
type(object)
查看一个对象的数据类型
>>> tup = tuple([11,22,33,44])
>>> type(tup)
<class 'tuple'>
vars(object)

查看一个对象里面有多少个变量

zip(*iterables)
将两个元素相同的序列转换为字典
 >>> li1 = ["k1","k2","k3"]
>>> li2 = ["a","b","c"]
>>> d = dict(zip(li1,li2))
>>> d
{'k2': 'b', 'k1': 'a', 'k3': 'c'}
import()

import(name,globals=None,locals=None,fromlist=(),level=0)

导入模块,把导入的模块作为一个别名
生成随机验证码例子
生成一个六位的随机验证码,且包含数字,数字的位置随机
#!/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
易犯错误
起的模块的名字不能和导入的库名相同

如下代码, 该文件名不能叫 sys.py, 会引起不必要的误, 系统不知道要导入什么模块

import sys
print(sys.path)
Win字符编码深解

advance

文件操作

python可以对文件进行查看,创建等功能,可以对文件内容进行添加,修改,删除,且所使用到的函数在python3.5.x中为open,在python2.7.x中同时支持fileopen,但是在3.5.x系列移除了file函数

对文件操作流程

  1. 打开文件,得到文件句柄并赋值给一个变量
  2. 通过句柄对文件进行操作
  3. 关闭文件
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
read
#!/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
readline
#!/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
readlines
#!/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文件写入方式
write
#!/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
writelines
#!/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文件操作所提供的方法
close(self)

关闭已经打开的文件

f.close()
fileno(self)

文件描述符

>>> f = open("hello.txt","r")
>>> ret = f.fileno()
>>> f.close()
>>> print(ret)
3
flush(self)

刷新缓冲区的内容到硬盘中

f.flush()
isatty(self)

判断文件是否是tty设备,如果是tty设备则返回True,否则返回False

>>> f = open("hello.txt","r")
>>> ret = f.isatty()
>>> f.close()
>>> print(ret)
False
readable(self)

是否可读,如果可读返回True,否则返回False

>>> f = open("hello.txt","r")
>>> ret = f.readable()
>>> f.close()
>>> print(ret)
True
readline(self,limit=-1)

每次仅读一行数据

>>> f = open("hello.txt","r")
>>> print(f.readline())
Hello Word!

>>> print(f.readline())
123

>>> f.close()
readlines(self,hint=-1)

把每一行内容当做列表中的一个元素

>>> 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()
tell(self)

获取指针位置

>>> f = open("hello.txt","r")
>>> print(f.tell())
0
>>> f.close()
seek(self,offset,whence=io.SEEK_SET)

指定文件中指针位置

>>> f = open("hello.txt","r")
>>> print(f.tell())
0
>>> f.seek(3)
3
>>> print(f.tell())
3
>>> f.close()
seekable(self)

指针是否可操作

>>> f = open("hello.txt","r")
>>> print(f.seekable())
True
>>> f.close()
writable(self)

是否可写

>>> f = open("hello.txt","r")
>>> print(f.writable())
False
>>> f.close()
writelines(self,lines)

写入文件的字符串序列,序列可以是任何迭代的对象字符串生产,通常是一个字符串列表

>>> f = open("wr_lines.txt","w")
>>> f.writelines(["11","22","33"])
>>> f.close()
read(self,n=None)

读取指定字节数据,后面不加参数默认读取全部

>>> f = open("wr_lines.txt","r")
>>> print(f.read(3))
112
>>> f.seek(0)
0
>>> print(f.read())
112233
>>> f.close()
write(self,s)

往文件里面写内容

>>> 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目前提供的字符串格式化方法有两种:

  1. 百分号方式
  2. format方式

这两种方式在python2python3中都适用,百分号方式是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

如果一个函数定义中包含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是永远不可能存储全体自然数的。

特点:

  1. 访问者不需要关心迭代器内部的结果,仅需要通过next()方法不断去取下一个内容
  2. 不能随机访问集合中的某个值,只能从头到尾依次访问
  3. 访问到一半时不能往回退
  4. 便于循环比较大的数据集合,节省内存

优化上面rangexrange的生成器

#!/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]
小结
  1. 凡是可作用与for循环的对象都是Iterable类型
  2. 凡是可作用域next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列
  3. 集合数据类型如list,dict,str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象
  4. 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
反射
反射的定义

根据字符串的形式去某个对象中操作成员

  1. 根据字符串的形式去一个对象中寻找成员
  2. 根据字符串的形式去一个对象中设置成员
  3. 根据字符串的形式去一个对象中删除成员
  4. 根据字符串的形式去一个对象中判断成员是否存在
初始反射

通过字符串的形式,导入模块

根据用户输入的模块名称,导入对应的模块并执行模块中的方法

# 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
反射相关的函数
getattr(object,name[,default])

根据字符串的形式去一个对象中寻找成员

➜  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'
setattr(object,name,value)

根据字符串的形式去一个对象中设置成员

设置全局变量

>>> getattr(commons,"age",None)
>>> setattr(commons,"age",18)
>>> getattr(commons,"age",None)
18
setattr结合lambda表达式设置一个函数
>>> setattr(commons,"as",lambda : print("as"))
>>> getattr(commons,"as")
<function <lambda> at 0x101c80e18>
>>> aa = getattr(commons,"as")
>>> aa()
as
delattr(object,name)

根据字符串的形式去一个对象中删除成员

>>> 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(object,name)

根据字符串的形式去一个对象中判断成员是否存在

>>> hasattr(commons,"name")
False
>>> setattr(commons,"name","yang")
>>> hasattr(commons,"name")
True
__import__方式导入多层模块

到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

执行流程

  1. 如果出现错误,那么就执行except代码块,然后再执行finally
  2. 如果没有出现错误,那么就执行else代码块,然后再执行finally
  3. 不管有没有出现异常都会执行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)

object oriented

面向对象基础

python编程方式

  1. 面向过程编程
  2. 面向函数编程
  3. 面向对象编程

各种定义

  1. 如果函数没有在类中,称之为函数
  2. 如果函数在类中,称之为方法

创建类

# 创建一个类,类名是 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

python-021-class

方法
  1. 构造方法
  2. 析构方法
  3. 私有方法
属性
  1. 实例变量
  2. 类变量
  3. 私有属性 __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

python-021-class-2

self是形式参数,由python自行传递

面向对象之封装

把一些功能的实现细节不对外暴露

封装就是将内容封装到某个地方,以后再去调用被封装在某处的内容,在使用面向对象的封装特性时,需要:

  1. 将内容封装到某处
  2. 从某处调用被封装的内容
#!/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 派生类

  1. 派生类可以集成基类中所有的功能
  2. 派生类和基类同时存在,优先找派生类
  3. 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
你是非洲人
面向对象之继承之多继承时的查找顺序

顶层两个类没有父类的情况

python-021-class-3

python-021-class-3

顶层两个类有父类的情况

python-021-class-4

python-021-class-4

多态

接口重用,一种接口,多种实现

面向对象进阶及类成员
深入了解多继承
#!/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

脚本释义:

  1. 创建了类A,B,C,D;
  2. D继承了CB,B继承了A,D内什么都不做,pass;
  3. 创建一个对象obj,类是D,当执行Dbar方法的时候会先从C里面寻找有没有bar方法;
  4. C内没有bar方法,然后继续从B里面查找,B里面也没有,B的父类是AA里面有bar方法,所以就执行了Abar方法;
  5. Abar方法首先输出了BAR;
  6. 然后又执行了self.f1()self=obj,相当于执行了obj.f1();
  7. 执行obj.f1()的时候先从C里面查找有没有f1这个方法,C里面有f1这个方法;
  8. 最后就执行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'
属性方法

面向对象类成员特性: 特性的存在就是将方法伪装成字段

property

把类方法当做普通字段去调用,即用对象调用的时候后面不加括号

# 把一个方法变成一个静态属性,不加括号调用即类似 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

属性方法的作用

航班的例子

setter

设置类方法的值

#!/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值
deleter
#!/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
我是注释
面向对象类成员内容梳理

字段

  1. 静态字段(类变量, 每个对象都有该属性, 节省开销)
  2. 普通字段(实例变量, 每个对象都不同的数据)

方法

  1. 静态方法(无需使用对象封装内容)
  2. 类方法
  3. 普通方法(适用对象中的数据)

特性

  1. 普通特性(将方法未造成字段?)

快速判断,类执行,对象执行:

  1. self -> 对象调用
  2. 无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实例添加nameage属性。

为了达到限制的目的,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代表实例, 上面代码做了两件事

  1. 获取inst的MRO列表
  2. 查找cls在当前MRO列表中的index, 并返回它的下一个类, 即 mro[index + 1]

当我们使用super(cls, inst)时, Python会在inst的MRO列表上搜索cls的下一个类

面向对象python2.7类继承
继承类定义

单继承

class <类名>(父类名)
    <代码块>

类的多重继承

class 类名(父类1,父类2,......,父类n)
    <代码块>
  1. python的类可以继承多个类,Java和C#中则只能继承一个类
  2. python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是: 深度优先和广度优先
python-026-1

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()
  1. 经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
  2. 新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错

注意: bar在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找

运算符重载

运算符重载概念

  1. 运算符重载让类拦截常规的python运算;
  2. 类可重载所有python表达式运算符;
  3. 类也可重载打印,函数调用,属性点号运算等内置运算;
  4. 重载是类实例的行为像内置类型;
  5. 重载是通过提供特殊名称的类方法来实现的.
常见的运算符重载方法

__bool__  | 布尔测试  | bool(X),真测试lt,gt,le,ge,eq,ne| 特定的比较  | XY…radd| 右侧加法  | Other + Xiadd| 增强的加法  | X += Yiter,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.attrnew| 创建  | 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

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模块

  1. 第一个是Socket,它提供了标准的BSD Sockets API
  2. 第二个是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服务端

  1. 创建套接字,绑定套接字到本地IP与端口
  2. 开始监听连接
  3. 进入循环,不断接受客户端的连接请求
  4. 然后接收传来的数据,并发送给对方数据
  5. 传输完毕后,关闭套接字

TCP客户端

  1. 创建套接字,连接远端地址
  2. 连接后发送数据和接收数据
  3. 传输完毕后,关闭套接字
创建一个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

python-028-1

基于socket实现聊天机器人
第一份
server
  1. 创建一个socket
  2. 监听地址
  3. 等待客户端发送消息
  4. 回应
  5. 关闭
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()
client
  1. 创建一个socket
  2. 连接服务器
  3. 发送消息
  4. 接收来自服务端的回复
  5. 关闭
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实现局域网内的聊天工具

server

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

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

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多路复用
线程与进程
协程

alex 协程

协程:

  • 必须在只有一个单线程里实现并发
  • 修改共享数据不需加锁
  • 用户程序里自己保存多个控制流的上下文栈
  • 一个协程遇到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执行完后才会继续向下走。

monkey.patch_all()
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)

modules

模块索引
时间
序列化
进程
模块

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式.在python中,一个.py文件就称之为一个模块(Module)

模块分为三种:

  • 自定义模块
  • 内置模块
  • 开源模块

PyPI

https://pypi.python.org/pypi

使用模块的好处
  1. 提高了代码的可维护性,可重用性.
  2. 避免函数名和变量名冲突,相同的名字的函数和变量完全可以分别存在不同的模块中,我们在编写模块时,不必考虑名字会与其他模块冲突.但要注意,尽量不要与内置函数名子冲突

python 内置函数

同时为了避免模块名冲突,python引入了按目录来组织模块的方法,称为包(Package)

比如: 一个abc.py的文件就是一个叫abc的模块和一个xyz.py的文件就是一个叫xyz的模块.现在abcxyz两个模块的名字与其他模块冲突,于是我们可以通过包来组织模块,为了避免冲突,方法就是选择一个顶层包名,比如mycompany,如下:

python-package-1

python-package-1

引入包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突.现在abc.py模块的名字就变成了mycompany.abc.

注意: 每一个包目录下面都会有一个__init__.py文件,这个文件是必须存在的,否则,python就把这个目录当成普通目录,而不是一个包,__init__.py文件可以是空文件,也可以有python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

类似的,可以有多及目录,组成多级层次的包结构.

python-package-2

python-package-2

web目录下的utils.py的模块名就是mycompany.web.utils,两个utils.py文件的模块名分别是mycompany.utilsmycompany.web.utils

自己创建模块时要注意命名,不能和python自带的模块名称冲突.

mycompany.web也是一个模块,其对应的文件为web目录下的__init__.py文件

导入模块
  1. import module
  2. from module.xx.xx import xx
  3. from module.xx.xx import xx as rename
  4. 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

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,等等。

内置模块

os

sys

用于提供对解释器相关的操作

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 模块中实现,并提供了更丰富的功能。

call

执行命令,返回状态码

>>> import subprocess
>>> ret = subprocess.call(["ls","-l"],shell=False)
# shell = True,允许shell命令是字符串形式
>>> ret = subprocess.call(["ls -l"],shell=True)
check_call

执行命令,如果执行状态是0,则返回0,否则抛出异常

>>> subprocess.check_call(["ls","-l"])
>>> subprocess.check_call("exit 1",shell=True)
check_output

执行命令,如果状态码是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
>>>
subprocess.Popen(…)

用于执行复杂的系统命令

参数:

  • 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 -> 同意使用 \n
  • startupinfo与createionflags只在windows下有效
  • 将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等

执行普通命令

>>> import subprocess
>>> ret1 = subprocess.Popen(["mkdir","t1"])
>>> ret2 = subprocess.Popen("mkdir t2",shell=True)

终端输入的命令分为两种

  • 输入即可得到输出,如ifconfing
  • 输入进行某环境,依赖再输入,如python
shutil

高级的 文件,文件夹,压缩包处理模块

shutil.copyfileobj(fsrc, fdst[, length])

将文件内容拷贝到另一个文件中,可以部分内容

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)
shutil.copyfile(src, dst)

拷贝文件

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)
shutil.copymode(src, dst)

仅拷贝权限。内容、组、用户均不变

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)
shutil.copystat(src, dst)

拷贝状态的信息,包括: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.

docopt

用于参数解析的Python三方库

http://docopt.org/

https://github.com/docopt/docopt

six

Python 2 and 3 compatibility utilities

https://pypi.python.org/pypi/six

Gunicorn

WSGI server

http://docs.gunicorn.org.

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:

tablib

Pythonic Tabular Datasets

数据导出为XLS, CSV, JSON, YAML格式等等

inspect
click

Click is a command line library for Python.

__future__

Python提供了__future__模块,把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性。

Excel

http://www.python-excel.org/

在处理excel数据时发现了xlwt的局限性–不能写入超过65535行、256列的数据(因为它只支持Excel 2003及之前的版本,在这些版本的Excel中行数和列数有此限制),这对于实际应用还是不够的。

openpyxl支持07/10/13版本Excel的, 功能很强大, 但是操作起来感觉没有xlwt方便.

  • 读取Excel时,选择openpyxl和xlrd 都能满足需求.
  • 写入少量数据且存为xls格式文件时, 用xlwt更方便.
  • 写入大量数据(超过xls格式限制)或者必须存为xlsx格式文件时,使用openpyxl了.
xlrd

读写Excel, 但是写操作会有一些问题. 用xlrd进行读取比较方便, 流程和平时都手操作Excel一样, 打开工作簿(Workbook), 选择工作表(sheets), 然后操作单元格(cell)

xlwt
The Python Standard Library

The Python Standard Library

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')
问题记录
IOError: cannot open resource

这是因为PIL无法定位到字体文件的位置,可以根据操作系统提供绝对路径,比如

'/Library/Fonts/Arial.ttf'
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除了实现listappend()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 为我们提供的标准库, 提供了ThreadPoolExecutorProcessPoolExecutor两个类,实现了对threadingmultiprocessing的更高级的抽象,对编写线程池/进程池提供了直接的支持。

concurrent.futures 基础模块是 executorfuture

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

ThreadPoolExecutorExecutor的子类, 使用线程池执行异步调用.

示例:

下面会造成死锁

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)
ThreadPoolExecutor Example
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
ProcessPoolExecutor Example
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 return True.

  • 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 or None, 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 or None, 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...finallytry...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')
附录 格式代码标准

https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior

简单列表:

%a 本地的星期缩写
%A 本地的星期全称
%b 本地的月份缩写
%B 本地的月份全称
%c 本地的合适的日期和时间表示形式
%d 月份中的第几天,类型为decimal number(10进制数字),范围[01,31]
%f 微秒,类型为decimal number,范围[0,999999],Python 2.6新增
%H 小时(24进制),类型为decimal number,范围[00,23]
%I 小时(12进制),类型为decimal number,范围[01,12]
%j 一年中的第几天,类型为decimal number,范围[001,366]
%m 月份,类型为decimal number,范围[01,12]
%M 分钟,类型为decimal number,范围[00,59]
%p 本地的上午或下午的表示(AM或PM),只当设置为%I(12进制)时才有效
%S 秒钟,类型为decimal number,范围[00,61](60和61是为了处理闰秒)
%U 一年中的第几周(以星期日为一周的开始),类型为decimal number,范围[00,53]。在度过新年时,直到一周的全部7天都在该年中时,才计算为第0周。只当指定了年份才有效。
%w 星期,类型为decimal number,范围[0,6],0为星期日
%W 一年中的第几周(以星期一为一周的开始),类型为decimal number,范围[00,53]。在度过新年时,直到一周的全部7天都在该年中时,才计算为第0周。只当指定了年份才有效。
%y 去掉世纪的年份数,类型为decimal number,范围[00,99]
%Y 带有世纪的年份数,类型为decimal number
%Z 时区名字(不存在时区时为空)
%% 代表转义的"%"字符
docker-py
getpass

包含下面两个函数:

  1. getpass.getpass()
  2. 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
json.dumps()

返回一个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.loads()

要把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的字符串之间转换。

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实例对象。

读取json串时保持原有顺序

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
pickle.dump()
>>> 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)
pickle.load()
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()
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

append方法

可以一次添加多行数据,从第一行空白行开始(下面都是空白行)写入。

# 添加一行
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方法去取
pandas

Python Data Analysis Library

pandas

pandas-docs

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

mac下安装可能会出现如下报错, __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
安装
pip3 install pymysql

或者下载源码安装

https://pypi.python.org/pypi/PyMySQL#downloads

简单测试
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>`__) 提供了如下公共方法.

队列方法
Queue.qsize()

返回队列的近似大小. 注意, qsize() > 0 不能保证随后的 get() 不阻塞, qsize() < maxsize 也不能保证 put() 不阻塞.

Queue.empty()

如果队列为空, 返回True, 否则返回False. 如果 empty()但会True不能保证随后调用put()不阻塞. 同样地, 如果empty() 返回 False 同样不能随后调用 get() 不阻塞.

Queue.full()

如果队列满了, 返回 True, 否则返回 False. 如果 full() 返回 True 不能保证随后调用 get()不阻塞. 同样地, 如果 full() 返回 False 不能保证随后调用 put() 不阻塞.

Queue.put(item, block=True, timeout=None)

在队尾插入一个项目, item为必需的, 为插入项目的值. 如果可选参数 blocktrue 并且 timeoutNone (默认值), 队列会一直阻塞, 知道有位置可用. 如果 timeout 是一个正数, 在放入项目的时候, 队列没有可用的空间, 它最多阻塞 timeout 传入的秒数, 之后会触发 `Full <https://docs.python.org/3/library/queue.html#queue.Full>`__ 异常. 反之 (blockFalse), 如果有空间可用将立刻放入项目, 否则触发 `Full <https://docs.python.org/3/library/queue.html#queue.Full>`__ 异常 (这种情况下``timeout``参数会被忽略).

Queue.put_nowait(item)

等价于 put(item, False).

Queue.get(block=True, timeout=None)

从队头删除并返回一个项目, block为是否阻塞, timeout为等待时间. 如果可选参数 blockis True 并且 timeoutNone (默认值), 阻塞知道有项目可用. 如果timeout为正数, 在队列没有项目可用的时候, 它最多阻塞timeout传入的秒数, 之后会触发 `Empty <https://docs.python.org/3/library/queue.html#queue.Empty>`__ 异常. 反之 (blockFalse), 项目可用直接返回, 否则触发 `Empty <https://docs.python.org/3/library/queue.html#queue.Empty>`__ 异常 (这种情况下``timeout``参数被忽略).

Queue.get_nowait()

相当于 get(False).

两个方法用于支持追踪入队任务是否被消费者线程完整地处理.

Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.

Queue.task_done()

表明之前入队的任务完成了. 被用于队列消费者线程. 每个 `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() 实际上意味着等到队列为空,再执行别的操作

Queue.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()
其他

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自带的方法,但功能十分强大。得益于这一点,在提供了正则表达式的语言里,正则表达式的语法都是一样的,区别只在于不同的编程语言实现支持的语法数量不同;但不用担心,不被支持的语法通常是不常用的部分。如果已经在其他语言里使用过正则表达式,只需要简单看一看就可以上手了。

正则表达式概念
  1. 使用单个字符串来描述匹配一系列符合某个句法规则的字符串
  2. 是对字符串操作的一种逻辑公式
  3. 应用场景: 处理文本和数据
  4. 正则表达式是过程: 依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;否则匹配失败
字符匹配

匹配任意一个字符

# 导入模块
>>> 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'
边界匹配

匹配字符串开头

# 必须以指定的字符串开头,结尾必须是@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模块常用的方法
re.match()

语法格式

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'>
re.findall()
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'}
re.split()
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']
re.sub()
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'
re.compile()

当我们在Python中使用正则表达式时,re模块内部会干两件事情:

  1. 编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
  2. 用编译后的正则表达式去匹配字符串。

如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:

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

ssl

可以用来验证服务端证书

https://wiki.python.org/moin/SSL

示例可参考链接内容

struct

Python提供一个 struct 模块来解决bytes和其他二进制数据类型的转换.

structpack函数把任意数据类型变成bytes

>>> import struct
>>> struct.pack('>I',10240099)
b'\x00\x9c@c'

pack的第一个参数是处理指令,'>I'的意思是:

>表示字节顺序是big-endian,也就是网络序,I 表示4字节无符号整数。

后面的参数个数要和处理指令一致。

unpackbytes 变成相应的数据类型:

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

doc

GitHub tablib

示例
生成单张工作簿, headers 为表头, title 为工作簿名

data为数据列表,里面是一行行内容

tablib.Dataset(*data, headers=headers, title=sheet_name)
写 xlsx
# 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

几个事实

  1. Python 默认参数创建线程后,不管主线程是否执行完毕,都会等待子线程执行完毕才一起退出,有无join结果一样
  2. 如果创建线程,并且设置了daemon为true,即thread.setDaemon(True), 则主线程执行完毕后自动退出,不会等待子线程的执行结果。而且随着主线程退出,子线程也消亡。
  3. join方法的作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,子线程还没有结束,则主线程强制结束子线程。
  4. 如果线程daemon属性为False, 则join里的timeout参数无效。主线程会一直等待子线程结束。
  5. 如果线程daemon属性为True, 则join里的timeout参数是有效的, 主线程会等待timeout时间后,结束子线程。此处有一个坑,即如果同时有N个子线程join(timeout),那么实际上主线程会等待的超时时间最长为 N * timeout, 因为每个子线程的超时开始时刻是上一个子线程超时结束的时刻。
time

在Python中,通常有几种方式表示时间

  1. 时间戳
  2. 格式化的时间字符串
  3. 元组(共九个元素)
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)
>>>
time.gmtime()
>>> help(time.gmtime) # 帮助信息超级详细

将秒转换成UTC时间的struct_time

默认返回当前time.time()

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

用filter求素数
>>> 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(...)
    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']
请编写一个prod()函数,可以接受一个list并利用reduce()求积
>>> from functools import reduce
>>> reduce(lambda x,y : x * y,[3,5,7,9])
945
利用map和reduce编写一个str2float函数

把字符串’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()排序的关键在于实现一个映射函数。

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()判断

判断某个属性是否是方法的最简单办法就是利用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
使用Python设置环境变量
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)
socket
获取域名对应IP
import socket
ip = socket.gethostbyname("www.baidu.com")
使用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))

其实,如果我们的脑洞在大一点,或者说抽象思维在强一点,就不难发现,其实阶乘和求和其实是类似的概念,区别仅仅在于:

  1. 将加号更换为乘号
  2. 接收累积的值的变量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

通过观察比较,其实也不难发现大部分地方都是相同的,不同的地方也就两个地方:

  1. 变量total的初始值不同
  2. 累积的方式不同,一个是用加号进行累加,另一个是用乘号进行累乘

然后呢,还是套路,就像上面我们在抽象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
同时兼容python2和python3
放弃2.6之前的Python版本

python 2.6之前的python版本缺少一些新特性

bin/2to3
安装python的bin目录下有2to3,可以将python2的代码自动转成python3,可以通过这种方式查看2和3的差异
使用python -3执行python程序
from __future__ import
import
try:
    #python2
    from UserDict import UserDict
    #建议按照python3的名字进行import
    from UserDict import DictMixin as MutableMapping

except ImportError:
    #python3
    from collections import UserDict
    from collections import MutableMapping
检查当前运行的python版本

有时候你或许必须为python2和python3写不同的代码,你可以用下面的代码检查当前系统的python版本。

import sys
if sys.version > '3':
    PY3 = True
else:
    PY3 = False
six

不推荐使用six,使用six写出来的代码更像python2

字典,列表排序
字典
按key排序
items = dict.items()
items.sort()
for key,value in items:
   print(key, value) # print(key,dict[key])
  1. print(key, dict[key] for key in sorted(dict.keys()))
按照value排序
  1. 把dictionary中的元素分离出来放到一个list中,对list排序,从而间接实现对dictionary的排序。这个“元素”可以是key,value或者item。
  2. 用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排序

list有sort方法:

>>> s=[2,1,3,0]
>>> s.sort()
[0, 1, 2, 3]
遍历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还有其他几个方法

错误,调试和测试
错误记录
Working with Matplotlib on OSX

在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()

把有问题的变量打印出来

print()最大的坏处就是回头还得删掉

断言

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来看

logging

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和文件。

pdb

启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。

python3 -m pdb err.py

pdb.set_trace()

这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点

IDE

StyleGuide

PEP 8 – Style Guide for Python Code

The Pocoo Style Guide

Pythonic
交换两个数字

在其他语言里面

t = a
a = b
b = t

在Python语言里面

a, b = b, a
链式比较
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]

与列表推导对应的,还有集合推导和字典推导。我们来演示一下。

列表:30~40 所有偶数的平方
>>> [ 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 换成其他语言,大多数题目也适用
  • 不会出现诸如「打印九九乘法表」、「打印水仙花」之类的题目
  • 点此链接,会看到部分题目的代码,仅供参考
  • 本文本文由@史江歌(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'))
第 0010 题

使用 Python 生成类似于下图中的字母验证码图片

字母验证码

字母验证码

第 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)
第 0013 题

用 Python 写一个爬图片的程序,爬 这个链接里的日本妹子图片 :-)

第 0014 , 0015, 0016 题
0014
纯文本文件 student.txt为学生信息, 里面的内容(包括花括号)如下所示:

{
    "1":["张三",150,120,100],
    "2":["李四",90,99,95],
    "3":["王五",60,66,68]
}

请将上述内容写到 student.xls 文件中,如下图所示:

student.xls

student.xls

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

请将上述内容写到 city.xls 文件中,如下图所示:

city.xls

city.xls

0016
纯文本文件 numbers.txt, 里面的内容(包括方括号)如下所示:

[
    [1, 82, 65535],
    [20, 90, 13],
    [26, 809, 1024]
]
numbers.xls

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 题的代码是否可以复用。

第 0023 题

使用 Python 的 Web 框架,做一个 Web 版本 留言簿 应用。

阅读资料:Python 有哪些 Web 框架

  • 留言簿参考
第 0024 题

使用 Python 的 Web 框架,做一个 Web 版本 TodoList 应用。

  • SpringSide 版TodoList
第 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)
输入某年某月某日,判断这一天是这一年的第几天?
  1. 年份能被4整除;
  2. 年份若是 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个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
pass
判断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。
pass
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框架

http://www.cnblogs.com/hazir/p/what_is_web_framework.html

Django
Django
快速开始
  1. 安装python
  2. 安装Django
  3. 创建工程
在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

访问 127.0.0.1:8000

python-django-01

python-django-01

Django的历史
Django访问流程

Django请求生命周期

  • URL对应关系(匹配) -> 视图函数 -> 返回用户字符串
  • URL对应关系(匹配) -> 视图函数 -> 打开一个HTML,读取内容
WSGI规范

只要遵循这个规范,就可以用来创建socket

安装python
➜  python3 -V
Python 3.5.3
安装django

使用 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 为我们提供了一个 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
创建APP

使用如下命令

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项目

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

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

python-django-02

找到刚刚添加的app,添加一个或多个用户

python-django-03

python-django-03

python-django-04

python-django-04

python-django-05

python-django-05

继续打开http://127.0.0.1:8000/users/就能够看到刚才添加的用户了,你可以试着再添加一个用户然后刷新页面,看看是否会显示出来你刚刚新添加的用户。

附,使用PyCharm创建Django项目

新建项目,可直接选择Django,输入项目名称,模板文件夹名字,应用名字,创建即可

python-django-08

python-django-08

相关配置

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

python-django-09

python-django-09

python-django-10

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'),
)
暂时注释CSRF
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',
]
注册APP
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'cmdb',
]
urls

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

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>
views

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):

访问地址为: http://127.0.0.1:8000/2017/3/14/

传值
url(r'^(?P<year>[0-9]{4})/$', views.id, {'foo': 'bar'}),

id函数必须接受yearfoo参数

别名

对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>
CBV
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.pyview.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")
  1. 当访问 http://127.0.0.1:8000/app1/hello/ 时返回内容 Hello App1
  2. 当访问 http://127.0.0.1:8000/app2/hello/ 时返回内容 Hello App2
视图
FBV & CBV

Django两者都支持

  • function base view
  • class base view
请求与响应

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

新建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
将新定义的app添加到settings.py中的INSTALL_APPS中
➜  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')
定义视图函数相关的url

即规定 访问什么网址对应什么内容

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

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

/add/3/4/

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模板的最基本方式如下:

  1. 可以用原始的模板代码字符串创建一个Template对象,Django同样支持用指定模板文件路径的方式来创建Template对象;
  2. 调用模板对象的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.'
>>>

当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:

  1. 字典类型查找(比如 foo["bar"] )
  2. 属性查找(比如 foo.bar )
  3. 方法调用(比如 foo.bar() )
  4. 列表类型索引查找(比如 foo[bar] )
jinja模板

在视图中使用模板
# 首先你要导入os模块
'DIRS': [os.path.join(BASE_DIR, 'templates')],
加载模板
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
It is now {{ current_date }}.
</body>
</html>
render_to_response()
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})
locals()技巧

很多时候,就像在这个范例中那样,你发现自己一直在计算某个变量,保存结果到变量中(比如前面代码中的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。对此如何取舍取决你的应用程序。

模板方法
内置方法,类似于python的内置函数
{% raw %}
{{ k1|lower }}  # 将所有字母都变为小写
{{ k1|first|upper }}  # 将首字母变为大写
{{ k1|truncatewords:"30" }}  # 取变量k1的前30个字符
{{ item.createTime|date:"Y-m-d H:i:s" }}    # 将时间转为对应格式显示
{% endraw %}
自定义方法

在内置的方法满足不了我们的需求的时候,就需要自己定义属于自己的方法了,自定义方法分别分为 filtersimple_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模型
MTV开发模式

把数据存取逻辑、业务逻辑和表现逻辑组合在一起的概念有时被称为软件架构的Model-View-Controller(MVC)模式。在这个模式中,Model代表数据存取层,View代表的是系统中选择显示什么和怎么显示的部分,Controller指的是系统中根据用户输入并视需要访问模型,以决定使用哪个视图的那部分。

Django紧紧地遵循这种MVC模式,可以称得上是一种MVC框架。

以下是Django中M、VC各自的含义:

  1. M: 数据存取部分,由django数据库层处理;
  2. V: 选择显示哪些数据要显示以及怎样显示的部分,由视图和模板处理;
  3. C: 根据用户输入委派视图的部分,由Django框架根据URLconf设置,对给定URL调用适当的Python函数;

C由框架自行处理,而Django里更关注的是模型(Model)模板(Template)视图(Views),Django也被称为MTV框架,在MTV开发模式中:

  1. M 代表模型(Model),即数据存取层,该层处理与数据相关的所有事务:如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等;
  2. T 代表模板(Template),即表现层,该层处理与表现相关的决定:如何在页面或其他类型文档中进行显示;
  3. 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()

没有报错,则说明数据库配置正确.

实战
创建一个新的app

让我们来创建一个Django app,一个包含模型,视图和Django代码,并且形式为独立Python包的完整Django应用。

Projectapp之间的不同就是一个是配置另一个是代码

  1. 一个Project包含很多个Django app以及对它们的配置;
  2. 技术上,Project的作用是提供配置文件,比方说哪里定义数据库连接信息, 安装的app列表,TEMPLATE等等;
  3. 一个app是一套Django功能的集合,通常包括模型和视图,按Python的包结构的方式存在;
  4. 例如,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
第一个模型

首先创建三张表

  1. 学生表(student) , 拥有字段: id/sname/gender
  2. 课程表(course) , 拥有字段: id/cname
  3. 成绩表(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>]>

snamecontains之间有双下划线,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 */ |
实战3
连表操作一对一

appmodels.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.
批量导入bulk_create()

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.
自定义admin界面

官方文档

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
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',
            ],
        },
    },
]
Django admin 上传下载
Django 上传文件

使用 request.FILES.get('xlsx') 获取文件对象 ‘xlsx’,为htmlname属性

直接使用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('文件错误')
Django下载文件
html
<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
关闭Debug之后, 静态文件的问题

如果是直接使用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'),
)
urls
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()),
]
templates
login.html
<!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>
home.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>
views
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,显示用户,点击用户名,显示详细用户信息

html templates
index.html
<!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>
视图(views)

添加如下内容

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())
路由(urls)
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

数据库配置
sqlite
# 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'),
    }
}
mysql

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'
    }
}
models.py

创建类

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
添加url,views,通过访问url进行测试

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

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>
第四部分-简单用户管理
project.urls
from django.conf.urls import include

urlpatterns = [
    # 添加如下语句,使用路由分发
    url(r'^cmdb/', include('app01.urls')),
]
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),
]
view
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/')
templates
base.html
{% 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>
user_info.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 %}
user_detail.html
{% 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 %}
user_edit.html
{% 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 %}
本实例相关知识
getlist

用于CheckBox,select等多选的内容,获取到的内容为一个列表

ct = request.POST.getlist("city")
上传文件
  • form表单需要做特殊设置, enctype="multipart/form-data"
  • 服务端接收文件并保存
form表单需要添加enctype属性
<!-- 上传文件需要增加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>
request.FILES
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')
FBV & CBV

function base view

class base view

Django支持FBV和CBV
views里面也可以使用类
urls
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()),
]
views

详细信息看父类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')
父类View
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')
models基本操作
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')
first
obj = models.UserInfo.objects.filter(username=u, password=p).first()
count

统计数量

models.UserInfo.objects.all().count()
Django用户认证

要实现这样的需求其实很简单:

  • 使用django自带的装饰器 @login_required
  • 在相应的view方法的前面添加 @login_required
  • 并在settings.py中配置LOGIN_URL参数
  • 修改login.html中的表单action参数
创建测试用户
python3 manage.py createsuperuser
登录后默认跳转到

登录后默认跳转到 /accounts/profile/

可以在settings.py中进行设置

LOGIN_REDIRECT_URL = '/'
登录页面
<form class="form-signin" action="/accounts/login/" method="post">{% csrf_token %}
...
</form>
Django相关命令
python manage.py

查看所有命令

Type ‘manage.py help ’ for help on a specific subcommand.

django-admin project mysite

新建项目

如果报错,尝试使用 django-admin.py 代替 django-admin

python manage.py startapp app

新建app 前提: 处于项目目录

一个项目可以有多个app,通用的app也可以在多个项目中使用.

app的名字需要为合法的Python包名

创建数据库表,更改数据库表或字段

Django 1.7.1及以上使用如下命令

如果是之前的版本,Django无法自动更改表结构,迁移数据,不过可以使用第三方工具south

python manage.py makemigrations

创建更改文件

python manage.py migrate

将更改应用到数据库

python manage.py runserver

启动 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 flush

清空数据库,仅留空表

python manage.py createsuperuser

创建超级管理员,按照提示输入用户名和对应密码

修改 用户密码

python manage.py changepassword username

导出,导入数据
python manage.py dumpdata appname > appname.json
python manage.py loaddata appname.json
python manage.py shell

Django项目环境终端

python manage.py dbshell

数据库命令行

Django 会自动进入在settings.py中设置的数据库,如果是 MySQL 或 postgreSQL,会要求输入数据库用户密码。

在这个终端可以执行数据库的SQL语句。

Django认证系统

https://docs.djangoproject.com/en/2.0/topics/auth/

Django提供了一个用户身份验证系统, 它处理用户帐户、组权限和基于cookie的用户会话。

综述

Django身份验证系统处理身份验证和授权.

身份验证系统包括:

  • Users
  • Permissions: 二进制 (yes/no) 标志指定用户是否可以执行某些任务.
Django配置详解
Django 时间与时区设置问题
  • 配置文件settings.py中,两个配置参数跟时间与时区有关
    • TIME_ZONE
    • USE_TZ
  • 如果 USE_TZTrue
    • Django会使用系统默认设置的时区,即America/Chicago,此时的TIME_ZONE不管有没有设置都不起作用。
  • 如果 USE_TZFalse
    • TIME_ZONENone,Django会使用默认的America/Chicago时间。
    • TIME_ZONE设置为其它时区的话,则还要分情况
      • 如果是Windows系统,则TIME_ZONE设置是没用的,Django会使用本机的时间。
      • 如果为其他系统,则使用该时区的时间,如设置USE_TZ = False, TIME_ZONE = 'Asia/Shanghai', 则使用上海的UTC时间
Django
Django问题记录
兼容 python2.x 和 python3.x

示例如下:

# 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 字符,以便有更好地兼容性。

如果抛出错误 django.core.exceptions.AppRegistryNotReady: Models aren’t loaded yet

导入数据

import django
if django.VERSION >= (1, 7):#自动判断版本
    django.setup()
Django REST framework
教程

本教程将指导你熟悉构成 REST 框架的组件, 需要一些时间, 但会让你对所有组件有一个全面的了解, 强烈推荐阅读.

官方 Tutorial

Tutorial 1: Serialization
介绍

本教程将会通过一些简单的代码来实现 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, 我们准备下一步

创建一个 model

为了实现本教程, 我们创建一个 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
创建一个序列化类(Serializer class)

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')])]
使用模型序列化(ModelSerializers)

我们的 SnippetSerializer 类复制了包含 Snippet 模型在内的很多信息. 如果能够简化我们的代码, 那是极好的.

和Django提供的 Form 类和 ModelForm 类相同, 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() 方法.
用我们的序列化写常规的Django视图

让我们看看, 如何使用我们的序列化类来实现一些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”.

测试我们的API

退出当前命令行.

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 数据.

Where are we now

到目前为止, 我们做得很好, 我们编写的序列化 APIDjango's Forms API 比较相似, 同时编写了一些常规的Django视图.

我们的 API 没有做什么特殊的事情, 除了作出json响应外, 还有一些边缘事件没有处理, 但至少是一个还有点功能的 Web API.

在教程的第2部分, 我们将介绍如何对我们的 API 进行改进.

Tutorial 2: Requests and Responses

从这节开始, 我们会接触到 REST 框架的核心. 让我们介绍一些基本构建组件.

Request 对象

REST framework 引入了一个 Request 对象, 它扩展了常规的 HttpRequest, 并提供了灵活的请求解析. Request 对象的核心功能是 request.data 属性, 它和 request.POST 属性很相似, 但是它对 Web APIs 更加有用.

request.POST  # 只处理表单数据. 仅用于 'POST' 方法.
request.data  # 处理任意数据. 可以用于 'POST', 'PUT' and 'PATCH' 方法.
Response 对象

REST framework 也引入了 Response 对象, 它是一类用为渲染和使用内容协商来决定返回给客户端的正确内容类型的 TemplateResponse.

return Response(data)  # Renders to content type as requested by the client.
Status codes

在你的视图中使用数字HTTP状态码并不总是易读的, 错误代码也容易被忽略. REST framework 为每个状态码提供更明确的标识符, 例如 状态模块中的 HTTP_400_BAD_REQUEST . 使用这种标识符代替纯数字标识符是一个不错的主意.

装饰 API 视图

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 框架将响应对象的数据渲染成正确的内容类型返回给客户端.

在URLs后添加可选的格式后缀

我们的响应不再是单一的内容格式, 根据这个事实, 我们可以在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 的信息, 比如 特性, 定制.

What’s next

在教程的第3部分, 我们将开始使用基于类的视图(CBV), 并介绍如何使用通用的视图来减少代码量.

Tutorial 3: Class-based Views

我们也可以使用基于类的视图编写我们的 API, 如我们所见, 这是一个有利的模式, 允许我们重用共同的功能, 使我们的代码不重复

使用基于类的视图重新我们的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, 重构完成, 再运行开发服务器, 一切都和之前一样正常工作.

使用 mixins

使用基于类的视图的最大的好处就是, 允许我们快速的创建可复用的行为.

我们一直使用的 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 创建我们的视图, 同时加入 ListModelMixinCreateModelMixin.

基础类提供核心功能, mixin 类提供 .list().create() 动作. 然后我们绑定 getpost 方法到合适的动作, 到目前为止, 已经变得足够简单.

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)

Tutorial 4: 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 中. 我们为用户添加只读视图, 因此我们使用基于视图的一般类 ListAPIViewRetrieveAPIView.

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()),
将用户和 Snippets 联系起来

现在, 如果我们创建一个代码片段, 我们没法将用户和创建的 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

然后在 SnippetListSnippetDetail 视图类中添加如下属性.

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
在可浏览API中添加登录

如果你打开浏览器操控可浏览的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

由于现在 API 有权限集合, 在我们需要编辑任何 snippets 的时候, 需要认证我们的请求, 我们没有设置其他任何认证类(authentication classes), 默认情况下只有 SessionAuthenticationBasicAuthentication.

当我们通过浏览器进行交互时, 我们可以登录, 浏览器会话(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 的凝聚力.

Tutorial 5: Relationships & Hyperlinked APIs

目前, 我们用主键代表我们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 中声明.

创建一个高亮的snippets端点

还有一个明显的事情就是我们的 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()),
为我们的API添加超链接

在Web API设计中, 处理实体之间的关系是一项非常有挑战的事情. 代表一种关系可以有很多种方式

  • 使用主键
  • 在实体间使用超链接
  • 在相关的实体上使用唯一的 slug
  • 使用相关实体的默认字符串
  • 在父表述使用嵌套的实体
  • 其他自定义的表述

REST 框架支持以上所有的方式, 正向或反向关系均可以使用, 或者像使用一般外键一样使用自定义的管理方式.

在这种情况下, 我们在实体间使用超链接方式. 为了达到目的, 我们将修改我们的序列(serializers), 扩展 HyperlinkedModelSerializer 代替 ModelSerializer.

HyperlinkedModelSerializerModelSerializer 有以下几点不同:

  • 默认不包括 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

确保我们的URL模式均已命名

如果我们要使用超链接API, 我们必须确保对 URL 模式进行命名, 让我们看看哪些链接需要命名

  • 根API指向 'user-list''snippet-list'.
  • snippet 序列包括一个指向 'snippet-highlight' 的字段.
  • user 序列包括一个指向 'snippet-detail' 的字段.
  • 我们的 snippetuser 序列包括 ‘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 的所有设置都是在 settingsREST_FRAMEWORK 字典中的. 它可以帮我们区分项目中的其他配置.

同时, 我们也可以自定义分页的样式, 在这里, 我们使用默认方式.

浏览API

如果我们打开浏览器, 并访问可浏览的 API, 你会发现你可以使用下面的链接使用 API .

你也可以看到 snippet 实例的 'highlight' 链接, 这些链接会返回高亮的 HTML 代码.

在教程的第6部分, 我们会介绍怎么使用 ViewSetsRouters 通过更少的代码, 实现我们的 API.

Tutorial 6: ViewSets & Routers

REST 框架包含一个 ViewSets 的抽象, 它可以让开发者将精力集中在构建API的状态和交互上, 同时帮助开发者, 基于共同约定, 自动处理 URL 构建.

ViewSet 类几乎和 View 类一样, 除了它提供的 read 或者 update 操作, 而不是像 getput 一样的方法.

一个 ViewSet 类在它被实例化成一个视图集合的最后时刻, 通过一个处理复杂 URL 配置的 Router 类绑定, 且只绑定一个方法集合.

使用 ViewSets 重构

首先使用单个 UserViewSet 视图重构 UserListUserDetail 视图.

文件 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' 操作. 我们需要像使用常规视图一样, 设置 querysetserializer_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 关键字参数.

明确绑定 ViewSets 到 URLs

处理方法, 只会按照我们的 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')
])
使用 Routers

因为我们使用 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 方法

权衡使用 views 和 viewsets

viewsets 是一个非常有用的抽象. 它可以确保 URL 原型和你的 API 保持一致, 最大限度的减少代码量, 允许你将精力放在 API 的交互和表示上, 而不是放在编写 URL conf 上.

这并不意味在所有地方都要使用 viewsets. 在使用基于类的视图和基于函数的视图时, 需要进行权衡. 使用 viewsets 没有单独构建 views 明确.

在教程第7部分, 我们将介绍, 如何添加一个 APP schema, 并使用客户端库或命令行工具与我们的 API 进行交互.

Tutorial 7: Schemas & client libraries

schema 是一种机器可读的文档, 用于描述可用的API端点, URLS, 以及他们支持的操作.

schema 可以用于自动生成文档, 也可以用于驱动可以与API交互的动态客户端库.

Core API

为了提供 schema 支持, REST 框架使用 Core API

Core API 是用于描述 APIs 的文档规范. 它可以用来提供内部可用端点内部表示格式和API暴露的可能的交互. 它可以用于服务端或客户端.

当用于服务端时, Core API 允许API支持呈现 schema 或渲染超媒体格式.

当用于客户端, Core API 允许动态驱动的客户端库与任何支持 schema 或超媒体格式的 API 交互.

添加一个 schema

REST framework 支持明确定义的 schema 视图, 或自动生成的 schemas. 由于我们使用 ViewSetsRouters, 我们可以很简单的自动生成 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

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 项目, 你可以使用以下几种方式:

Now go build awesome things.

Django REST framework初探

当前版本 version 3

http://www.django-rest-framework.org/

Django REST framework 用于构建 web api, 具有强大,灵活的特性.

Requirements

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)

可选包:

安装

通过 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.

快速开始

创建一个简单的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可以很好地, 简洁地组织视图

URLs

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.

settings

添加 'rest_framework'INSTALLED_APPS 中. 配置模块 tutorial/settings.py

INSTALLED_APPS = (
    ...
    'rest_framework',
)
测试 API

测试 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

右上角登录可以进行编辑

Flask

The Flask Mega-Tutorial

Flask tips
跨域请求

CORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。

跨域资源共享 CORS 详解

Flask-CORS

Flask-CORS

Flask扩展,用于处理Cross Origin Resource Sharing (CORS), 允许 cross-origin AJAX 操作.

Flask-CORS

flask 跨域访问装饰器实现

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
什么是跨域

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

浏览器的同源策略

安装

Flask依赖一些额外的库,比如Werkzeug and Jinja2. Werkzeug是一个用于开发部署Web应用以及多样服务的标准python接口的WSGI工具箱.

推荐使用virtualenv环境

前提

  • Python 2.x 2.6 ~
  • Python 3.x 3.3 ~

Python 3 Support.

virtualenv
使用pyenv
pyenv virtualenv 3.6.1 xx_flask
pyenv shell xx_flask
pip install Flask
直接安装
sudo pip install Flask
快速开始
最小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.

访问 http://127.0.0.1:5000/

默认,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
唯一URLs/重定向

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构建

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
HTTP方法

默认情况下,路由仅应答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属性来获取. 访问表单数据(PUTPOST请求提交的数据)可以使用``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``**属性访问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 将返回值转换为响应对象的逻辑如下:

  1. 如果返回的是一个合法的响应对象, 会直接从视图返回.
  2. 如果返回值为字符串, 会使用字符串数据以及默认参数创建响应对象
  3. 如果返回值是一个元组, 元组可以提供额外的信息, 但是元组必须是(response, status, headers)(response, headers)的形式, 且至少包含一个元素. status的值会覆盖状态码, headers可以是一个列表或者字典, 作为额外的表头值.
  4. 如果上述条件都不满足, 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
Sessions

除了请求对象, 还有一个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 .

Application Errors.

WSGI中间件

如果你想给你的应用添加WSGI中间件, 你可以封装内部WSGI应用. 比如, 你想使用Werkzeug包中的某个中间件来解决lighttpd中的bugs, 你可以这样做

from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
使用 Flask Extensions

扩展插件可以帮你完成常见的任务. 比如, Flask-SQLAlchemy 提供 SQLAlchemy 支持, 它可以让你更简单的使用Flask.

更多关于Flask 扩展插件的信息, 查阅 Flask Extensions.

部署到 Web Server

Ready to deploy your new Flask app? Go to Deployment Options.

Tutorial

本教程里面, 我们会创建一个简单的微博应用. 它仅支持一个用户, 可以创建文本条目, 没有提要和评论, 但是它仍然有我们需要的一切. 我们将使用FlaskSQLite.

示例代码 example source.

介绍Flaskr

我们将博客命名为Flaskr, 基本上, 它将实现这些功能:

  1. 允许用户使用配置文件里面指定的凭证登录登出, 只支持一个用户.
  2. 用户登录时, 可以新增条目到页面(包含文本标题以及一些HTML文本), 因为用户可信任, 这部分HTML文本不做审查,.
  3. 首页倒序显示所有条目, 并且用户登录后可在这里添加新条目.

我们会直接使用SQLite3, 因为它足够应付这个应用. 对于大型项目可以使用 SQLAlchemy, 它可以智能的处理数据库连接, 允许你同时连接不同的关系型数据库. 如果你的数据更适合NoSQL, 你也可以考虑流行的NoSQL数据库.

这是应用最终的效果图

python-flask-01

python-flask-01

步骤0: 创建目录

项目开始之前, 我们需要先将目录创建好

/flaskr
    /flaskr
        /static
        /templates

推荐使用Python包进行安装和运行应用. 稍后你可以看到怎么运行flaskr 现在继续创建应用目录结构. 接下来几个步骤将要创建数据库表结构以及主要模块.

static目录用来存放静态文件, 比如CSS, JavaScript, 通过HTTP的方式提供给用户.templates目录将用来存放 Jinja2 模板.

步骤1: 数据库配置

我们的应用只需要一张表, 将如下内容写到一个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列为自增主键.

步骤2: 应用安装代码

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)
步骤3: 安装flaskr
/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). 在这种情况下, statictemplates目录需要被包含进来, 同时还有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.

步骤4: 数据库连接

你现在有一个函数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_dbclose_db函数放在之前的connect_db函数下面.

如果你想找准定位, 可以查看一下示例代码. 在Flask里面, 你可以把所有代码放在单一的python模块里, 但是当你的应用规模扩大时, 这不是一个好主意.

example source grows larger

步骤5: 创建数据库

如前面介绍所说, 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命令, 以及你的表名是否正确(比如,单数和复数)

步骤6: 视图函数

现在数据库正常, 你可以开始编写视图函数.

显示所有条目

这个视图显示数据库存储的所有条目. 它监听/, 应用将会从数据库查询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_inkey, 这里有一个窍门: 如果使用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也有很多库可用于散列.

Flask推荐的插件

步骤7: 模板

是时候使用模板了. 你可能会注意到, 当运行app的时候, 会触发异常, 提示Flask无法找到模板. Flask默认启用 Jinja2 模板 . 这意味着除非你使用 `Markup <http://flask.pocoo.org/docs/0.12/api/#flask.Markup>`__ 标记一段代码或者在模板中使用 |safe 过滤器, 否则Jinja2将自动转义, 确保特殊字符, 例如 < or > 被转义为等价的XML实体.

我们也会使用模板继承, 在所有网页中重用布局.

将下面的模板放置在templates目录

layout.html

这个模板包含HTML主体, 标题, 和登录链接(如果用户已经登录, 则提供登出功能). 如果有, 也会显示闪现消息. {% block body %} 将被子模板中的同名blcok (body)替换.

session字典在模板中也是可用的, 你可以用来检查, 用户是否登录. Jinja支持访问不存在的属性,对象/字典属性或成员, 即便logged_inkey不存在.

<!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>
show_entries.html

这个模板扩充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 %}
login.html

登录模板, 仅仅显示一个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应用多么容易.

添加测试到flaskr

假设你已经看了 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
Testing + setuptools

处理测试的一种方法就是使用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 Getting Start

  1. 注册账号 Heroku account.
  2. 本地安装python 3.6
  3. 安装setuptools, pip
  4. 安装virtualenv
  5. postgres
安装heroku

安装之后,登录, 安装方法往下看

$ heroku login
Enter your Heroku credentials.
Email: python@example.com
Password:
...
Mac
brew install heroku
查看日志
heroku logs --tail
uWSGI

http://uwsgi-docs.readthedocs.io/en/latest/index.html

Quickstart for Python/WSGI applications
安装uWSGI支持Python

uWSGI使用C编写, 所以需要C编译器(比如gcc或clang)以及Python开发包

基于Debian的发行版执行如下命令

apt-get install build-essential python-dev
安装uWSGI

安装方法

  • 通过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
部署WSGI应用

从以下示例开始, 将内容保存到foobar.py

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]
Deploy it on HTTP port 9090

如果前端有负载均衡等, 不要使用--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安装).

使用Nginx代理
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
Automatically starting uWSGI on boot

查阅官方文档

WSGIquickstart

部署Flask

使用如下命令开始, 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参数

Deploying web2py

一个比较受欢迎的选择, 编写一个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',
            ],
        },
    },
]

爬虫

Beautiful Soup
安装 Beautiful Soup
安装bs4
使用easy_install 或pip

:: shell

# easy_install easy_install beautifulsoup4

# pip pip install beautifulsoup4

使用源码包安装

下载

python setup.py install
安装解释器

Beautiful Soup支持Python标准库里的解释器, 同时也支持第三方库, 其中一个就是 lxml

详情

快速开始
创建soup

通过BeautifulSoup解析文档, 文档可以是字符串或者文件句柄.

from bs4 import BeautifulSoup

with open("index.html") as fp:
    soup = BeautifulSoup(fp)

soup = BeautifulSoup("<html>data</html>")
对象类型

四大类型

  • Tag
  • NavigableString
  • BeautifulSoup
  • Comment
Tag

与XML或HTML文档内容中的tag一致

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
name
>>> tag.name
'b'

# 修改tag名字, 此操作会影响所有通过当前Beautiful Soup对象生成的HTML文档
>>> tag.name = "blockquote"
>>> tag
<blockquote class="boldest">Extremely bold</blockquote>
attributes

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

BeautifulSoup 对象表示的是一个文档的全部内容. 大部分时候, 可以把它当作 Tag 对象,它支持 遍历文档树搜索文档树 中描述的大部分的方法.

因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性. 但有时查看它的 .name 属性是很方便的, 所以 BeautifulSoup 对象包含了一个值为 "[document]" 的特殊属性 .name

>>> soup.name
'[document]'
Comment

文档注释

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
基础
基础
Session
Session的运行方式
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)
Variable
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))
placeholder

占位符, 运行的时候, 再传入输入的值

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

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是一个入门级的计算机视觉数据集,它包含各种手写数字图片以及对应的标签.

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

GitHub

介绍

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, 需要安装一些依赖.

Bazel (only if compiling source code)

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:

  1. 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
  1. Set up your environment. Put this in your ~/.bashrc.
export PATH="$PATH:$HOME/bin"
gRPC

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 installpip install 命令, 你可能需要运行.

TensorFlow Serving Python API PIP package

运行python客户端代码, 不需要安装Bazel, 使用如下命令安装tensorflow-serving-api

pip install tensorflow-serving-api
通过apt-get安装
可用二进制包

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
安装ModelServer
  1. 添加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 -
  1. 安装升级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 .

通过源码安装
克隆TensorFlow Serving 仓库
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 更深入的例子.

Optimized build

针对一些使用特殊指令集(比如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), 可以简化开发过程. 你需要的工具是gitdocker. 不需要手动安装所有依赖项.

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

使用docker部署TensorFlow Serving

参考

OpenCV
OpenCV

http://opencv.org/

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,

安装

一个参考

MacOS安装

另一个参考(虽然好像没参考上)

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

开始之前

确保 pip 版本 8 或更高:

python -m pip install --upgrade pip

如果在虚拟环境中, 就先切换到虚拟环境, 然后再升级

安装gRPC
gRPC
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
安装gRPC工具

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

https://grpc.io/

现代开源高性能RPC框架, 可以在任何环境中运行. 它可以有效地连接服务, 跨数据中心提供负载均衡, 跟踪, 健康检查, 和验证功能. 它同时适用于最后一英里的分布式连接设备, 移动应用和浏览器到后端服务.

主要使用场景

  • 高效连接微服务架构的多语言服务.
  • 连接移动设备, 浏览器客户端到后端服务.
  • 生成高效的客户端库.

核心特性

  • 10种语言惯用客户端库.
  • 高性能报文, 简单的服务定义框架.
  • 基于http/2标准设计.
  • 支持认证, 跟踪, 负载均衡和健康检查.
机器学习

Deployment

uWSGI

https://uwsgi.readthedocs.io/en/latest/index.html

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
先决条件
virtualenv

可以使用虚拟环境

virtualenv uwsgi-tutorial
cd uwsgi-tutorial
source bin/activate
Django

安装 Django , 创建项目, 并切换到目录:

pip install Django
django-admin.py startproject mysite
cd mysite
域名及端口

可以自行设置域名(IP), 及端口.

uWSGI安装及简单配置
在虚拟环境安装uWSGI
pip install uwsgi
pip3 install uwsgi
基础测试

Basic test

Create a file called test.py:

基本测试

创建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:

127.0.0.1:8000

如果返回hello world, 则以下组件工作正常:

the web client <-> uWSGI <-> Python
测试Django项目

确保项目能够跑起来

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, 我们可以在前面加上负载均衡.

Nginx
安装Nginx

Ubuntu

sudo apt-get install nginx
sudo /etc/init.d/nginx start    # start nginx

使用浏览器访问http://localhost:8080确认Nginx是否可以正常运行, 确定如下组件是否正常工作.

the web client <-> the web server
配置Nginx

我们需要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

重启Nginx

sudo /etc/init.d/nginx restart

添加 media.png(随便找张图片)/path/to/your/project/project/media目录, 使用浏览器访问http://localhost:8000/media/media.png , 如果正常, 就可以确定Nginx服务正常.

nginx, uWSGI 测试 test.py
# 指定socket , 指定8001端口
uwsgi --socket :8001 --wsgi-file test.py

访问http://localhost:8000测试

the web client <-> the web server <-> the socket <-> uWSGI <-> Python
使用Unix sockets代替端口

使用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.

浏览器访问http://localhost:8000

如果没有工作

检查 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 和 nginx运行 Django 应用
uwsgi --socket mysite.sock --module mysite.wsgi --chmod-socket=664

现在你的Django应用可以通过uWSGInginx为用户提供服务.

配置 uWSGI 从 .ini文件运行

通过配置文件运行, 可以使你的维护异常简单

创建文件 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

到此为止, 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

检查站点

系统启动是运行uWSGI

使用 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项目
生成项目依赖

生成requirements.txt

使用pip freeze
pip freeze > requirements.txt

这种方式配合 virtualenv 才好使,否则把整个环境中的包都列出来了

使用 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
django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE
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()
ImportError
ImportError: cannot import name UnrewindableBodyError

错误信息:

File "/usr/lib/python2.7/site-packages/urllib3/util/__init__.py", line 4, in <module>
    from .request import make_headers
File "/usr/lib/python2.7/site-packages/urllib3/util/request.py", line 5, in <module>
    from ..exceptions import UnrewindableBodyError
ImportError: cannot import name UnrewindableBodyError

解决办法,重装 urllib3 库:

pip uninstall urllib3
pip install urllib3

Python相关项目

这个Python项目:Cnblog
这个Python项目:Cnblog 是练习Python django的项目
涉及到知识点

python、django、css、html、orm、ajax、、、

地址

tmp

print
输出不换行

Python 2.x print

print x,

Python 3.x

print(x,end="")
print原型

end 默认为 \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][width][.][precision]type

name:可为空,数字(占位),命名(传递参数名,不能以数字开头)以字典格式映射格式化,其为键名

flag:标记格式限定符号,包含+-#和0,+表示右对齐(会显示正负号),-左对齐,前面默认为填充空格(即默认右对齐),0表示填充0,#表示八进制时前面补充0,16进制数填充0x,二进制填充0b

width:宽度(最短长度,包含小数点,小于width时会填充)

precision:小数点后的位数,与C相同

type:输入格式类型,请看上面

还有一种format_spec格式{[name][:][[fill]align][sign][#][0][width][,][.precision][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

pip

安装
通过 get-pip.py 安装

下载 get-pip.py

wget https://bootstrap.pypa.io/get-pip.py

# 获取
python get-pip.py
python3 get-pip.py # 没有python命令,有其他版本的话使用该版本运行
# 使用相应的python可以安装相应2,3的pip
root@ubuntu-linux:~/src# pip -V
pip 9.0.1 from /usr/local/lib/python3.5/dist-packages (python 3.5)
升级

Linux or macOS:

pip install -U pip

Windows:

python -m pip install -U pip
快速开始

先决条件: 安装 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 install -U urllib3==1.22

查看安装的文件

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使用过程中遇到的问题
MacOS OSError: [Errno 1] Operation not permitted

原因是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
配置阿里云pip源

配置文件位置:

全局的位于 /etc/pip.conf
用户级别的位于 $HOME/.pip/pip.conf
每个 virtualenv 也可以有自己的配置文件 $VIRTUAL_ENV/pip.conf
配置文件内容:

[global]
trusted-host=mirrors.aliyun.com
index-url=http://mirrors.aliyun.com/pypi/simple

执行pip的时候,指定源:

# pip版本太旧可能不支持 --trusted-host
pip install --upgrade pip -i http://mirrors.aliyun.com/pypi/simple  --trusted-host mirrors.aliyun.com
cannot install ‘’numpy’.It is a distutils installed project and thus we cannot

强行安装高版本:

pip install numpy --ignore-installed numpy
指定国内源
mkdir ~/.pip
vim ~/.pip/pip.conf
[global]
index-url = http://mirrors.aliyun.com/pypi/simple/
[install]
trusted-host = mirrors.aliyun.com

# pip国内镜像源。
阿里云 http://mirrors.aliyun.com/pypi/simple/
中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
豆瓣 http://pypi.douban.com/simple
Python官方 https://pypi.python.org/simple/
v2ex http://pypi.v2ex.com/simple/
中国科学院 http://pypi.mirrors.opencas.cn/simple/
清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/
python版本管理
使用virtualenv管理多个开发环境

Virtualenv

安装
sudo pip3 install virtualenv virtualenvwrapper

其他安装方法

用法
创建一个新的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多版本

GitHub pyenv

Install
通用

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 -)"
Mac
  1. 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
  1. pyenv init添加到shell

    echo ‘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.

  1. 重启shell或执行如下命令开始使用pyenv

exec $SHELL

  1. 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

GitHub pyenv-virtualenv

pyenv-virtualenv是一个pyenv插件, 在类Unix系统上管理Python virtualenvsconda环境.

安装

通用

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
virtualenv and venv

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

GitHub pyenv-virtualenvwrapper

错误记录
ERROR: The Python zlib extension was not compiled. Missing the zlib?

在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

http://www.seleniumhq.org/

Selenium 自动化浏览器

WebDriver
ChromeDriver - WebDriver for Chrome

方式一: 指定 /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调用
  • 无与伦比的速度
使用 selenium 的 webdrive 驱动 headless chrome
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)
利用Xvfb方式实现伪 headless chrome

当浏览器不支持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:

原因: 粘包

解决: 服务端发送之后,接收一次客户端的确认,再继续发送

1.9. 暂停一秒输出。

import time

time.sleep(1)
uuid 模块
>>> import uuid
>>> uuid.uuid4()
UUID('77d8c6da-4433-4b05-8b8e-a15cb6d01f5e')

>>> str(uuid.uuid4()).replace('-','')
'f0a0e57fb81c432d84c283c1d9d82c73'

>>> str(uuid.uuid4()).replace('-','').upper()
'D98DEA99788241B087CE3352E3639BBC'
webapp

廖雪峰 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

即可显示所有的安装包

部分系统

str

strip() 方法用于移除字符串头尾指定的字符(默认为空格)

isinstance(1,int)

默认参数有个最大的坑

默认参数必须指向不变对象!

默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:

先定义一个函数,传入一个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这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

安装Python

Mac升级python

安装 python 3.x

  1. 方法一: 官网下载需要安装的版本 -> 安装
  2. 方法二: 如果安装了Homebrew,直接通过命令``brew install python3``安装即可

命令行python 然后狂按tab键,会显示很多python,选择需要的,做一个别名,结束

➜  ~ alias python='python3'

永久生效,请写到配置文件

或者直接使用 python3 运行 python3.x

编译安装
安装依赖
Centos
yum install gcc -y
yum install openssl-devel
下载Python源码包
wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz
xz -d Python-3.6.4.tar.xz
tar xf Python-3.6.4.tar
编译, 安装
cd Python-3.6.4
./configure && make && make install

service

服务相关笔记

GitLab

GitLab
环境准备
硬件需求
存储

存储空间的大小主要取决于你将存储的Git仓库的大小。但根据 rule of thumb(经验法则) 你应该考虑多留一些空间用来存储Git仓库的备份。

如果你想使用弹性的存储空间,你可以考虑在分配分区的时候使用LVM架构,这样可以在后期需要的清空下添加硬盘在增加存储空间。

除此之外你还可以挂在一个支持NFS的分卷,比如NAS、 SAN、AWS、EBS。

如果你的服务器有足够大的内存和CPU处理性能,GitLab的响应速度主要受限于硬盘的寻道时间。 使用更快的硬盘(7200转)或者SSD硬盘会很大程度的提升GitLab的响应速度。

CPU
  • 1 核心CPU最多支持100个用户,所有的workers和后台任务都在同一个核心工作这将导致GitLab服务响应会有点缓慢。
  • 2核心 支持500用户,这也是官方推荐的最低标准。
  • 4 核心支持2,000用户。
  • 8 核心支持5,000用户。
  • 16 核心支持10,000用户。
  • 32 核心支持20,000用户。
  • 64 核心支持40,000用户。
  • 如果想支持更多用户,可以使用 集群式架构
Memory

安装使用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
添加GitLab仓库,并安装到服务器上
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
启动GitLab
sudo gitlab-ctl reconfigure
使用浏览器访问GitLab

首次访问GitLab,系统会让你重新设置管理员的密码,设置成功后会返回登录界面. 默认的管理员账号是root,如果你想更改默认管理员账号,请输入上面设置的新密码登录系统后修改帐号名.

使用docker部署GitLab

Maven

Manve

https://maven.apache.org/index.html

Apache Maven是一个软件项目管理综合工具, 基于项目对象模型(POM)的概念, Maven管理项目的构建,报告和文档

安装

先决条件: 已安装JDK, 并配置好环境变量

直接使用二进制包安装

  1. 解压到想要安装的路径
  2. 配置环境变量

验证:

新开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:

Building a Project with Maven

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>

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"
fenfa_key.sh
# !/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
key_provider.sh
[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
使用Ansible

python语言是运维人员必会的语言!

ansible是一个基于Python开发的自动化运维工具!其功能实现基于SSH远程连接服务!

ansible可以实现批量系统配置、批量软件部署、批量文件拷贝、批量运行命令等功能

特点

  1. no agents:不需要在被管控主机上安装任何客户端;
  2. no server:无服务器端,使用时直接运行命令即可;
  3. modules in any languages:基于模块工作,可使用任意语言开发模块;
  4. yaml,not code:使用yaml语言定制剧本playbook;
  5. ssh by default:基于SSH工作;
  6. strong multi-tier solution:可实现多级指挥。

配置文件

  1. ansible 应用程序的主配置文件:/etc/ansible/ansible.cfg
  2. 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
Ansible模块

查看模块

[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
……
ping

测试远程主机的运行状态

[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"
}
command

在远程主机上执行命令

相关选项如下:

  • 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

file

设置文件的属性

相关选项如下

  • 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
copy

复制文件到远程主机

相关选项如下

  • 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
切换到某个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'
cron

定时任务管理

-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
user
系统用户管理
[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
group
系统用户组管理
template
service

系统服务管理

-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"
}
script

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'
ansible简单调优
关闭gathering facts

不需要获取被控机器的 fact 数据,可以关闭fact功能。

只需要在 playbook 文件中加上 gather_facts: false 即可

- hosts: pre-saas-backend
  gather_facts:  false
  ......
开启pipelining

为了兼容不同sudo配置,主要是 requiretty 选项。如果不使用sudo,建议开启。

修改配置文件 /etc/ansible/ansible.cfg

使用长链接

前提:ssh版本高于5.6

修改参数 /etc/ansible/ansible.cfg
ssh_args = -C -o ControlMaster=auto -o ControlPersist=5d
普通用户使用ansible
  1. 创建普通用户
  2. ansible inventory对应的 host key 路径需要属于该普通用户
  3. ansible相关配置文件目录,该普通用户至少拥有 权限

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
httpd2.4配置文件:
# /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页面配置文件;
php
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组件

ELK Stack, 新名字 Elastic 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

一个网络数据包分析器,用于监控、收集网络流量信息,

Packetbeat嗅探服务器之间的流量,解析应用层协议,并关联到消息的处理,其支持 ICMP (v4 and v6)、DNS、HTTP、Mysql、PostgreSQL、Redis、

MongoDB、Memcache等协议;

Filebeat

用于监控、收集服务器日志文件,其已取代 logstash forwarder;

Metricbeat

可定期获取外部系统的监控指标信息,其可以监控、收集Apache、HAProxy、MongoDB、MySQL、Nginx、PostgreSQL、Redis、System、Zookeeper等服务;

Winlogbeat:用于监控、收集Windows系统的日志信息;
Create your own Beat

自定义beat ,如果上面的指标不能满足需求,elasticsarch鼓励开发者使用go语言,扩展实现自定义的beats,只需要按照模板,实现监控的输入,日志,输出等即可。

X-Pack

x-pack是elasticsearch的一个扩展包,将安全,警告,监视,图形,报告和机器学习功能捆绑在一个易于安装的软件包中,虽然x-pack被设计为一个无缝的工作,但是你可以轻松的启用或者关闭一些功能.

参考推荐
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环境

两种方式

jdk8 下载地址

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包进行安装
使用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

参考链接

Elastic Stack and Product Documentation

elastic

kibana

使用仓库安装
## 安装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
Running Logstash on Docker

Running Logstash on Docker

报错
安装时报如下错误:

    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,这样就限制了发生脑裂现象的可能,且保持着高度的可用性:如果你设置了副本,在丢失一个节点的情况下,集群仍可运行
修改elasticsearch内存
/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系列
_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/
第二:_cluster系列
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秒关闭
第三:_nodes系列
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
    }
报错信息
elasticsearch-head安装报错
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镜像
elasticsearch启动报错
[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
启动elasticsearch报错

情况: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
启动elasticsearch报错2
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
配置
logstash 内存大小配置

root@ubuntu47:/etc/logstash# vim jvm.options

# 具体根据服务器硬件配置

-Xms256m
-Xmx256m
logstash.yml配置
root@ubuntu47:/etc/logstash# grep -Ev "^$|#" logstash.yml
path.data: /var/lib/logstash
path.config: /etc/logstash/conf.d
path.logs: /var/log/logstash
filter.conf

以收集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" }
          }
        }

}
}
}
input.conf

cat input.conf

input {
 beats {
   port => 5044
  }
}
output.conf

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监控日志目录,或者指定日志文件,

安装

downloads

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
安装
本地安装

logstash-plugins

在线安装
bin/logstash-plugin install logstash-output-zabbix
由于网络原因,使用下面方式安装

美国开通ecs,使用在线安装,对比差异,提取出以下安装方式

操作之前备份logstash目录

Gemfile
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"
修改logstash/Gemfile.jruby-1.9.lock
/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
新加文件(注意文件属主,属组 logstash)

相关文件已经打包在项目里,文件名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

检查zabbix插件是否安装成功

上述操作完成之后,需要重启logstash,而后通过如下命令验证

root@ubuntu47:/usr/share/logstash# bin/logstash-plugin list|grep zabbix
logstash-output-zabbix
logstash向zabbix发送数据
安装logstash-output-zabbix3
zabbix Web界面配置
logstash-output-zabbix-1

logstash-output-zabbix-1

配置filter
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")
            }
        }
    }
}
配置output
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
        }
    }
}
}
问题记录
[WARN ][logstash.outputs.zabbix ] Field referenced by 1 is missing
将漏掉的 Field 添加到filter中
比如 filter中添加如下配置
            mutate {
                add_field => { "[@metadata][zabbix_key]" => "nginx_status" }
                add_field => { "[@metadata][zabbix_host]" => "ubuntu47" }
          }
[WARN ][logstash.outputs.zabbix ] Zabbix server at 10.29.164.37 rejected all items sent. {:zabbix_host=>“ubuntu47”}
原因: zabbix_value => "1"

修改成如下配置后,解决:
    zabbix_value => "status"
search-guard 5

Search Guard

Search Guard GitHub

Search Guard SSL

安装

安装配置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
node-1 配置插件

配置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
配置HTTPS
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查询语法

参考链接1

参考链接2

全文搜索

在搜索栏输入login,会返回所有字段值中包含login的文档

如果搜索一个短语,可以使用双引号引起来:

"like Gecko"

字段

也可以按页面左侧显示的字段搜索

限定字段全文搜索:field:value

精确搜索:关键字加上双引号 filed:"value"

http.code:404 搜索http状态码为404的文档

字段本身是否存在

_exists_:http:返回结果中需要有http字段

_missing_:http:不能含有http字段

通配符

? 匹配单个字符 * 匹配0到多个字符

kiba?a, el*search

? * 不能用作第一个字符,例如:?text *text

正则

es支持部分正则功能

mesg:/mes{2}ages?/

模糊搜索

~ : 在一个单词后面加上~启用模糊搜索

first~ 也能匹配到 frist

还可以指定需要多少相似度

cromm~0.3 会匹配到 from 和 chrome

数值范围0.0 ~ 1.0,默认0.5,越大越接近搜索的原始值

近似搜索

在短语后面加上~

"select where"~3 表示 select 和 where 中间隔着3个单词以内

范围搜索

数值和时间类型的字段可以对某一范围进行查询

length:[100 TO 200]

date:{"now-6h" TO "now"}

[ ] 表示端点数值包含在范围内,{ } 表示端点数值不包含在范围内

逻辑操作

AND

OR

+:搜索结果中必须包含此项

-:不能含有此项

+apache -jakarta test:结果中必须存在apache,不能有jakarta,test可有可无

分组

(jakarta OR apache) AND jakarta

字段分组

title:(+return +“pink panther”) 转义特殊字符

+ - && || ! () {} [] ^" ~ * ? : \

以上字符当作值搜索的时候需要用\转义

配置文件
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
logstash conf
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

使用docker搭建ftp服务

使用 pure-ftpd

pure-ftpd参考

docker-compose.yml

image : stilliard/pure-ftpd

GitHub

➜  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
创建并启动

这个过程会自动pull stilliard/pure-ftpd 镜像,也可以先pull下来

docker-compose up -d
创建用户

容器内执行:

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

keepalived

mq

kafka
quickstart
单一broker
下载代码
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/
kafka使用zookeeper,首先需要启动zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties
新开窗口,启动kafka
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
查看topic
bin/kafka-topics.sh --list --zookeeper localhost:2181
发送消息
每一行内容会被分为一条消息
启动producer
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
启动一个consumer

kafka有一个命令行的consumer,可以将消息显示到标准输出

新开一个窗口运行如下命令

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning

现在可以在刚刚启动的producer窗口发布消息,你将在consumer中看到

刚刚使用的命令行工具,不加参数运行可以查看帮助信息

kafka-quickstart-2017222

kafka-quickstart-2017222

创建一个多broker集群

在同一台机器创建

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

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导入/导出数据

使用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-quickstart3-2017222

使用Kafka Streams处理数据
准备使用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

kafka-quickstart4-2017222

问题记录
kafka启动失败,提示内存不足
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,视服务器内存而定

Kafka Connect启动报错
[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

nexus

官方网站

Nexus 产品概述

Nexus Repository OSS

组织,存储,分发

安装和运行
使用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 # 查看日志
访问Nexus

浏览器访问 IP:8081

nexus-01-access

nexus-01-access

默认用户 admin 密码 admin123

目录说明
安装目录
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
修改HTTP端口

其他配置

配置文件路径:

$data-dir/etc/nexus.properties

application-port=9081
使用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

hello lua-20161221

OpenResty

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
问题记录
ld returned 1 exit status
    -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源码路径编译安装
日志:

使用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种:

  1. 同步模式(apache-mod_proxy和squid)
  2. 异步模式(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是边收数据边转向用户浏览器。

那么这到底有什么好处呢?

  1. 假设用户执行一个上传文件操作,因为用户网速又比较慢,因此需要花半个小时才能把文件传到服务器。squid的同步代理在用户开始上传后就和后台建立了连接,半小时后文件上传结束,由此可见,后台服务器连接保持了半个小时;而nginx异步代理就是先将此文件收到nginx上,因此仅仅是nginx和用户保持了半小时连接,后台服务器在这半小时内没有为这个请求开启连接,半小时后用户上传结束,nginx才将上传内容发到后台,nginx和后台之间的带宽是很充裕的,所以只花了一秒钟就将请求发送到了后台,由此可见,后台服务器连接保持了一秒。同步传输花了后台服务器半个小时,异步传输只花一秒,可见优化程度很大。
  2. 在上面这个例子中,假如后台服务器因为种种原因重启了,上传文件就自然中断了,这对用户来说是非常恼火的一件事情,想必各位也有上传文件传到一半被中断的经历。用nginx代理之后,后台服务器的重启对用户上传的影响减少到了极点,而nginx是非常稳定的并不需要常去重启它,即使需要重启,利用kill -HUP就可以做到不间断重启nginx。
  3. 异步传输可以令负载均衡器更有保障,为什么这么说呢?在其它的均衡器(lvs/haproxy/apache等)里,每个请求都是只有一次机会的,假如用户发起一个请求,结果该请求分到的后台服务器刚好挂掉了,那么这个请求就失败了;而nginx因为是异步的,所以这个请求可以重新发往下一个后台,下一个后台返回了正常的数据,于是这个请求就能成功了。还是用用户上传文件这个例子,假如不但用了nginx代理,而且用了负载均衡,nginx把上传文件发往其中一台后台,但这台服务器突然重启了,nginx收到错误后,会将这个上传文件发到另一台后台,于是用户就不用再花半小时上传一遍。
  4. 假如用户上传一个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
1.1 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
1.2 站点目录或内部的程序文件没有nginx用户访问权限
[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
1.3 nginx配置文件中设置allow、deny等权限控制,导致客户端没有没权限访问。
[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

简单配置

  1. 在http标签下,添加upstream
upstream linuxidc {
    server 10.0.0.10:7080;
    server 10.0.0.20:8980;
}
  1. 配置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;
负载均衡方式
round-robin(默认)

每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。虽然这种方式简便、成本低廉。 缺点是:可靠性低和负载分配不均衡。适用于图片服务器集群和纯静态页面服务器集群。

weight(权重)
指定轮询几率,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  }
least_conn(最小连接数)
请求发送到激活连接数最少的服务器,服务器权重也会成为选择因素

upstream backend {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
}
least_time(最小响应时间)
least_time header | last_byte;

header表示是计算从后台返回的第一个字节,last_byte计算的是从后台返回的所有数据时间

请求发送到具有最短平均响应时间和最少活动连接数的服务器,同时考虑服务器的权重。如果有几个这样的服务器,则使用加权循环平衡方法依次尝试它们.
hash
请求发送到哪个服务器取决于一个用户端定义的关键词,如文本,变量或两者组合。例如,这个关键词可以是来源IP和端口,或者URI:

upstream backend {
    hash $request_uri consistent;
    server backend1.example.com;
    server backend2.example.com;
}
ip_hash(访问ip)
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

upstream favresin{
    ip_hash;
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
}
fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。与weight分配策略类似。

upstream favresin{
    server 10.0.0.10:8080;
    server 10.0.0.11:8080;
    fair;
}
url_hash(第三方)
按访问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;
}
一致性hash

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;
}
自建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
1.1 修改CA的一些配置文件

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
1.2 生成根密钥

安全起见,修改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
1.3 生成根证书

使用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密钥
2.1 nginx生成key

以上都是在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
2.2 为nginx生成证书签署请求(信息填写和上面自建CA一样)
[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 []:
2.3 将请求通过可靠方式发送给CA主机
[root@ruin ~]# scp /etc/nginx/ssl/nginx.csr root@10.0.11.102:/tmp
2.4 私有CA服务器根据请求来签署证书
[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
2.5 将证书发送给申请者
[root@ruin ~]# scp /etc/pki/CA/certs/nginx.crt root@10.0.11.103:/etc/nginx/ssl/
3 配置nginx https加密
3.1 修改ssl.conf配置文件
[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;

}
3.2 创建ssl目录首页
[root@ruin ~]# mkdir /data/www -p
[root@ruin ~]# echo 'test ssl is ok!' >/data/www/index.html
3.3 修改主配置强制跳转之https
[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 {
    }
}
3.4 检查重启nginx打开浏览器测试
[root@ruin ~]# nginx -t
[root@ruin ~]# nginx -s reload

参考: 基于OpenSSL自建CA和颁发SSL证书 在Nginx服务器中启用SSL的配置方法

supervisor

简单使用

supervisor更改某项目配置后,需要重新启动才有效

在linux服务器上部署了node项目,使用supervisor进行管理,supervisor是个好工具,具体介绍见这里about supervisor

梗概了该项目对的某些配置后,重新启动项目,发现问题仍在,通过日志内容才知道原来新的配置没有被应用。 要使更新的配置得以应用,需要重新启动supervisor服务。具体操作如下: 参考文章

二、更新新的配置到supervisord

supervisorctl update
# 根据新的配置文件,启动新配置或有改动的进程
# 配置没有改动的进程不会受影响而重启
# 用stop停止调的重启,用reload或者update都不会自动重启

三、重新启动配置中的所有程序

supervisorctl reload

四、启动某个进程(program_name=你配置中写的程序名称)

supervisorctl start program_name

五、查看正在守候的进程

supervisorctl

六、停止某一进程 (program_name=你配置中写的程序名称)

supervisorctl stop program_name

七、重启某一进程 (program_name=你配置中写的程序名称)

supervisorctl restart program_name

八、停止全部进程

supervisorctl stop all

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-GatewayJMXJava 程序之间通信获取数据

JMX在Zabbix中的运行流程
1. Zabbix-Server找Zabbix-Java-Gateway获取Java数据
2. Zabbix-Java-Gateway找Java程序(zabbix-agent)获取数据
3. Java程序返回数据给Zabbix-Java-Gateway
4. Zabbix-Java-Gateway返回数据给Zabbix-Server
5. Zabbix-Server进行数据展示
配置JMX监控的步骤
1. 安装Zabbix-Java-Gateway。
2. 配置zabbix_java_gateway.conf参数。
3. 配置zabbix-server.conf参数。
4. Tomcat应用开启JMX协议。
5. ZabbixWeb配置JMX监控的Java应用。
1.配置所有Agent(标准化目录结构)
vim /etc/zabbix/zabbix_agentd.conf #编辑配置文件引用key
    Include=/etc/zabbix/zabbix_agentd.d/*.conf

mkdir /etc/zabbix/scripts #存放Shell脚本
2.安装java以及zabbix-java-gateway (如果源码安装加上–enable-java参数)
yum install  zabbix-java-gateway java-1.8.0-openjdk -y
3.启动zabbix-java-gateway
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

ikev2-vpn-server

镜像

gaomd/ikev2-vpn-server/

GitHub 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 poller processes more than 75% busy

可能原因,zabbix_server不断轮训agent,向agent提取数据,所以报如上错误

解决办法,agent主动将值发送给server

Docker容器监控

步骤:

  1. zabbix-agent 加载 zabbix_module_docker.so 模块(配置之后,重启zabbix-agent)
  2. 导入模块,添加主机
监控方案

使用 zabbix-docker-monitoring 插件。

容器监控参考

gh-pages有已经编译好的so文件,可以直接使用

项目用来持续集成的脚本思路,可以学习

配置服务端

Zabbix 是 C/S 架构,服务端最好能配置在一台独立的宿主机上。

服务端 docker-compose 文件:

version:'2'
  services:
    zabbix:image: monitoringartist/zabbix-xxl
      ports:
        - 8080:80
        - 10051:10051
      volumes:
        - /etc/localtime:/etc/localtime:ro
      depends_on:
        - zabbix.db
      environment:
        ZS_DBHost: zabbix.db
        ZS_DBUser: zabbix
        ZS_DBPassword: zabbix_password
    zabbix.db:
      image: monitoringartist/zabbix-db-mariadb
      volumes:
        - /backups:/backups
        - /etc/localtime:/etc/localtime:rovolumes_from:
        - zabbix-db-storage
      environment:
        MARIADB_USER: zabbix
        MARIADB_PASS: zabbix_password
    zabbix-db-storage:
      image: busybox:latest
      volumes:
        - /var/lib/mysql

容器方式运行 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

  1. 添加 zabbix 用户和组

    groupadd zabbix useradd -g zabbix zabbix

  2. 安装 zabbix-agent

  3. 下载或编译 zabbix_module_docker.so:

  4. 启动 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
  1. 配置加载项

修改 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
  1. 启动失败分析
  • 如果启动失败,查看日志

报错:

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

zabbix-snmp-02

而后将此SNMPINDEX用于其他的 SNMP OID

监控项原型

zabbix-snmp-01

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
zabbix相关问题记录
使用脚本微信报警成功,zabbix自身报警微信未收到消息

原因

脚本没有添加 !/bin/bash

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/

GitHub

安装
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
  1. python版本, 2.6或更高

    python -V

  2. 当运行 setup.py 时, 会自动安装setuptools, 如果想手动安装看这个

  3. 构建 protoc 的C++代码, 或者安装二进制包. 如果安装的二进制包, 确保更当前包版本一致.

    protoc –version

OS X

没安装过, 可以使用如下指令安装

brew install protobuf
  1. 构建并运行测试
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
  1. 安装

    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

命令

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&param2=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
应用
测速
curl -o /dev/null -s -w "%{http_code}\n%{http_connect}\n%{content_type}\n%{time_namelookup}\n%{time_redirect}\n%{time_pretransfer}\n%{time_connect}\n%{time_starttransfer}\n%{time_total}\n%{speed_download}\n" baidu.com
echo
-e
显示颜色
echo显示带颜色,需要使用参数-e man console_codes(不同数字对应的背景字体颜色)
格式
echo -e "\033[字背景颜色;文字颜色m字符串\033[0m"
注:
1. 字背景颜色和文字颜色之间是英文的";"
2. 文字颜色后面有个m
3. 字符串前后可以没有空格,如果有的话,输出也是同样有空格
下面是相应的字和背景颜色,可以自己来尝试找出不同颜色搭配
字颜色:30—–37
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"
字背景颜色范围:40—–47
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 `\b\w+(?=ing\b)',匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找
I'm singing while you’re dancing.
  • (?<=exp) 也叫零宽度正回顾后发断言
    • 它断言自身出现的位置的前面能匹配表达式exp。
    • 比如``(?<=bre)wb``会匹配以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

查看当前有多少在后台运行的命令

fg

将后台中的命令调至前台继续运行

如果后台中有多个命令,可以用 fg %jobnumber 将选中的命令调出.

%jobnumber是通过jobs命令查到的后台正在执行的命令的序号(非pid)

bg

将一个在后台暂停的命令,变成继续执行

如果后台中有多个命令,可以用 bg %jobnumber 将选中的命令调出.

进程的终止
  1. 通过jobs命令查看job号(假设为num),然后执行kill %num
  2. 通过ps命令查看job的进程号(PID,假设为pid),然后执行kill pid
  3. 前台进程的终止: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远程拷贝包含空格的目录或文件

使用反斜线转义,并使用双引号引起来

示例

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
问题
Unable to negotiate with xx.xx.xx.xx port 22: no matching cipher found. Their offer: aes256-cbc,aes128-cbc,3des-cbc,des-cbc

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
wget

下载全站内容

wget --mirror  -p --convert-links -P . http://xxxxx.com
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
help
[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软件列表
yum提示命令不存在
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源的所有包列表
移除epel
yum remove epel...

yum -y update
# 升级所有包,改变软件设置和系统设置,系统版本内核都升级

yum -y upgrade
# 升级所有包,不改变软件设置和系统设置,系统版本升级,内核不改变
安装epel源
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
yum设置软件缓存
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包内容
  1. file.tar.gz

    gzip -dc file.tar.gz | tar tvf -

  2. file.tar.bz2

    bzip2 -dc file.tar.bz2 |tar tvf -

  3. 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

解决办法:

  1. 使用bash执行
  2. 脚本里面添加#!/bin/bash,然后添加执行权限,使用.执行
  3. 修改软连接,指向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
创建随机密码方法
  1. echo $RANDOM|md5sum
  2. openssl rand -base64 48
  3. expect mkpasswd
    • mkpasswd -l 10
  4. date +%N|sha512sum
  5. head /dev/urandom|md5sum
  6. uuidgen|md5sum
    • 加密可以使用 md5sumsha512sum等等,加密之前可以添加一个干扰码,例如: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

Back to TOC

判断用户输入是否为数字
    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

变量

函数里面定义变量

函数里面定义变量 使用 local 防止跟脚本里面的变量冲突 重复

可以学习 /etc/init.d/functions 下面的写法

Linux中变量 $#,$@,$0,$1,$2,$*,$$,$? 的含义
$# 是传给脚本的参数个数
$0 是脚本本身的名字
$1 是传递给该shell脚本的第一个参数
$2 是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
$* 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个
$$ 是脚本运行的当前进程ID号
$? 是显示最后命令的退出状态,0表示没有错误,其他表示有错误

区别: $@ $*

相同点:都是引用所有参数
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数(分别存储在$1 $2 $3)则"$*" 等价于 “$1 $2 $3"(传递了一个参数);而“$@" 等价于 "$1" "$2" "$3"(传递了三个参数)

后台运行shell

使用&符号在后台执行命令

你可以在Linux命令或者脚本后面增加&符号,从而使命令或脚本在后台执行,例如:

./my-shell-script.sh &
使用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)
使用watch连续地执行一个命令

要想按一个固定的间隔不停地执行一个命令,可以使用watch命令,如下所示:

watch df -h

解决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
修改设置时区
  1. tzselect
  2. timeconfig (仅限于RedHat Linux 和 CentOS)
  3. dpkg-reconfigure tzdata (适用于Debian)
  4. 复制相应的时区文件,替换系统时区文件;或者创建链接文件
    • cp /usr/share/zoneinfo/$主时区/$次时区 /etc/localtime
    • 在中国可以使用: cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
时间
查看时间和日期
date
设置时间和日期

将系统日期设定成1996年6月10日的命令

date -s 06/22/96

将系统时间设定成下午1点52分0秒的命令

date -s 13:52:00
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
开机自动挂载

vi /etc/fstab文件里面添加需要挂载的NTFS盘

内容如下:

/dev/sdb1 /ntfs ntfs-3g defaults 0 0

Makefile

设置环境变量

示例:

export FLASK_ENV=dev
export FLASK_DEBUG=1

dev:
    @echo $(FLASK_ENV)
    @echo $(FLASK_DEBUG)

不同target设置不同环境变量:

dev:export FLASK_ENV=dev
dev:export FLASK_DEBUG=1
dev:
    @echo $(FLASK_ENV)
    @echo $(FLASK_DEBUG)

prod:export FLASK_ENV=prod
prod:export FLASK_DEBUG=0
prod:
    @echo $(FLASK_ENV)
    @echo $(FLASK_DEBUG)

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,然后重启程序即可,报警详细截图如下

以下是脚本是分析占用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

参考

服务器主要是php程序

发现内存使用过多

发现 PHP配置的连接模式为静态的,不做回收

修改成动态的即可

pm.dynamic

修改之后,内存占用降低一半以上

ssh加密私钥避免重复输入(keychain)

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
  1. 安装brew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. 使用brew安装 keychain

    brew install keychain

tips

使用 speedtest.net 通过命令行测试带宽

sivel/speedtest-cli

wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py
chmod +x speedtest-cli
./speedtest-cli

vscode使用技巧

删除文件行尾多余空格

VSCODE

VSCODE

vscode 使用七牛图床插件

插件名称 qiniu-upload-image

Install

方法一

Ctrl+p (Mac下为command + p) 输入命令: ext install qiniu-upload-image

方法二

vscode-qiniu1-2017221

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",

使用Python创建简单的HTTP服务

在某个目录下执行, 该目录即是 /

python -m SimpleHTTPServer 80
pip install psutil

使用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

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

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

临时目录

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

C

register

register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。

使用register修饰符的限制

  1. register变量必须是能被CPU所接受的类型。

这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

  1. 因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
  2. 只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。

在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。

  1. 局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;
  2. 由于寄存器的数量有限(不同的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

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

NodeJS

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直接下载安装包安装
安装

Windows使用Git Bash执行下列相关命令

npm install hexo-cli -g
使用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

Maupassant主题——大道至简

github地址

$ 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
  1. 注册GitHub账号
  2. 新建仓库, 命名为username.github.io, username为用户名
  3. 使用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:\Users\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

perl1line

http://www.catonmat.net/download/perl1line.txt

Useful One-Line Scripts for Perl                    Dec 03 2013 | version 1.10
--------------------------------                    -----------   ------------

Compiled by Peteris Krumins (peter@catonmat.net, @pkrumins on Twitter)
http://www.catonmat.net -- good coders code, great reuse

Latest version of this file is always at:

    http://www.catonmat.net/download/perl1line.txt

This file is also available in other languages:

    Chinese: https://github.com/vinian/perl1line.txt

    Please email me peter@catonmat.net if you wish to translate it.

Perl One-Liners on Github:

    https://github.com/pkrumins/perl1line.txt

    You can send me pull requests over GitHub! I accept bug fixes,
    new one-liners, translations and everything else related.

I have also written "Perl One-Liners Explained" ebook that's based on
this file. It explains all the one-liners here. Get it at:

    http://www.catonmat.net/blog/perl-book/

No Starch Press has published "Perl One-Liners" as a real book too:

    http://nostarch.com/perloneliners

These one-liners work both on UNIX systems and Windows. Most likely your
UNIX system already has Perl. For Windows get the Strawberry Perl at:

    http://www.strawberryperl.com/

Table of contents:

    1. File Spacing
    2. Line Numbering
    3. Calculations
    4. String Creation and Array Creation
    5. Text Conversion and Substitution
    6. Selective Printing and Deleting of Certain Lines
    7. Handy Regular Expressions
    8. Perl tricks


FILE SPACING
------------

# Double space a file
perl -pe '$\="\n"'
perl -pe 'BEGIN { $\="\n" }'
perl -pe '$_ .= "\n"'
perl -pe 's/$/\n/'
perl -nE 'say'

# Double space a file, except the blank lines
perl -pe '$_ .= "\n" unless /^$/'
perl -pe '$_ .= "\n" if /\S/'

# Triple space a file
perl -pe '$\="\n\n"'
perl -pe '$_.="\n\n"'

# N-space a file
perl -pe '$_.="\n"x7'

# Add a blank line before every line
perl -pe 's//\n/'

# Remove all blank lines
perl -ne 'print unless /^$/'
perl -lne 'print if length'
perl -ne 'print if /\S/'

# Remove all consecutive blank lines, leaving just one
perl -00 -pe ''
perl -00pe0

# Compress/expand all blank lines into N consecutive ones
perl -00 -pe '$_.="\n"x4'

# Fold a file so that every set of 10 lines becomes one tab-separated line
perl -lpe '$\ = $. % 10 ? "\t" : "\n"'


LINE NUMBERING
--------------

# Number all lines in a file
perl -pe '$_ = "$. $_"'

# Number only non-empty lines in a file
perl -pe '$_ = ++$a." $_" if /./'

# Number and print only non-empty lines in a file (drop empty lines)
perl -ne 'print ++$a." $_" if /./'

# Number all lines but print line numbers only non-empty lines
perl -pe '$_ = "$. $_" if /./'

# Number only lines that match a pattern, print others unmodified
perl -pe '$_ = ++$a." $_" if /regex/'

# Number and print only lines that match a pattern
perl -ne 'print ++$a." $_" if /regex/'

# Number all lines, but print line numbers only for lines that match a pattern
perl -pe '$_ = "$. $_" if /regex/'

# Number all lines in a file using a custom format (emulate cat -n)
perl -ne 'printf "%-5d %s", $., $_'

# Print the total number of lines in a file (emulate wc -l)
perl -lne 'END { print $. }'
perl -le 'print $n=()=<>'
perl -le 'print scalar(()=<>)'
perl -le 'print scalar(@foo=<>)'
perl -ne '}{print $.'
perl -nE '}{say $.'

# Print the number of non-empty lines in a file
perl -le 'print scalar(grep{/./}<>)'
perl -le 'print ~~grep{/./}<>'
perl -le 'print~~grep/./,<>'
perl -E 'say~~grep/./,<>'

# Print the number of empty lines in a file
perl -lne '$a++ if /^$/; END {print $a+0}'
perl -le 'print scalar(grep{/^$/}<>)'
perl -le 'print ~~grep{/^$/}<>'
perl -E 'say~~grep{/^$/}<>'

# Print the number of lines in a file that match a pattern (emulate grep -c)
perl -lne '$a++ if /regex/; END {print $a+0}'
perl -nE '$a++ if /regex/; END {say $a+0}'


CALCULATIONS
------------

# Check if a number is a prime
perl -lne '(1x$_) !~ /^1?$|^(11+?)\1+$/ && print "$_ is prime"'

# Print the sum of all the fields on a line
perl -MList::Util=sum -alne 'print sum @F'

# Print the sum of all the fields on all lines
perl -MList::Util=sum -alne 'push @S,@F; END { print sum @S }'
perl -MList::Util=sum -alne '$s += sum @F; END { print $s }'

# Shuffle all fields on a line
perl -MList::Util=shuffle -alne 'print "@{[shuffle @F]}"'
perl -MList::Util=shuffle -alne 'print join " ", shuffle @F'

# Find the minimum element on a line
perl -MList::Util=min -alne 'print min @F'

# Find the minimum element over all the lines
perl -MList::Util=min -alne '@M = (@M, @F); END { print min @M }'
perl -MList::Util=min -alne '$min = min @F; $rmin = $min unless defined $rmin && $min > $rmin; END { print $rmin }'

# Find the maximum element on a line
perl -MList::Util=max -alne 'print max @F'

# Find the maximum element over all the lines
perl -MList::Util=max -alne '@M = (@M, @F); END { print max @M }'

# Replace each field with its absolute value
perl -alne 'print "@{[map { abs } @F]}"'

# Find the total number of fields (words) on each line
perl -alne 'print scalar @F'

# Print the total number of fields (words) on each line followed by the line
perl -alne 'print scalar @F, " $_"'

# Find the total number of fields (words) on all lines
perl -alne '$t += @F; END { print $t}'

# Print the total number of fields that match a pattern
perl -alne 'map { /regex/ && $t++ } @F; END { print $t }'
perl -alne '$t += /regex/ for @F; END { print $t }'
perl -alne '$t += grep /regex/, @F; END { print $t }'

# Print the total number of lines that match a pattern
perl -lne '/regex/ && $t++; END { print $t }'

# Print the number PI to n decimal places
perl -Mbignum=bpi -le 'print bpi(n)'

# Print the number PI to 39 decimal places
perl -Mbignum=PI -le 'print PI'

# Print the number E to n decimal places
perl -Mbignum=bexp -le 'print bexp(1,n+1)'

# Print the number E to 39 decimal places
perl -Mbignum=e -le 'print e'

# Print UNIX time (seconds since Jan 1, 1970, 00:00:00 UTC)
perl -le 'print time'

# Print GMT (Greenwich Mean Time) and local computer time
perl -le 'print scalar gmtime'
perl -le 'print scalar localtime'

# Print local computer time in H:M:S format
perl -le 'print join ":", (localtime)[2,1,0]'

# Print yesterday's date
perl -MPOSIX -le '@now = localtime; $now[3] -= 1; print scalar localtime mktime @now'

# Print date 14 months, 9 days and 7 seconds ago
perl -MPOSIX -le '@now = localtime; $now[0] -= 7; $now[4] -= 14; $now[7] -= 9; print scalar localtime mktime @now'

# Prepend timestamps to stdout (GMT, localtime)
tail -f logfile | perl -ne 'print scalar gmtime," ",$_'
tail -f logfile | perl -ne 'print scalar localtime," ",$_'

# Calculate factorial of 5
perl -MMath::BigInt -le 'print Math::BigInt->new(5)->bfac()'
perl -le '$f = 1; $f *= $_ for 1..5; print $f'

# Calculate greatest common divisor (GCM)
perl -MMath::BigInt=bgcd -le 'print bgcd(@list_of_numbers)'

# Calculate GCM of numbers 20 and 35 using Euclid's algorithm
perl -le '$n = 20; $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $m'

# Calculate least common multiple (LCM) of numbers 35, 20 and 8
perl -MMath::BigInt=blcm -le 'print blcm(35,20,8)'

# Calculate LCM of 20 and 35 using Euclid's formula: n*m/gcd(n,m)
perl -le '$a = $n = 20; $b = $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $a*$b/$m'

# Generate 10 random numbers between 5 and 15 (excluding 15)
perl -le '$n=10; $min=5; $max=15; $, = " "; print map { int(rand($max-$min))+$min } 1..$n'

# Find and print all permutations of a list
perl -MAlgorithm::Permute -le '$l = [1,2,3,4,5]; $p = Algorithm::Permute->new($l); print @r while @r = $p->next'

# Generate the power set
perl -MList::PowerSet=powerset -le '@l = (1,2,3,4,5); for (@{powerset(@l)}) { print "@$_" }'

# Convert an IP address to unsigned integer
perl -le '$i=3; $u += ($_<<8*$i--) for "127.0.0.1" =~ /(\d+)/g; print $u'
perl -le '$ip="127.0.0.1"; $ip =~ s/(\d+)\.?/sprintf("%02x", $1)/ge; print hex($ip)'
perl -le 'print unpack("N", 127.0.0.1)'
perl -MSocket -le 'print unpack("N", inet_aton("127.0.0.1"))'

# Convert an unsigned integer to an IP address
perl -MSocket -le 'print inet_ntoa(pack("N", 2130706433))'
perl -le '$ip = 2130706433; print join ".", map { (($ip>>8*($_))&0xFF) } reverse 0..3'
perl -le '$ip = 2130706433; $, = "."; print map { (($ip>>8*($_))&0xFF) } reverse 0..3'


STRING CREATION AND ARRAY CREATION
----------------------------------

# Generate and print the alphabet
perl -le 'print a..z'
perl -le 'print ("a".."z")'
perl -le '$, = ","; print ("a".."z")'
perl -le 'print join ",", ("a".."z")'

# Generate and print all the strings from "a" to "zz"
perl -le 'print ("a".."zz")'
perl -le 'print "aa".."zz"'

# Create a hex lookup table
@hex = (0..9, "a".."f")

# Convert a decimal number to hex using @hex lookup table
perl -le '$num = 255; @hex = (0..9, "a".."f"); while ($num) { $s = $hex[($num%16)&15].$s; $num = int $num/16 } print $s'
perl -le '$hex = sprintf("%x", 255); print $hex'
perl -le '$num = "ff"; print hex $num'

# Generate a random 8 character password
perl -le 'print map { ("a".."z")[rand 26] } 1..8'
perl -le 'print map { ("a".."z", 0..9)[rand 36] } 1..8'

# Create a string of specific length
perl -le 'print "a"x50'

# Create a repeated list of elements
perl -le '@list = (1,2)x20; print "@list"'

# Create an array from a string
@months = split ' ', "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
@months = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/

# Create a string from an array
@stuff = ("hello", 0..9, "world"); $string = join '-', @stuff

# Find the numeric values for characters in the string
perl -le 'print join ", ", map { ord } split //, "hello world"'

# Convert a list of numeric ASCII values into a string
perl -le '@ascii = (99, 111, 100, 105, 110, 103); print pack("C*", @ascii)'
perl -le '@ascii = (99, 111, 100, 105, 110, 103); print map { chr } @ascii'

# Generate an array with odd numbers from 1 to 100
perl -le '@odd = grep {$_ % 2 == 1} 1..100; print "@odd"'
perl -le '@odd = grep { $_ & 1 } 1..100; print "@odd"'

# Generate an array with even numbers from 1 to 100
perl -le '@even = grep {$_ % 2 == 0} 1..100; print "@even"'

# Find the length of the string
perl -le 'print length "one-liners are great"'

# Find the number of elements in an array
perl -le '@array = ("a".."z"); print scalar @array'
perl -le '@array = ("a".."z"); print $#array + 1'


TEXT CONVERSION AND SUBSTITUTION
--------------------------------

# ROT13 a string
'y/A-Za-z/N-ZA-Mn-za-m/'

# ROT 13 a file
perl -lpe 'y/A-Za-z/N-ZA-Mn-za-m/' file

# Base64 encode a string
perl -MMIME::Base64 -e 'print encode_base64("string")'
perl -MMIME::Base64 -0777 -ne 'print encode_base64($_)' file

# Base64 decode a string
perl -MMIME::Base64 -le 'print decode_base64("base64string")'
perl -MMIME::Base64 -ne 'print decode_base64($_)' file

# URL-escape a string
perl -MURI::Escape -le 'print uri_escape($string)'

# URL-unescape a string
perl -MURI::Escape -le 'print uri_unescape($string)'

# HTML-encode a string
perl -MHTML::Entities -le 'print encode_entities($string)'

# HTML-decode a string
perl -MHTML::Entities -le 'print decode_entities($string)'

# Convert all text to uppercase
perl -nle 'print uc'
perl -ple '$_=uc'
perl -nle 'print "\U$_"'

# Convert all text to lowercase
perl -nle 'print lc'
perl -ple '$_=lc'
perl -nle 'print "\L$_"'

# Uppercase only the first word of each line
perl -nle 'print ucfirst lc'
perl -nle 'print "\u\L$_"'

# Invert the letter case
perl -ple 'y/A-Za-z/a-zA-Z/'

# Camel case each line
perl -ple 's/(\w+)/\u$1/g'
perl -ple 's/(?<!['])(\w+)/\u\1/g'

# Strip leading whitespace (spaces, tabs) from the beginning of each line
perl -ple 's/^[ \t]+//'
perl -ple 's/^\s+//'

# Strip trailing whitespace (space, tabs) from the end of each line
perl -ple 's/[ \t]+$//'

# Strip whitespace from the beginning and end of each line
perl -ple 's/^[ \t]+|[ \t]+$//g'

# Convert UNIX newlines to DOS/Windows newlines
perl -pe 's|\n|\r\n|'

# Convert DOS/Windows newlines to UNIX newlines
perl -pe 's|\r\n|\n|'

# Convert UNIX newlines to Mac newlines
perl -pe 's|\n|\r|'

# Substitute (find and replace) "foo" with "bar" on each line
perl -pe 's/foo/bar/'

# Substitute (find and replace) all "foo"s with "bar" on each line
perl -pe 's/foo/bar/g'

# Substitute (find and replace) "foo" with "bar" on lines that match "baz"
perl -pe '/baz/ && s/foo/bar/'

# Binary patch a file (find and replace a given array of bytes as hex numbers)
perl -pi -e 's/\x89\xD8\x48\x8B/\x90\x90\x48\x8B/g' file


SELECTIVE PRINTING AND DELETING OF CERTAIN LINES
------------------------------------------------

# Print the first line of a file (emulate head -1)
perl -ne 'print; exit'

# Print the first 10 lines of a file (emulate head -10)
perl -ne 'print if $. <= 10'
perl -ne '$. <= 10 && print'
perl -ne 'print if 1..10'

# Print the last line of a file (emulate tail -1)
perl -ne '$last = $_; END { print $last }'
perl -ne 'print if eof'

# Print the last 10 lines of a file (emulate tail -10)
perl -ne 'push @a, $_; @a = @a[@a-10..$#a]; END { print @a }'

# Print only lines that match a regular expression
perl -ne '/regex/ && print'

# Print only lines that do not match a regular expression
perl -ne '!/regex/ && print'

# Print the line before a line that matches a regular expression
perl -ne '/regex/ && $last && print $last; $last = $_'

# Print the line after a line that matches a regular expression
perl -ne 'if ($p) { print; $p = 0 } $p++ if /regex/'

# Print lines that match regex AAA and regex BBB in any order
perl -ne '/AAA/ && /BBB/ && print'

# Print lines that don't match match regexes AAA and BBB
perl -ne '!/AAA/ && !/BBB/ && print'

# Print lines that match regex AAA followed by regex BBB followed by CCC
perl -ne '/AAA.*BBB.*CCC/ && print'

# Print lines that are 80 chars or longer
perl -ne 'print if length >= 80'

# Print lines that are less than 80 chars in length
perl -ne 'print if length < 80'

# Print only line 13
perl -ne '$. == 13 && print && exit'

# Print all lines except line 27
perl -ne '$. != 27 && print'
perl -ne 'print if $. != 27'

# Print only lines 13, 19 and 67
perl -ne 'print if $. == 13 || $. == 19 || $. == 67'
perl -ne 'print if int($.) ~~ (13, 19, 67)'

# Print all lines between two regexes (including lines that match regex)
perl -ne 'print if /regex1/../regex2/'

# Print all lines from line 17 to line 30
perl -ne 'print if $. >= 17 && $. <= 30'
perl -ne 'print if int($.) ~~ (17..30)'
perl -ne 'print if grep { $_ == $. } 17..30'

# Print the longest line
perl -ne '$l = $_ if length($_) > length($l); END { print $l }'

# Print the shortest line
perl -ne '$s = $_ if $. == 1; $s = $_ if length($_) < length($s); END { print $s }'

# Print all lines that contain a number
perl -ne 'print if /\d/'

# Find all lines that contain only a number
perl -ne 'print if /^\d+$/'

# Print all lines that contain only characters
perl -ne 'print if /^[[:alpha:]]+$/

# Print every second line
perl -ne 'print if $. % 2'

# Print every second line, starting the second line
perl -ne 'print if $. % 2 == 0'

# Print all lines that repeat
perl -ne 'print if ++$a{$_} == 2'

# Print all unique lines
perl -ne 'print unless $a{$_}++'

# Print the first field (word) of every line (emulate cut -f 1 -d ' ')
perl -alne 'print $F[0]'


HANDY REGULAR EXPRESSIONS
-------------------------

# Match something that looks like an IP address
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
/^(\d{1,3}\.){3}\d{1,3}$/

# Test if a number is in range 0-255
/^([0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/

# Match an IP address
my $ip_part = qr|([0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|;
if ($ip =~ /^($ip_part\.){3}$ip_part$/) {
 say "valid ip";
}

# Check if the string looks like an email address
/\S+@\S+\.\S+/

# Check if the string is a decimal number
/^\d+$/
/^[+-]?\d+$/
/^[+-]?\d+\.?\d*$/

# Check if the string is a hexadecimal number
/^0x[0-9a-f]+$/i

# Check if the string is an octal number
/^0[0-7]+$/

# Check if the string is binary
/^[01]+$/

# Check if a word appears twice in the string
/(word).*\1/

# Increase all numbers by one in the string
$str =~ s/(\d+)/$1+1/ge

# Extract HTTP User-Agent string from the HTTP headers
/^User-Agent: (.+)$/

# Match printable ASCII characters
/[ -~]/

# Match unprintable ASCII characters
/[^ -~]/

# Match text between two HTML tags
m|<strong>([^<]*)</strong>|
m|<strong>(.*?)</strong>|

# Replace all <b> tags with <strong>
$html =~ s|<(/)?b>|<$1strong>|g

# Extract all matches from a regular expression
my @matches = $text =~ /regex/g;


PERL TRICKS
-----------

# Print the version of a Perl module
perl -MModule -le 'print $Module::VERSION'
perl -MLWP::UserAgent -le 'print $LWP::UserAgent::VERSION'


PERL ONE-LINERS EXPLAINED E-BOOK
--------------------------------

I have written an ebook based on the one-liners in this file. If you wish to
support my work and learn more about these one-liners, you can get a copy
of my ebook at:

    http://www.catonmat.net/blog/perl-book/

The ebook is based on the 7-part article series that I wrote on my blog.
In the ebook I reviewed all the one-liners, improved explanations, added
new ones, and added two new chapters - introduction to Perl one-liners
and summary of commonly used special variables.

You can read the original article series here:

    http://www.catonmat.net/blog/perl-one-liners-explained-part-one/
    http://www.catonmat.net/blog/perl-one-liners-explained-part-two/
    http://www.catonmat.net/blog/perl-one-liners-explained-part-three/
    http://www.catonmat.net/blog/perl-one-liners-explained-part-four/
    http://www.catonmat.net/blog/perl-one-liners-explained-part-five/
    http://www.catonmat.net/blog/perl-one-liners-explained-part-six/
    http://www.catonmat.net/blog/perl-one-liners-explained-part-seven/


CREDITS
-------

Andy Lester       http://www.petdance.com
Shlomi Fish       http://www.shlomifish.org
Madars Virza      http://www.madars.org
caffecaldo        https://github.com/caffecaldo
Kirk Kimmel       https://github.com/kimmel
avar              https://github.com/avar
rent0n


FOUND A BUG? HAVE ANOTHER ONE-LINER?
------------------------------------

Email bugs and new one-liners to me at peter@catonmat.net!


HAVE FUN
--------

I hope you found these one-liners useful. Have fun!

#---end of file---

svn

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun

Have you upgraded to OS X El Cap­i­tan from App Store ?

Have you sud­denly started get­ting the fol­low­ing 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

Remem­ber, in MAC git is attached to XCode’s Com­mand 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 即可

tmp

禁ping

iptables -A INPUT -p icmp --icmp-type 8 -s 0/0 -j DROP

分布式系统-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阶段分为两个,首先是选举过程,然后在选举出来的领导人带领进行正常操作,比如日志复制等。下面用图示展示这个过程

  1. 任何一个服务器都可以成为一个候选者Candidate,它向其他服务器Follower发出要求选举自己的请求
  2. 其他服务器同意了,发出OK。 如果在这个过程中,有一个Follower宕机,没有收到请求选举的要求,因此候选者可以自己选自己,只要达到N/2 + 1 的大多数票,候选人还是可以成为Leader的。
  3. 这样这个候选者就成为了Leader领导人,它可以向选民也就是Follower们发出指令,比如进行日志复制。
  4. 以后通过心跳进行日志复制的通知
  5. 如果一旦这个Leader当机崩溃了,那么Follower中有一个成为候选者,发出邀票选举。
  6. 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)以及特殊字符(称为“元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

普通字符

普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。

非打印字符

非打印字符也可以是正则表达式的组成部分。下表列出了表示非打印字符的转义序列:

\cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。
x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。

\f 匹配一个换页符。等价于 \x0c 和 \cL。

\n 匹配一个换行符。等价于 \x0a 和 \cJ。

\r 匹配一个回车符。等价于 \x0d 和 \cM。

\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。

\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。

\t 匹配一个制表符。等价于 \x09 和 \cI。

\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
特殊字符

所谓特殊字符,就是一些有特殊含义的字符,如上面说的“.txt“中的*,简单的说就是表示任何字符串的意思。 如果要查找文件名中有的文件,则需要对*进行转义,即在其前加一个。ls *.txt。

许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符“转义”,即,将反斜杠字符 () 放在它们前面。下表列出了正则表达式中的特殊字符:

$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。

( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。

* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。

+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。

. 匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \。

[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[。

? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。

\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\\’ 匹配 “\”,而 ‘\(’ 则匹配 “(”。

^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。

{ 标记限定符表达式的开始。要匹配 {,请使用 \{。

| 指明两项之间的一个选择。要匹配 |,请使用 \|。
限定符

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有*或+或?或{n}或{n,}或{n,m}共6种。

正则表达式的限定符有:

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 "zoo",但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 或 “does” 中的“do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ’o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。

由于章节编号在大的输入文档中会很可能超过九,所以您需要一种方式来处理两位或三位章节编号。限定符给您这种能力。下面的正则表达式匹配编号为任何位数的章节标题:

/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>

/<.*?>/

通过在 *、+ ? 限定符之后放置 ?,该表达式从“贪心”表达式转换为“非贪心”表达式或者最小匹配。

定位符

定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。

定位符用来描述字符串或单词的边界,^和$分别指字符串的开始与结束,\b``描述单词的前或后边界,B``表示非单词边界。

正则表达式的限定符有:

^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,
^ 还会与 \n 或 \r 之后的位置匹配。

$ 匹配输入字符串结尾的位置。如果设置了RegExp 对象的 Multiline 属性,
$ 还会与 ``\n``或 ``\r``之前的位置匹配。

\b 匹配一个字边界,即字与空格间的位置。

\B 非字边界匹配。

注意:不能将限定符与定位点一起使用。由于在紧靠换行或者字边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。

若要匹配一行文本开始处的文本,请在正则表达式的开始使用 ^ 字符。不要将 ^ 的这种用法与中括号表达式内的用法混淆。

若要匹配一行文本的结束处的文本,请在正则表达式的结束处使用 $ 字符。

若要在搜索章节标题时使用定位点,下面的正则表达式匹配一个章节标题,该标题只包含两个尾随数字,并且出现在行首:

/^Chapter [1-9][0-9]{0,1}/

真正的章节标题不仅出现行的开始处,而且它还是该行中仅有的文本。它即出现在行首又出现在同一行的结尾。下面的表达式能确保指定的匹配只匹配章节而不匹配交叉引用。通过创建只匹配一行文本的开始和结尾的正则表达式,就可做到这一点。

/^Chapter [1-9][0-9]{0,1}$/

匹配字边界稍有不同,但向正则表达式添加了很重要的能力。字边界是单词和空格之间的位置。非字边界是任何其他位置。下面的表达式匹配单词 Chapter 的开头三个字符,因为这三个字符出现字边界后面:

/\bCha/ # ``\b``字符的位置是非常重要的

如果它位于要匹配的字符串的开始,它在单词的开始处查找匹配项。如果它位于字符串的结尾,它在单词的结尾处查找匹配项。例如,下面的表达式匹配单词 Chapter 中的字符串 ter,因为它出现在字边界的前面:

/ter\b/

下面的表达式匹配 Chapter 中的字符串 apt,但不匹配 aptitude 中的字符串 apt:

/\Bapt/ # 字符串 ``apt`` 出现在单词 ``Chapter`` 中的非字边界处,
但出现在单词 ``aptitude`` 中的字边界处。对于
``\B``非字边界运算符,位置并不重要,因为匹配不关心究竟是单词的开头还是结尾。
选择

用圆括号将所有选择项括起来,相邻的选择项之间用|分隔。但用圆括号会有一个副作用,是相关的匹配会被缓存,此时可用?:放在第一个选项前来消除这种副作用。

其中 ?: 是非捕获元之一,还有两个非捕获元是?=和?!,这两个还有更多的含义, 前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。

反向引用

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\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)
设计模式六大原则
  1. 开闭原则(Open Close Principle)
  2. 里氏代换原则(Liskov Substitution Principle)
  3. 依赖倒转原则(Dependence Inversion Principle)
  4. 接口隔离原则(Interface Segregation Principle)
  5. 迪米特法则(最少知道原则)(Demeter Principle)
  6. 合成复用原则(Composite Reuse Principle)

问题记录

mount.nfs:Stale file handle

当你在你的环境中使用网络文件系统时,你一定不时看到 mount.nfs:Stale file handle 错误。此错误表示 NFS 共享无法挂载,因为自上次配置后有些东西已经更改。

无论是你重启 NFS 服务器或某些 NFS 进程未在客户端或服务器上运行,或者共享未在服务器上正确输出,这些都可能是导致这个错误的原因。此外,当这个错误发生在先前挂载的 NFS 共享上时,它会令人不快。因为这意味着配置部分是正确的,因为是以前挂载的。

重新挂载即可解决,如果nginx有使用到该目录,错误日志中提示 Stale file handle,重启nginx

tools

git

Git
分布式版本控制

分布式版本控制的每个节点都是完整仓库

svn集中式版本控制

svn中央仓库挂了, 无法根据本地项目重新搭建一个服务器, 因为本地没有历史版本

名词
Workspace:工作区
Index / Stage:暂存区
Repository:仓库区(或本地仓库)
Remote:远程仓库
常用命令速查表
git-command

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

生成.gitignore文件内容

有些时候, 你必须把某些文件放到Git工作目录中, 但又不能提交它们, 比如保存了数据库密码的配置文件啦, 等等, 每次git status都会显示Untracked files …, 有强迫症的童鞋心里肯定不爽。

解决办法: 在Git工作区的根目录下创建一个特殊的.gitignore文件, 然后把要忽略的文件名填进去, Git就会自动忽略这些文件。

不需要从头写.gitignore文件, GitHub已经为我们准备了各种配置文件, 只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:https://github.com/github/gitignore

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件, 比如缩略图等;
  2. 忽略编译生成的中间文件、可执行文件等, 也就是如果一个文件是通过另一个文件自动生成的, 那自动生成的文件就没必要放进版本库, 比如Java编译产生的.class文件;
  3. 忽略你自己的带有敏感信息的配置文件, 比如存放口令的配置文件。

编写.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

生成.gitignore文件内容

删除错误提交
# 彻底回退到某个版本
git reset --hard <commit_id>
# 强制推送
git push origin HEAD --force
删除远程分支
# 查看远程分支
git branch -

删除远程分支

git branch -r -d origin/branch-name
git push origin :branch-name
全局忽略 .DS_Store
  1. 创建 ~/.gitignore_global 文件
  2. 把需要全局忽略的文件类型写到这个文件里。
# .gitignore_global
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
BFG Repo-Cleaner

可以像 git-filter-branch 一样清除提交历史, 比如删除无用提交, 删除仓库大文件

https://rtyley.github.io/bfg-repo-cleaner/

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
Gists

REST API v3

请求地址 https://api.github.com

认证

scopes-for-oauth

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
GET /gists/:id

示例

curl https://api.github.com/gists/689019b5c033a931096396cb609f5c49
创建 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)

raw_url

编辑 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"
},
其他详见官方开发文档
GitBook 入门
安装
安装 Node.js

下载地址

或者

Mac
wget https://nodejs.org/dist/v7.8.0/node-v7.8.0.pkg
安装 GitBook

首先需要安装Node.js,然后使用npm安装gitbook

# 安装
npm install gitbook-cli -g

# 检查
gitbook -V
gitbook help
# 获取帮助信息
$ gitbook help
GitBook使用

Gitbook有几个主要的文件

README.md

这个文件相当于一本Gitbook的简介,最上层(和 SUMMARY.md 文件同级)的 README.md 文件 最终会被用于生成本书的 Introduction

SUMMARY.MD

该文件是书的目录结构,使用Markdown语法,这个文件在使用gitbook命令行工具之前要先写好,以便生成书籍目录.

gitbook editor

gitbook editor 实际上就是一个本地应用版的在线编辑器,使用方式和 gitbook在线编辑器类似,而这里的目录及章节是使用编辑器添加生成,同时也会存放到本地之前建立的目录.

导出图书

目前为止,Gitbook支持如下格式输出:

  • 静态HTML,可以看作一个静态网站
  • PDF格式
  • eBook格式
  • 单个HTML文件
  • JSON格式
输出为HTML
➜  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 目录,这个目录就是就是生成的静态网站内容.

使用build参数生成到指定目录

在图书目录的上层目录使用如下命令生成

➜  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 !
生成 eBooks 和 PDFs
$ gitbook pdf ./ ./mybook.pdf

# Generate an ePub file
$ gitbook epub ./ ./mybook.epub

# Generate a Mobi file
$ gitbook mobi ./ ./mybook.mobi
安装 ebook-convert

生成 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

Covers are used for all the ebook formats. You can either provide one yourself, or generate one using the autocover plugin.

输出PDF

由于生成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

gitbook 文档

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"
]
插件安装方法

方法一:

  1. 配置book.json 下的 plugins
  2. 然后执行命令 gitbook install(自动安装插件)
"plugins": [
    "toggle-chapters",
    "theme-comscore",
    "splitter",
    "-sharing",
    "search"
],

完整配置请查看

book.json 配置示例

方法二:

npm install gitbook-plugin-multipart -g
然后编辑 book.json 添加 multipart 到 plugins 中:

```json
    "plugins": [
        "multipart"
    ],
```
指定插件版本

"myPlugin@0.3.1"

主题插件
ComScore

ComScore 是一个彩色主题,默认的 gitbook 主题是黑白的,也就是标题和正文都是黑色的,而 ComScore 可以为各级标题添加不同的颜色,更容易区分各级标题

anchor-navigation-ex
实用插件
  • disqus, 集成用户评论系统
  • multipart 插件可以将书籍分成几个部分,例如:
    • GitBook Basic
    • GitBook Advanced
    • 对有非常多章节的书籍非常有用,分成两部分后,各个部分的章节都从 1 开始编号。
  • toggle-chapters 使左侧的章节目录可以折叠
  • Splitter 使侧边栏的宽度可以自由调节
发布
GitBook发布
GitHub托管,集成

md托管到GitHub

GitHub Pages

直接手动将生成的_book推送到GitHub仓库即可

或者使用Grunt发布

grunt-gh-pages插件

模板

https://toolchain.gitbook.com/templating/

忽略特殊的模板标签, 输出为纯文本。

{% raw %}
  this will {{ not be processed }}
{% endraw %}
Git问题记录
Git Fatal: cannot do a partial commit during a merge

在提交单个文件的时候出现这个错误.

意思是不能部分提交代码.

原因是Git认为你有部分代码没有做好提交的准备,比如没有添加

解决方法是

  1. 提交全部

    git commit -a -m “xxx”

  2. 如果不想提交全部,那么可以通过添加 -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到本地。这样就产生了版本冲突的问题。

有如下几种解决方法:

  1. 使用强制push的方法:
git push -u origin master -f
# 这样会使远程修改丢失,一般是不可取的,尤其是多人协作开发的时候。
  1. push前先将远程repository修改pull下来
git pull origin master
git push -u origin master
  1. 若不想merge远程和本地修改,可以先创建新的分支
git branch [name]
# 然后push
git push -u origin [name]
git merge报错,fatal: refusing to merge unrelated histories
"git merge" used to allow merging two branches that have no common base by default, which led to a brand new history of an existing project created and then get pulled by an unsuspecting maintainer, which allowed an unnecessary parallel history merged into the existing project. The command has been taught not to allow this by default, with an escape hatch "--allow-unrelated-histories" option to be used in a rare event that merges histories of two projects that started their lives independently.
See the git release changelog for more information.

You can use --allow-unrelated-histories to force the merge to happen.

git merge --allow-unrelated-histories test
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,主要有如下几种模式:

命令模式(Normal mode)

一进入VIM就是处于命令模式,该模式下所有键盘输入都作为命令来对待,不会输入都文件里,其他任何模式都可以通过Esc键回到命令模式;

编辑模式(Insert mode)

在命令模式中输入a、i、A、I、o、O等命令即可进入该模式,此时在状态列会有INSERT字样。在该模式下才能输入文字,按Esc键回到命令模式;

命令行模式模式(Command-line mode)

在命令模式中输入“:”(一般命令)、“/”(正向搜索)或“?”(反向搜索)进入该模式。此时屏幕左下角出现一个冒号提示符,等待输入命令,命令行模式下的命令输入完成后按Enter键才会执行,按Esc键回到命令模式;

可视模式(Visual mode)

在命令模式中通过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而不读入任何文件
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的帮助指令
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. 快速移动
  2. 不要两次键入同样的东西
  3. 错误修复
  4. 经常需要编辑不止一个文件
  5. 协同作业
  6. 文本是结构化的
  7. 养成习惯
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技巧
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
关闭自动补全
解决方法如下:

:set noautoindent
:set nosmartindent

:set noai
:set nosi
关闭自动注释
:set comments=

Sphinx

Docs

安裝
pip install Sphinx
支持markdown

http://www.sphinx-doc.org/en/stable/markdown.html

安装 recommonmark

pip install recommonmark

添加 Markdown parser 到 Sphinx 配置文件变量 source_parsers 中 :

from recommonmark.parser import CommonMarkParser

source_parsers = {
    '.md': CommonMarkParser,
}

我们也可设置 .md 后缀为其他名字.

添加 Markdown filename extension 到配置文件前缀 source_suffix 配置变量中:

source_suffix = ['.rst', '.md']

You can further configure recommonmark to allow custom syntax that standard CommonMark doesn’t support. Read more in the recommonmark documentation.

AutoStructify组件

支持markdown转换到rst的高级用法.

首先需要在Sphinx配置文件 conf.py 中添加配置:

# At top on conf.py (with other import statements)
import recommonmark
from recommonmark.transform import AutoStructify

# At the bottom of conf.py
def setup(app):
    app.add_config_value('recommonmark_config', {
            'url_resolver': lambda url: github_doc_root + url,
            'auto_toc_tree_section': 'Contents',
            }, True)
    app.add_transform(AutoStructify)

有如下配置:

  • enable_auto_toc_tree: 启用自动标题树特性.
  • auto_toc_tree_section: 为 True 时, 自动标题树仅会在标题与章节匹配的时候启用.
  • enable_auto_doc_ref: 启用 Auto Doc Ref 特性.
  • enable_math: 数学公式特性.
  • enable_inline_math: 内联数学特性.
  • enable_eval_rst: 启用嵌入式 reStructuredText 特性.
  • url_resolver: 映射文档相对路径到http链接.

详细使用方法参考: http://recommonmark.readthedocs.io/en/latest/auto_structify.html#

sphinx-autobuild

自动生成html, 并启动server

# 安装sphinx-autobuild
pip install sphinx-autobuild
# 监听 source 目录, 生成的文件放置于 _build/html
sphinx-autobuild source _build/html

Server 默认端口 8000, http://127.0.0.1:8000/

theme
# 安装
pip install sphinx_rtd_theme

# 配置
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'

HTML theming support

插入图片
资源文件放置于 source/_static 目录下, 绝对路径从 /_static 开始, 也可使用相对路径
.. image:: /_static/5g.jpg

reStructuredText 入门

常用格式
标题
===================
这就是一个标题
===================

----------------
这也是一个章节标题
----------------

reStructuredText 推荐使用这些字符: = - ` : . ' " ~ ^ _ * + #

标题支持: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~

标题可嵌套, 可以只写下半部分

这个标题和上面的一样
===================
段落

段落一般隶属于某个章节中,是一块左对齐并且没有其他元素体标记的块。在``.rst``文件中,段落和其他内容的分割是靠空行来完成,如果段落相较于其他的段落有缩进,reStructuredText会解析为引用段落,样式上有些不同。

这里是一段reStructuredText的内容,它可以很长很长。。。。最后,末尾留出空行表示是本段落的结束即可。

这里是另外一段reStructuredText的内容,这段内容和上一段之间,乃至后面的其他内容之间都要留出空行进行分割。

    这个也是段落,但是由于缩进了,会变成引用段落。显示和直接的段落有点不同
列表

列表在HTML中被分为两种

  • 有序列表(Enumerated Lists)
  • 无序列表(Bullet Lists)

在reStructuredText中,我们也能找到这两种列表,还有一种称为定义列表(Definition Lists),这和HTML中的DL一样,在``.rst``文件中可以支持嵌套列表。

无序列表要求文本块是以下面这些字符开始,并且后面紧跟空格,而后跟列表项的内容,其中列表项比趋势左对齐并且有与列表对应的缩进。

常用字符: * + -

- 这里是列表的第一个列表项
- 这是第二个列表项
- 这是第三个列表项
    - 这是缩进的第一个列表项
        注意,这里的缩进要和当前列表项的缩进同步。
- 第一级的第四个列表项

- 列表项之间要用个空格来分割。

有序列表在格式上和无序列表差不多,但是在使用的前缀修饰符上,使用的不是无序列表那种字符,而是可排序的字符,可以识别的有下面这些:

arabic numerals: 1, 2, 3, ... (no upper limit).
uppercase alphabet characters: A, B, C, ..., Z.
lower-case alphabet characters: a, b, c, ..., z.
uppercase Roman numerals: I, II, III, IV, ..., MMMMCMXCIX (4999).
lowercase Roman numerals: i, ii, iii, iv, ..., mmmmcmxcix (4999).

如果你不想使用这些,在你标明第一个条目的序号字符后,第二个开始你还可以使用”#”号来让reStructuredText自动生成需要的序号(Docutils >= 0.3.8)。

1. 第一项
    巴拉巴拉好多内容在这里。。。

#. 第二项

    a. 第二项的第一小项

    #. 第二项的第二小项

#. 第三项

定义列表:每个定义列表项里面包含术语(term),分类器(classifiers,可选), 定义(definition)。术语是一行文字或者短语,分类器跟在术语后面,用“ : ”(空格,冒号,空格)分隔。定义是相对于术语缩进后的一个块。定义中可以包含多个段落或者其他的内容元素。术语和定义之间可以没有空行,但是在定义列表前后必须要有空行的存在。下面是示例:

术语1
    术语1的定义

术语 2
    术语2的定义,这是第一段

    术语2的定义,第二段

术语 3 : 分类器
    术语3的定义


术语 4 : 分类器1 : 分类器2
    术语4的定义

TIPS:在reStructuredText中,还有两种列表, 一种是字段列表(Field Lists), 一种是选项列表(Option Lists)。由于是rst的语法入门教程,这里不做深入介绍

表格(Table)

reStructuredText提供两种表格:网格表格(Grid Tables), 简单表格(Simple Tables)。

网格表中,共使用的符号有:

- = | +
“-” 用来分隔行, “=“ 用来分隔表头和表体行,"|" 用来分隔列,而"+"用来表示行和列相交的节点,如下面的例子
+------------------------+------------+----------+----------+
| Header row, column 1   | Header 2   | Header 3 | Header 4 |
| (header rows optional) |            |          |          |
+========================+============+==========+==========+
| body row 1, column 1   | column 2   | column 3 | column 4 |
+------------------------+------------+----------+----------+
| body row 2             | Cells may span columns.          |
+------------------------+------------+---------------------+
| body row 3             | Cells may  | - Table cells       |
+------------------------+ span rows. | - contain           |
| body row 4             |            | - body elements.    |
+------------------------+------------+---------------------+

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

Jupyter

python3
python3 -m pip install --upgrade pip
python3 -m pip install jupyter

运行 notebook

jupyter notebook

PPTP和OpenVPN有什么区别

PPTP点对点隧道协议(PPTP)是一种实现虚拟专用网络的方法。 PPTP使用用于封装PPP数据包的TCP及GRE隧道控制通道。
OpenVPNOpenVPN是一免费开源软件,以路由器或桥接配置和远程访问设备方式实现虚拟专用网络(VPN)创建安全的点对点或站对站连接的解决方案。它使用SSL / TLS安全加密,具有穿越网络地址转换(NATs)和防火墙的功能。
在PPTP和OpenVPN二者之间做出选择的一个重要考虑因素,也是我们无法控制的因素,就是有时互联网服务供应商会阻止PPTP连接。次情况下我们无计可施,只能选择使用OpenVPN。 PPTP具有一些独特优势,但此刻用OpenVPN会是不错的选择。
PPTP可以应用到几乎所有的操作系统软件,无需安装任何软件。它也兼容许多移动设备,如iphone,ipad和Windows移动,安装简易。相比之下,OpenVPN的安装比PPTP要复杂一点,但只要按照正确的指示安装则无太大困难。请注意OpenVPN不兼容移动设备。
PPTP加密技术使用密码作为密钥,它的数据流载有可获取的混编密码。如果中间有人拦截到了数据流并且破译了密码(尽管可能但很难),那么他就可以破译你的信息。然而OpenVPN使用非常强大的加密(Blowfish)技术。即使有人拦截你的数据流,他们也无计可施。这使得OpenVPN比PPTP安全得多。
选择如果你希望得到高安全性以及更加关注数据安全传输问题,那么你应该使用OpenVPN。如果您为了简便或者想在移动设备上使用VPN那么PPTP适合你。还有其他协议,例如L2P或IPSec,但他们在用户友好或成本上没有优势。

PyCharm

新文件默认换行符

Settings -> Editor -> Code Style -> Line separator (for new files)

Visual Studio Code

常用插件
Visual Studio Code Settings Sync

使用插件同步vscode配置

其实就是借助的 GitHub Gist, Gist可以设置为 publicsecret, 该插件会创建一个 secret 的gist, 所以设置里面有key之类的, 也不是很要紧

GitHub Gist

Marketplace Settings Sync

GitHub shanalikhan/code-settings-sync

图床工具

vscode-qiniu-upload-image

可以自己写一个, 或者直接使用GitHub, 仓库内的图片是可以直接通过链接来访问的.

使用技巧
文件默认换行符设置

在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

Visual Studio Code

Yo Code - Extension Generator
依赖
  • npm
  • 配置环境变量, 可以直接使用npm命令
安装
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
第一个扩展插件

Example - Hello World

生成插件
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
package.json

示例

{
    "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 文件.
打包发布

Publishing Extensions

打包
安装
npm install -g vsce

直接在命令行使用 vsce 命令

vsce package

发布

vsce publish

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"

Indices and tables