admin 发布的文章

Golang进阶-file文件操作

1.file类是在os包中,封装了底层的文件描述符和相关信息,同时封装了Read和Write的实现;

2.文件信息通过一个接口来实现:FileInfo;如何操作这个接口的方法呢:os.Stat获取fileinfo的返回值

type FileInfo interface {
    Name() string       // base name of the file 文件名. 扩展名
    Size() int64        // 文件大小
    Mode() FileMode     // 文件权限
    ModTime() time.Time // 修改时间
    IsDir() bool        // 是否是文件
    Sys() interface{}   // 基础数据源接口(can return nil)
}

fileinfo, err := os.Stat("E:/GoPath/src/a/aa.txt")
if err != nil {
    fmt.Println("err: ", err)
}
fmt.Printf("%T\n",fileinfo)

//文件名
fmt.Println(fileinfo.Name())
//大小
fmt.Println(fileinfo.Size())
//是否是目录
fmt.Println(fileinfo.IsDir())
//修改时间
fmt.Println(fileinfo.ModTime())
//权限
fmt.Println(fileinfo.Mode())

3文件操作

  • 路径操作
fileName1 := "E:/GoPath/src/a/aa.txt"
//路径:是否是绝对路径
fmt.Println(filepath.IsAbs(fileName1))

//绝对路径
fmt.Println(filepath.Abs(fileName1))

//上级路径
fmt.Println(path.Join(fileName1,".."))
  • 创建目录
//Mkdir只能创建一层目录
err := os.Mkdir("E:/GoPath/src/b", os.ModePerm)
if err != nil {
    fmt.Println("err: ", err)
    return
}
fmt.Println("创建b目录成功")

//MkdirAll能创建多层
err := os.MkdirAll("E:/GoPath/src/a/cc/dd/ee", os.ModePerm)
if err != nil {
    fmt.Println("err: ", err)
    return
}
fmt.Println("多层目录创建成功")
  • 创建文件
//创建文件: create采用模式06666(任何人可读写,不可执行),已存在的文件会截断(内容清空),不存在则新建
file1, err := os.Create("E:/GoPath/src/a/ab.txt")
if err != nil {
    fmt.Println("err: ", err)
}
fmt.Println(file1)
  • 打开文件
//文件权限
const (
    // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
    // The remaining values may be or'ed in to control behavior.
    O_APPEND int = syscall.O_APPEND // append data to the file when writing.
    O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
    O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
    O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
    O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

//只读
file3, err := os.Open("E:/GoPath/src/a/ab.txt")
if err != nil {
    fmt.Println("err: ", err)
}
fmt.Println(file3)

//指定权限: (文件名称,文件的打开方式,文件的的权限:打开的文件如果不存在,so需要指定权限)
file4, err := os.OpenFile("E:/GoPath/src/a/ab.txt", os.O_RDONLY|os.O_WRONLY, os.ModePerm)
if err != nil {
    fmt.Println("err: ", err)
}
fmt.Println(file4)
  • 关闭文件:文件名称.Close()
  • 删除文件或文件夹
err := os.Remove("E:/GoPath/src/a/aa.txt")
    if err != nil {
        fmt.Println("err: ", err)
        return
    }

fmt.Println("删除文件成功")

//强制删除,所有
err := os.RemoveAll("E:/GoPath/src/a/cc/dd")
    if err != nil {
        fmt.Println("err: ", err)
        return
    }

fmt.Println("删除多层文件夹成功")

Golang进阶-time包

1:time相关转化

1秒=1000毫秒,millisecond
1毫秒=1000微妙,microsecond-->us
1微秒=1000纳秒,nanosecond-->ns
1纳秒=1000皮秒,picosecond-->ps

2:常用函数

//1获取现在的时间
    t1 := time.Now()
    fmt.Printf("%T\n", t1)
    fmt.Println(t1)

    //2获取指定的时间
    t2 := time.Date(2008,7,16,16,30,28,0,time.Local)
    fmt.Println(t2)

    //3格式化时间 time-->string之间的转换(里面的模板时间必须为 2006.1.2 15:04:05)
    s1 := t1.Format("2006年1月2日 15:04:05")
    fmt.Println(s1)
    fmt.Printf("%T\n", s1)

    s2 := t1.Format("2006-1-2")
    fmt.Println(s2)
    fmt.Printf("%T\n", s2)

    //string-->time日期类型(模板时间同上,且格式需相同于数据源)
    s3 := "1999年10月10日"//sting
    t3 ,err:= time.Parse("2006年1月2日", s3)
    if err != nil {
        fmt.Println("err", err)
    }
    fmt.Println(t3)
    fmt.Printf("%T\n", t3)

    fmt.Println(t1.String())

    //4.根据当前时间,获取指定的内容
    year,month,day := t1.Date()
    fmt.Println(year,month,day)

    hour,min,sec := t1.Clock()
    fmt.Println(hour,min,sec)


    //5.时间戳:指定的的日期,距离1970年1月1日0点0时0分0秒的时间差值
    t4 := time.Date(1970,1,1,1,0,0,0,time.UTC)
    timeStamp1 := t4.Unix()
    fmt.Println(timeStamp1)

    timeStamp2 := t1.Unix()
    fmt.Println(timeStamp2)

    timeStamp3 := t4.UnixNano()
    fmt.Println(timeStamp3)

    timeStamp4 := t1.UnixNano()
    fmt.Println(timeStamp4)


    //6.时间间隔
    t5 := t1.Add(time.Minute)
    fmt.Println(t1)
    fmt.Println(t5)
    fmt.Println(t1.Add(24*time.Hour))

    //差值
    t6 := t5.Sub(t1)
    fmt.Println(t6)

    //7.睡眠
    time.Sleep(3 * time.Second)
    fmt.Println("main...over...")

Golang进阶-init()初始化

1.init()、main()是go语言中的保留函数;init()函数用于初始化信息,main()函数用于程序执行的入口

2.相同点

  • 两个函数在定义时不能有任何的返回值和参数
  • 该函数只能由go程序自动调用,不可以被引用

3.不同点

  • init可以应用于任何包中,且可以重复定义多个
  • main函数只能用于main包中,且只能定义一个

4.两个函数的执行顺序

  • 同一个go文件中的init()调用顺序是从上到下
func init() {
    fmt.Println("第一个init()函数")
}
func init() {
    fmt.Println("第二个init()函数")
}
结果:
untils包下的until.go文件下第一个init()函数
untils包下的until.go文件下第二个init()函数
  • 同一个package包中的不同文件,将文件名按字符串进行 “从小到大”排序,之后顺序调用个文件中的init()函数
untils包下的test1.go文件中的init()函数。。。
untils包下的until.go文件下第一个init()函数
untils包下的until.go文件下第二个init()函数
  • 不同的package包,如果不互相依赖的话,按照import的顺序调用init()函数
import (
    "Go_Advanced/pk1"
    "Go_Advanced/utils"
)
结果:
pk1包下的init()函数
untils包下的test1.go文件中的init()函数。。。
untils包下的until.go文件下第一个init()函数
untils包下的until.go文件下第二个init()函数
untils包下的until.go文件下count()函数
pk1包下的MyTest1函数
  • 如果pakage存在依赖,调用顺序为最后被依赖的最先被初始化;例如导入顺序 main->A->B->C, 则初始化顺序为 C->B->A->main

Golang进阶-包的使用

1:包其实就是目录,不同的包相当于不同的目录;每个包下是具有相同属性的文件:类似MVC目录的作用

2:src目录是以代码包的形式组织并保存Go源文件的;每个项目的main包下的是入口main函数

3:定义包的规则和要求:package 包名

  • 一个目录下的所有文件归属一个包,package的声明要一致;可以不一致,但习惯上还是要写一致
  • 包可以嵌套
  • 同包下的函数不需要导入包,可以直接使用
  • main包,main函数所在的包,其它的包不能使用mian包名
  • 导入包的时候,路径要从src下开始写;可以写成绝对路径和相对路径

4:特殊的一些import方式

  • 点操作: 例如:import( . "fmt") 含义:导入这个包后,调用这个包的函数时,可以省略前缀的包名;fmt.Plintln("hello world") => Println("hello world")
  • 起别名:import( p1 "package1") p1.Method()
  • _ 操作:如果仅仅导入包时执行初始化操作,并不需要使用包内的其他函数,常量等资源;只是执行了这包内的init函数,不会调用其他函数
  • 对于import要导入的外部包,可以使用go get命令获取下来放到GOPATH对应的目录中
示例:mysql
1>go get github.com/go-sql-driver/mysql

2>链接
db, err := sql.Open("mysql", "admin:admin@tcp(127.0.0.1:3306)/test?charset=utf8")
if err != nil {
    fmt.Println("错误信息:", err)
    return
}
fmt.Println("链接成功:", db)

接口安全设计

常见的安全问题以及解决方案

1 接口被大规模调用消耗系统资源,影响系统的正常访问,甚至系统瘫痪

  • 解决方案: 获取 timestamp (时间戳), 设置接口失效时间
***验证time*** 

/**
* 验证请求是否超时
 * @param  [array] $arr [包含时间戳的参数数组]
 * @return [json]      [检测结果]
*/
public function check_time($arr) {
    if (!isset($arr['time']) || intval($arr['time']) <= 1) {
        $this->return_msg(400, '时间戳不正确!');
    }
    if (time() - intval($arr['time']) > 60) {
        $this->return_msg(400, '请求超时!');
    }
}

2 接口数据被黑客篡改(伪造请求)

  • 解决方案: 对参数加密, 生成 token , 判断 token 是否正确
***验证token***

/**
* 验证token(防止篡改数据)
 * @param  [array] $arr [全部请求参数]
 * @return [json]      [token验证结果]
*/
public function check_token($arr) {
 
    /*********** api传过来的token  ***********/
    if (!isset($arr['token']) || empty($arr['token'])) {
        $this->return_msg(400, 'token不能为空!');
    }
    $app_token = $arr['token']; // api传过来的token
 
    /*********** 服务器端生成token  ***********/
    unset($arr['token']);
    $service_token = '';
    foreach ($arr as $key => $value) {
        $service_token .= md5($value);
    }
    $service_token = md5('api_' . $service_token . '_api'); // 服务器端即时生成的token
 
    /*********** 对比token,返回结果  ***********/
    if ($app_token !== $service_token) {
        $this->return_msg(400, 'token值不正确!');
    }
}

3 数据被黑客截取

  • 解决方案: 使用 https , 用证书对数据进行加密, 即使数据被截取, 对黑客也没有意义

docker安装elasticsearch

1:ElasticSearch

  • 全文搜索引擎
  • 快速地储存,搜索和分析海量数据

2: es能做什么

  • 存储我们的数据
  • 不需要建表,配置字段
  • 存入json格式的文档
  • 原生支持语句,不需要写代码,不需要拼装查询语句

3:Docker

  • 容器引擎
  • 打包/发布应用程序,包括系统环境,配置,依赖
  • 虚拟化,沙箱机制更好

4: 安装docker

  • 官网下载
  • doceker version 查看版本
  • docker info 查看详细信息
Registry: https://index.docker.io/v1/
这个是拉镜象的地址
由于墙的原因,极有可能timeout
所以更换这个地址
daocloud:https://www.daocloud.io/mirror

5:安装nginx

  • docker run -d -p 80:80 nginx
  • docker ps 查看镜像运行状况
  • docker kill 加上镜像的ID
-d: 代表deman,后台一直保持进程
-p: 把容器里面的80端口映射到外面的物理机80端口

6:安装elasticsearch

  • docker run -d -p 9200:9200 elasticsearch
  • docker ps 查看以下
  • docker logs ID号 查看一下输出哪些日志

Golang错误机制初识

Go语言-defer语句

  • 位置:仅能在函数和方法中
  • 构成:关键字和一个表达式语句(表达式不能是内建函数和unsafe中的函数)
  • 作用:最后执行;保护措施;节约资源
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}
  • 注意:当有多个defer时,执行顺序为倒序
func deferIt() {
    defer func() {
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}

输出:4321
  • 特别提示1:有参数传入,比如:fmt.Print(i + 1)。如果代表传入参数的是一个表达式,那么在defer语句被执行的时候该表达式就会被求值了
func deferIt3() {
    f := func(i int) int {
        fmt.Printf("%d ",i)
        return i * 10
    }
    for i := 1; i < 5; i++ {
        defer fmt.Printf("%d ", f(i))
    }
}
打印出1 2 3 4 40 30 20 10
  • 特别提示2:defer携带的表达式语句代表的是对匿名函数的调用(注意外部变量传递)
func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}
打印出5555

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func(n int) {
            fmt.Print(n)
        }(i)
    }
}
打印出1234

Go语言异常处理——error

  • 处理错误的惯用法之一:Go函数可以一次返回多个结果(怎样在传递错误)
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}
函数readFile有两个结果声明。第二个结果声明的类型是error。error是Go语言内置的一个接口类型
type error interface { 
    Error() string
}
  • 怎样创造错误:调用标准库代码包errors的New函数即可
if path == "" {
    return nil, errors.New("The parameter is invalid!")
}

Go语言异常处理——panic

  • 可被意译为运行时恐慌,只有在程序运行的时候才会被“抛出来” == 程序崩溃
  • 恐慌是会被扩散的,它会被迅速地向调用栈的上层传递。如果我们不显式地处理它的话,程序的运行瞬间就会被终止。
  • 人为地产生一个运行时恐慌,可以被恢复的;内建函数recover
  • 内建函数panic和recover是天生的一对。前者用于产生运行时恐慌,而后者用于“恢复”它
  • recover函数必须要在defer语句中调用才有效,因为一旦有运行时恐慌发生,当前函数以及在调用栈上的所有代码都是失去对流程的控制权
  • 只有defer语句携带的函数中的代码才可能在运行时恐慌迅速向调用栈上层蔓延时“拦截到”它
defer func() {
    if p := recover(); p != nil {
        fmt.Printf("Fatal error: %s\n", p)
    }
}()
调用了recover函数。该函数会返回一个interface{}类型的值;interface{}代表空接口。Go语言中的任何类型都是它的实现类型。

yii2基础知识总结

#1:开启 Schema 缓存,会节省两次查询,生成缓存文件;可以通过debug查看

'enableSchemaCache' => true,
'schemaCacheDuration' => 3600,
'schemaCache' => 'cache',

2:DAO的4种查询方法(Database Access Objects);建立在 PHP PDO 之上的数据访问层 (DAO)

  • queryAll
  • queryOne
  • queryScalar
  • queryColumn

3:DAO的插入,更新,删除

  • 插入:Yii::$app->db->createCommand()->insert('表名',[数据])->execute;(batchInsert批量插入)
  • 更新:Yii::$app->db->createCommand()->('表名',[数据],[条件])->execute;
  • 删除:Yii::$app->db->createCommand()->('表名',[条件])->execute;

4:查询生成器(Query Builder);查询构建器建立在DAO上,前提是必须先实例化一个对象$query = new query;

  • 查看构造后的语句,$query->createCommand()->sql;$query ->createCommand()->getRawSql();
  • from:from('user u') || from(['u'=>'user']),别名
  • select:不能不填参数,但是可以为空数组;以下是select的小技巧
1> 按需查找字段
2> 别名:select(['ID'=>'id','名字'=>'username'])
3> 拼接字段:select(['卡号'=>"CONCAT(id,'-',username)" ]),可以用一些mysql的函数
4> 查找字段数量:select("count(*)")
5> 唯一性:select("username")->distinct

5:查询构造器的where方法

三种参数方式

  • 字符串形式:where("id=2"),不推荐这样;可以where("id=:id")->addParams([":id"=>2]),where("id=:id", [":id"=>2])绑定参数,预防sql注入,参数化
  • 数组形式:where(['name'=>"lzc", 'id'=>[2,6]]),里面的[],相当于in;
  • 复杂的数组形式:where(["<>","name","12"])
  • orderBy最好也用数组形式,清晰明了

6:查询构造器的limit,offset,groupBy,Having

  • limit(2),从0到1
  • offset(1),从1开始,偏移一个,从第几个开始
  • groupBy([]),分组;$query->select(['user_total'=>"count(*)"])->groupBy(['province'])
  • having(['>','count(*)',1])

7:new ActiveDataProvider

8:AR的rules;可以查速查表validate

save之前会去自动验证;
language=>‘zh’,转换成中文;
if($model->save() == false){
    $model->getErrors();
};
规则模版['字段',‘规则’,‘message’=>'']
massage提示语;
自定义rule: 
1>:['username', function($attr,$params){
    if($this->attr == 'abei'){
        return true;        
    } else {
        return->addError('username', '你不等于lv,所以不能通过')
    }
}];
2>:通过使用匿名函数来自定义rule;
    ['字段',方法名称(只要方法名称就行)]
3>:或者自定义一个方法,来调用验证
    ['字段', 验证类名::classname()]

9AR关联

  • hasOne
  • hasMany
  • get(魔术方法)+自定义名字:直接掉这个自定义名字即可
  • return $this->hasMany():是个query对象,因为可以用query的方法,后面接

Docker认知

1:安装docker

2:docker优势

  • 统一环境,标准化部署
  • 解决复杂的依赖问题:比如两个微服务的依赖相互冲突
  • 隔离应用的运行环境:比如redis获取服务器的权限的漏洞
  • 轻量级的虚拟环境,相比虚拟机而言开销速度快
  • 统一的服务管理:不同的服务有不同的管理工具和方式,例如不同的语言,不同的版本
  • dockerhub上有许多高价值的镜像可以直接使用

3:镜像和容器的介绍

  • 镜像:相当于光盘,光盘里面存储的数据是只读的,不会被更改;可以吧镜像看做一个模板,蓝图
  • 容器:容器是用镜像生成的docker实例,一个镜像可以生成多个容器,每个容器之间,容器与宿主之间都是相互隔离的;可以在功能上吧容器看做虚拟机

容器可以快速方便的运行,也可以方便的删除

4:手动启动容器

docker run -d -t -p 8000:5000 --name demo ubuntu:18.04

  • run: 表示启动一个新的docker容器
  • -d 容器在后台运行
  • -t 极少会用到,表示让一个空白的ununtu镜像在后台运行
  • -p 指定端口,表示本机访问的8000会被自动转到容器中的5000端口;前提是本机的8000端口没被占用
  • ubuntu:18.04 是启动容器使用的镜像
  • 每个容器生成后都有ID,并且每个容器的ID都是不一样的

5:启动容器的常见问题

端口占用

  • 占用3456端口: docker run -t -d -p 3456:5000 --name test ununtu:18.04
  • 运行一个使用3456端口的容器,会发现因为端口被占用而失败:docker run -t -d -p 3456:5000 --name test1 ununtu:18.04
  • 删除test容器来关闭对3456端口的占用 docker rm -f test
  • 再次运行容器发现任然失败,因为名字已经存在,test1容器虽然因为端口被占用而运行失败,但实际上容器已经生成了
  • 这时候要么改名字要么删除容器重来

docker run -t -d -p 3456:5000 --name test2
docker rm -f test1
docker run -t -d -p 3456:5000 --name test1

6:在容器中安装必备的软件

假如我们要运行一个 flask web程序;用 exec 参数在运行中的Docker 容器里面执行命令安装必要的依赖库

  • docker exec demo apt update
  • docker exec demo apt -y install python3 python3-pip
  • docker exec demo pip3 install flask

7:在demo容器中运行web程序

  • 当前目录下有个a.py文件
  • docker exec demo mkdir /code
  • docker cp a.py "demo:/code/a.py"
  • docker exec demo python3 /code/a.py
  • flask默认的启动端口是5000,之前我们已经做了8000::5000的端口转发

8:用脚本的方式配置容器

之前的一系列命令有些繁琐;更好的方式是在宿主机写脚本,然后cp 到容器中直接运行

  • 再用脚本方式运行程序之前,我们先清理刚才运行的程序
  • Ctrl c终止进程:只是到后台运行
  • docker stop 真正停止容器
写一个install.sh脚本用于安装软件:
# install.sh
apt update
apt -y install python3 python-pip
pip3 install flask
写一个run.sh脚本运行程序:
# run.sh
cd /code
cd /code
python3 a.py

9:运行脚本配置并开启新容器

我们可以先删除容器重新开始,也可以开一个新容器 demo1

  • docker run -t -d -p 7000:5000 --name demo1 ubuntu:18.04
  • docker exec demo1 mkdir /code
  • docker cp install.sh "demo1:/code/install.sh"
  • docker cp a.py "demo1:/code/a.py"
  • docker exec demo1 bash /code/install.sh
  • docker exec demo1 bash /code/run.sh
  • localhost:7000

10:容器的其他操作

  • 启动一个停止运行的容器:docker start demo
  • 查看正在运行的容器:docker ps
  • 停止容器:docker stop demo
  • 再查看正在运行的容器,已经看不到demo了:docker ps
  • 查看所有的容器,包括未运行的:docker ps -a
  • 删除被停止的容器:docker rm demo
  • 删除运行的容器:docker rm -f demo1

11:Dockerfile介绍

使用Dockerfile打包一个镜像并运行;优势解决手动执行命令;脚本的劣势,如果要启动多个容器,自制脚本每次都要重新安装配置一次

# 在Dockerfile 文件中#是注释
# FROM 用于指定构建镜像使用的基础镜像
FROM ununtu:18.04

# RUN 用于在构建镜像的时候在镜像中执行命令
RUN apt update
RUN apt -y install python3 python3-pip
RUN pip3 install flask

# COPY 相当于命令的 docker cp
# 把本机当前的目录下的app.py 文件拷贝到镜像中
# 不同的是可以自动创建不存在的目录

# WORKDIR 用于指定从镜像启动的容器内的工作目录
WORKDIR /code

# CMD 用于指定容器运行后要执行的命令和参数列表
CMD ["python3", "app.py"]

# ENTRYPOINT 参数用于指定容器运行后的入口程序,但是现在的这个参数意义已经很小了

12:使用Dockerfile构建镜像

本文件夹下有2个文件app.py和Dockerfile

  • docker build -t webimage .

命令中参数 -t webimage 指定了镜像的名字为webimage,这个名字可以用于在之后从镜像启动容器,最后的. 用于指定构建镜像时候的工作目录为本机当前目录

  • docker image 用于查看本机的镜像,包括下载的和构建的
  • docker run -p 8001:5000 --name demo2 webimage

13:服务器安装Docker

把安装的脚本和程序文件都放在本机的app 目录下,一共有如下3个文件: install-docker.sh=> 在ubuntu服务器安装docker的脚本;Dockerfile,上面的Dockerfile;app.py 上面的web程序; 我们可以用scp命令把app目录拷贝到服务器中,windows用户必须用cmder软件才有scp 这个命令;或者用 其他上传程序也可以,下面的用户名和IP换成你购买的服务器的用户名和IP

  • 拷贝:scp -r app your-ununtu@your-ip:/tmp
  • 登录:ssh your-ubuntu@your-ip
  • 安装docker:sh /tmp/app/install-docker.sh
  • 打包(提权):sudo docker build -t webimage .
  • 运行:sudo run -d -p 8000:5000 --name serverdemo webimage

14:数据卷介绍

我们可以在概念上把docker看做虚拟机,当容器被删除的时候,容器里的所有数据都会被删除,两个不同的容器之间无法互通,所有推出了数据卷 volume 功能
可以把数据卷理解为虚拟机的虚拟磁盘,独立于容器的文件,在容器中它被挂载为一个目录的形式,对于容器的应用来说,数据卷是透明的,无法感知它的存在,就是一个普通的文件夹,独立于容器存在,因此删除容器的时候数据卷不会受到影响

  • 优点:多容器可以通过挂载同一个数据卷来共享数据,数据卷可以方便地备份,存储数据

15:数据卷的使用

  • 创建一个volume:docker volume create testvolume
  • 列出所有数据卷:docker volume ls
  • 删除一个数据卷:docker volume rm testvolume
  • 查看一下:docker volume ls
  • 先创建一个volume:docker volume create web
  • 在运行容器的时候,使用参数 --mount:docker run -d --name demovolume --mount source=web,target=/volume webimage
  • --mount source=web,target=/volume,
    就是把数据卷挂载到容器的 /volume上

16:数据卷的特性演示

没有保存在数据卷上的文件会在容器被删除后丢失

  • 执行命令在容器的 /b.txt 写入时间内容并查看
docker exec demovolume sh -c 'date > /b.txt'
docker exec demovolume sh -c 'cat /b.txt'
  • 删除容器后重新启动同名容器再查看,之前容器的内容已经没有了
docker rm -f demovolume
docker run -d --name demovolume --mount source=web,target=/volume webimage
docker exec demovolume sh -c 'cat /b.txt'
  • 保存在数据卷上的内容即使容器被删除了仍然存在
docker exec demovolume sh -c 'date > /volume/b.txt'
docker rm -f demovolume
docker run -d --name demovolume --mount source=web,target=/volume webimage
docker exec demovolume sh -c 'cat /volume/b.txt'
  • 多容器之间可以通过数据卷共享文件
docker run -d --name demovolume2 --mount source=web,target=/v2 webimage 
docker exec demovolume2 sh -c 'cat /v2/b.txt'

17:共享目录

除了挂载数据卷外,docker 还可以挂载共享目录(这一点和虚拟机一样);优势:使用方便,易于理解
下面会从nginx 镜像运行一个nginx1的容器。并且设置了8080:80的端口映射;--mount参数的 type=bind 表明要挂载共享目录
把宿主机的当前目录映射为容器的 /usr/share/nginx/html;这样在宿主机中访问 localhost:8080 会自动访问宿主机当前目录下的 index.html 文件

  • docker run -p 8080:80 --name nginx1 --mount type=bind,source="${PWD}",target=/usr/share/nginx/html/ nginx
需要注意的是 source 参数必须使用绝对路径,所以这里使用 “${PWD}” 
的方式来在 Mac/Linux/Windows 中获取当前目录路径;这是一个可以在多平台通用的获取当前目录路径的方法(win下必须使用PowerShell),加引号是因为路径中可能含有空格等特殊符号,如果路径有空格而未加引号,会产生错误

18其他挂载模式

  • 单文件挂载,单文件挂载一定要保证宿主机文件存在,否则这个路径会被认为是一个目录挂载
docker run -p 8081:80 --name nginx2 --mount type=bind,source="${PWD}/index.html",target=/usr/share/nginx/html/test.html nginx
这时候我们访问 http://localhost:8081 返回的是 nginx 的默认首页,而访问 http://localhost:8081/test.html
  • 多文件挂载
docker run -p 80812:80 --name nginx3 --mount type=bind,source="${PWD}/index.html",target=/usr/share/nginx/html/test.html 
--mount type=bind,source="${PWD}/test.html",target=/usr/share/nginx/html/test2.html
nginx

19:compose的介绍

解决多容器互相配合,协同工作;虽然可以手动配置多容器之间的虚拟网络,文件互访等功能来实现容器互相访问,但docker官方推出了composer 程序用于配置,管理多容器的运行;Compose通过单独的docker-compose.yml配置文件来管理一组容器

20:安装Compose

在Docker for mac,Docker for Windows中 docker-compose 是自带的;只有在linux服务器上,才需要单独安装

sudo su
curl -L
https://github.com/docker/compose/releases/download/版本号/docker-compose
chmod +x /usr/local/bin/docker-compose

使用国内的地址来安装
sudo su
curl -L https://get.daocloud.io/docker/compose/releases/download/版本号/docker-compose
chmod +x /usr/local/bin/docker-compose

官方安装指南:
https://doc.docker.com/compose/install/

21:compose使用

compose 把一组容器作为一个项目来进行管理,并且会设置好容器之间的内部网络,每个容器在compose中被称为服务;composer使用docker-compose.yml文件来描述compose项目的构建;如同docker使用dockerfile来描述一个镜像的构建一样;

  • 命令启动:docker-compose up(Ctrl c 终止项目的运行)
  • 后台运行:docker-compose up -d
  • 暂停运行:docker-compose stop
  • 关闭并删除所有容器:docker-compose down

22:加速换源安装

23:可选的镜像

基于Alpine linux的alpine镜像,alpine已经是官方推荐的打包基础镜像

  • 使用专有镜像,而不是在Ubuntu镜像上安装软件
  • 使用基于alpine打包的镜像

24:镜像的分层构建和缓存

docker会对dockerfile中执行的结果生成缓存,再次构建镜像时,如果没有改动会复用缓存结果,因此指令的顺序很重要,前面的指令有改变,后面会全部重新build,所以应该把最不易更改的指令放在前面

25:compose使用技巧

  • 端口绑定;8000:3000形式实际上是 0.0.0.0:8000:3000;表示任意机器都能访问本机的8000端口;很多时候我们不能希望暴露服务端口,希望只有本机能访问,这时候我们应该写成:127.0.0.1:8000:3000
  • 查看日志:在项目中,使用 docker-compose logs 可以查看一个compose项目的日志;使用 docker-compose logs pyweb 的形式可以只查看某个特定服务的
  • compose的多文件和覆盖配置:当我们使用docker-compose up启动compose项目的时候,实际上相当于 docker-compose -f docker-compose.yml up
我们可以用多个不同的compose配置文件来实现不同的启动方式;可以用init.yml
用于初始化,可以用debug.yml用于启动调试模式
docker-compose -f docker-compose.yml -f debug.yml up

26:开发部署

项目的开发和部署需要不同的设置,我们通常会使用不同的环境变量来配置环境

27:docker 其它用法

  • 用docker来发布C C++或者其它平台相关的软件,可以让mac win用户都可以使用Linux软件
  • 同时使用多个软件的不同版本
  • 用于快速,方便安装一些服务,例如gogs:
docker run -p 3000:3000 gogs/gogs:latest

实战docker持续集成和部署

Active Record轻松学习

1:AR是一个类

  • 继承 yiidbActiveRecord
  • 自动生成后至少会有三个方法tableName, rules,
    attributeLabels
  • 数据库的操作小助手(操作的主要是属性和方法)

2:连接数据库

  • ActiveRecord中有个方法:getDb

3:查询数据

例子:User::find()->where([])->one();find()方法发生了什么,batch&each大数据查询优化

  • var_dump(User::find()):【是个AR对象】返回的是yiidbActiveQuery这个对象;User::find()等价于ActiveQuery一个对象;ActiveQuery继承于yiidbQuery,所以能用where等方法
  • 大数据优化查询,one方法最好也填上个limit方法

4:对数据的访问

  • 在model中什么是属性:业务数据
  • 获取所有属性:attributes获取所有字段名称及其值&attributes()只是获取字段名称
  • 获取旧的数据getOldAttributes():第一次读时的数据
  • 属性标签:attributeLabels是,getAttributeLabel,generateAttributeLabel

5:场景的意义:scenario方法

  • 在rules中 on设置场景,safe代表安全
  • rules规则
  • 块赋值,rules里的数据都可以:

$model->attributes = [
'username' => 123,
'password' => 234,
]

6:保存数据那些事

  • save如何做到insert&update两个操作:$this->getIsNewRecord()变量来区分
  • save操作如何跳过数据验证:save(false)
  • 块赋值问题
  • 更新计数问题updateCounts

7:脏属性和加载默认值

  • 获取脏属性:$model->getDirtyAttributes()
  • 加载默认值:$model->loadDefaultValues()

8:批量更新和批量删除

  • Goods::updateAll(['key'=>'val'],[]);
  • Goods::updateAllCounters()更新计数
  • Goods::deleteAll()

9:ar对象的生命周期

  • 只针对AR对象 new

10:延迟加载和即时加载

  • 延迟加载:第一次查询完会缓存起来
  • 即时加载:
$orders = Order::find()->all();
foreach($orders as $order){
    $og = $order->orderGoods;(关联)循环里面会有很多查询,查询次数很多
}
解决方案:
$orders = Order::find()->with('orderGoods')->all();
foreach($orders as $order){
    $og = $order->orderGoods;(关联)
}
这样只会有两条查询,第二条会是in()这种方式来解决

11:关于JOIN查询

  • city表和user表关联
第一种:
$data = City::find()
->leftJoin("user",'city'.id = 'user'.city_id)
->with('user'):即时加载
->asArray()
->all()

第二种:
$data = City::find()
->joinWith('user')
->asArray()
->all()

12:反向关联,用的较少

  • 减少sql查询的冗余
  • inverseOf

13:关联中的link和unlink方法

14:额外字段

  • set和get

15:关联

  • 获取属性的关联:$model->admins,获得的是一个AR对象
  • 通过方法的形式调用关联:$model->getadmins(),获得是ActiveQuery,AQ是个查询集合,里面有很多查询构造器的方法;这就是为什么一个$model::find()后,可以用很多查询方法
  • 用属性查,除第一次,后面的都是差的缓存;用方法的话每次都得查询,并且对服务器的压力大一些