0%

介绍

缓存是将一些常用的数据保存在内存或者memcache中,在设置好的时间内,如果有请求访问这些数据,则不会再去操作数据库、执行逻辑、渲染等操作,而是直接从内存或者memcache的缓存中取取出数据,返回给用户

在动态网站中,用户向服务端发起请求,服务器都会去数据库中进行相应的增删改查 —> 执行逻辑 —> 渲染模板 —> 返回响应 —> 最后生成用户所看到的页面

出现的问题:

  • 当用户访问量很庞大时,每一次都去操作一遍数据库,会消耗非常多的服务器资源

解决方案:

  • 使用缓存来减轻后端服务器的压力

缓存方式

  • 开发调试缓存
  • 内存缓存
  • 文件缓存
  • 数据库缓存
  • Memcache缓存(使用python-memcached模块/使用pylibmc模块)

常用:文件缓存、Memcache缓存

缓存配置

开发调试(此模式为开发调试使用,实际上不执行任何操作)

1
2
3
4
5
6
7
8
9
10
11
12
# settings.py

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 缓存后台使用的引擎
'TIMEOUT': 300, # 缓存超时时间(默认300秒,None表示永不过期,0表示立即过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
},
}
}

内存缓存(将缓存内容保存至内存区域中)

1
2
3
4
5
6
7
8
9
10
11
12
13
# settings.py

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 指定缓存使用的引擎
'LOCATION': 'unique-snowflake', # 写在内存中的变量的唯一值
'TIMEOUT':300, # 缓存超时时间(默认为300秒,None表示永不过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}

文件缓存(把缓存数据存储在文件中)

1
2
3
4
5
6
7
8
9
10
11
12
13
# settings.py

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', #指定缓存使用的引擎
'LOCATION': '/var/tmp/django_cache', #指定缓存的路径
'TIMEOUT':300, #缓存超时时间(默认为300秒,None表示永不过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}

数据库缓存(把缓存数据存储在数据库中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# settings.py

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache', # 指定缓存使用的引擎
'LOCATION': 'cache_table', # 数据库表
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}

'''
注意:
python manage.py createcachetable
(创建缓存的数据库表使用的语句)

'''

Memcache缓存(使用python-memcached模块连接memcache)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Memcached是Django原生支持的缓存系统,要使用Memcached,需要下载Memcached的支持库python-memcached或者pylibmc

# settings.py

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', # 指定缓存使用的引擎
'LOCATION': '192.168.10.100:11211', # 指定Memcache缓存服务器的IP地址和端口
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}

# LOCATION也可以配置成如下:

'LOCATION': 'unix:/tmp/memcached.sock', # 指定局域网内的主机名加socket套接字为Memcache缓存服务器
'LOCATION': [ # 指定一台或多台其他主机ip地址加端口为Memcache缓存服务器
'192.168.10.100:11211',
'192.168.10.101:11211',
'192.168.10.102:11211',
]

Memcache缓存(使用pylibmc模块连接memcache)

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
# settings.py文件配置

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', # 指定缓存使用的引擎
'LOCATION':'192.168.10.100:11211', # 指定本机的11211端口为Memcache缓存服务器
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
},
}
}

# LOCATION也可以配置成如下:

'LOCATION': '/tmp/memcached.sock', # 指定某个路径为缓存目录
'LOCATION': [ # 分布式缓存,在多台服务器上运行Memcached进程,程序会把多台服务器当作一个单独的缓存,而不会在每台服务器上复制缓存值
'192.168.10.100:11211',
'192.168.10.101:11211',
'192.168.10.102:11211',
]

'''
注意:
Memcache是基于内存的缓存,数据存储在内存中,所以如果服务器宕机的话,数据就会丢失,所以Memcache一般与其他缓存配合使用
'''

缓存应用

Django提供了不同力度的缓存,可以只缓存一个页面的某个部分,也可以缓存整个页面,甚至可以缓存整个网站

视图函数使用缓存

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
# views.py

from django.views.decorators.cache import cache_page
import time
from .models import *

@cache_page(15) #超时时间为15秒
def index(request):
  t=time.time() #获取当前时间
  bookList=Book.objects.all()
  return render(request,"index.html",locals())


# index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>当前时间:-----{{ t }}</h3>

<ul>
{% for book in bookList %}
<li>{{ book.name }}--------->{{ book.price }}$</li>
{% endfor %}
</ul>

</body>
</html>

# settings.py

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', # 指定缓存使用的引擎
'LOCATION': 'E:\django_cache', # 指定缓存的路径
'TIMEOUT': 300, # 缓存超时时间(默认为300秒,None表示永不过期)
'OPTIONS': {
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}

全站使用缓存

用户的请求通过中间件,经过一系列的认证的操作

如果请求的内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户

如果请求的内容在缓存中不存在,则使用UpdateCacheMiddleware将缓存保存至Django的缓存中,并返回,实现全站缓存

缓存整个站点,是最简单的缓存方式(用的少)

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
# 在 MIDDLEWARE_CLASSES 中加入 “update” 和 “fetch” 中间件

MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware', # 第一个
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware', # 最后一个
)

'''
“update” 必须配置在第一个
“fetch” 必须配置在最后一个
'''

# settings.py

MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware', #响应HttpResponse中设置几个headers
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware', #用来缓存通过GET和HEAD方法获取的状态码为200的响应

)


CACHE_MIDDLEWARE_SECONDS=10

# views.py

from django.views.decorators.cache import cache_page
import time
from .models import *


def index(request):

t=time.time() #获取当前时间
bookList=Book.objects.all()
return render(request,"index.html",locals())

def foo(request):
t=time.time() #获取当前时间
return HttpResponse("HELLO:"+str(t))

# index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3 style="color: green">当前时间:-----{{ t }}</h3>

<ul>
{% for book in bookList %}
<li>{{ book.name }}--------->{{ book.price }}$</li>
{% endfor %}
</ul>

</body>
</html>

局部视图缓存

刷新页面时,网页的一部分实现缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# views.py

from django.views.decorators.cache import cache_page
import time
from .models import *
def index(request):
t=time.time() #获取当前时间
bookList=Book.objects.all()
return render(request,"index.html",locals())

# index.html

{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3 style="color: green">不缓存:-----{{ t }}</h3>

{% cache 2 'name' %}
<h3>缓存:-----:{{ t }}</h3>
{% endcache %}

</body>
</html>

Docker学习

  • Docker概述
  • Docker安装
  • Docker命令
    • 镜像命令
    • 容器命令
    • 操作命令
  • Docker镜像
  • 容器的数据卷
  • DockerFIle
  • Docker网络原理
  • IDEA整合Docker
  • Docker Compose
  • Docker Swarm
  • CI\CD jenkins

Docker概述

Docker为什么会出现?

一款产品:开发-上线 两套环境,应用配置!

开发 — 运维 问题: 项目在我的电脑上可以运行!版本更新,导致服务不可用!对于运维来说,考验就十分大

环境配置是十分的麻烦.每一个机器都要部署环境(集群Redis, ES, Hadoop…)! 费时费力

发布一个项目(Jar+ (Redis,ES, Hadoop)),项目为什么不带上环境安装打包

在服务器上配置十分麻烦,不能够跨平台

Windows开发,发布到Linux

传统开发: 开发人员做项目,运维人员做部署

现在: 开发打包部署上线,一套流程做完

java – apk – 发布(应用商店) –使用者使用apk– 安装即用

java — jar(环境) — 打包上线自带环境(镜像) —(Docker仓库: 商店) — 下载我们发布的镜像 — 直接运行即可

Docker是基于Go语言开发的开源项目!

官网: https://www.docker.com/

在这里插入图片描述

文档地址: https://docs.docker.com/get-docker/

仓库地址: https://hub.docker.com/

Docker能干嘛?

之前的虚拟机技术

在这里插入图片描述

虚拟机技术缺点:

  • 资源占用十分多

  • 冗余技术多

  • 启动很慢

容器化技术

容器化技术不是一个完整的操作系统

在这里插入图片描述

比较Docker和虚拟机技术的不同

  • 传统的虚拟机,虚拟出一条硬件,运行一个完整的操作系统,然后在这个系统上安装和运行软件
  • 容器内的应用直接运行在宿主机的内容,容器是没有自己的内核的,也没有虚拟我们的硬件,所以就轻便了
  • 每个容器间都是互相隔离的,每一个容器内都有一个属于自己的文件系统,互不影响

DevOps(开发,运维)

更快速的交付和部署

传统: 一堆帮助文档,安装程序

Docker: 一键运行打包镜像发布测试

更便捷的升级和扩缩容

使用了Docker之后,我们部署应用就像搭积木一样!

项目打包为一个镜像,扩展,服务器A!服务器B

更简单的系统运维

在容器化之后,我们开发,测试环境都是高度一致的

更高效的计算资源利用:

Docker是内核级别的虚拟化, 可以在一个物理机上运行很多的容器实例! 服务器的性能可以被压榨到极致

Docker安装

Docker的基本组成

在这里插入图片描述

镜像(image):

docker镜像好比一个模板,可以通过这个模板来创建容器服务,tomcat镜像===>run ==>tomcat01容器(提供服务器)

通过这个镜像可以创建多个容器(最终服务运行或者项目运行就是在容器中的)

容器(container):

Docker利用容器技术,独立运行一个或者一个组应用,通过镜像来创建的

启动,停止,删除,基本命令!

仓库(repository):

仓库就是存放镜像的地方!

仓库分为共有仓库和私有仓库

Docker Hub(默认是国外的)

阿里云… 都有容器服务器(配置镜像加速)

安装Docker

环境准备

  1. 需要会一点点Linux基础
  2. CentOS7
  3. 使用Xshell连接远程服务器

环境查看

1
2
3
 # 查看系统内核
[root@iZ2zeheduaqlfxyl598si8Z /]# uname -r
3.10.0-1062.18.1.el7.x86_64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 系统版本
[root@CZP ~]# 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"

安装Docker

查看帮助文档

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
# 一, 卸载旧的版本
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

# 2, 需要安装的包
yum install -y yum-utils

#3. 设置镜像仓库
yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo # 默认是国外的

yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 推荐使用
# 更新yum软件包索引
yum makecache fast
# 安装docker
sudo yum install docker-ce docker-ce-cli containerd.io
# 启动docker
systemctl start docker
# 查看docker版本
docker version

下载镜像

docker pull [要下载的软件]

查看下载的镜像

docker images

卸载docker

1
2
3
yum remove docker-ce docker-ce-cli containerd.io

rm -rf /var/lib/docker#docker默认工作路径

阿里云配置Docker镜像加速

在这里插入图片描述

执行命令

1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["阿里云镜像加速地址"]
}
EOF

sudo systemctl daemon-reload

sudo systemctl restart docker

在这里插入图片描述

底层原理

Docker是怎么工作的?

Docker是一个C/S结构的系统,Docker的守护进程运行在主机上.通过Socket从客户端访问!

DockerServer接收到Docker-Client的指令,就会执行这个命令!

在这里插入图片描述

Docker为什么比VM快?

  1. Docker有着比虚拟机更少的抽象层

  2. Docker利用的是宿主机的内核,vm需要的是Guest OS

    img

    所以说,新建一个容器的时候,docker不需要像虚拟机一样重新安装一个操作系统内核,虚拟机是加载Guest OS,分钟级别的,而docker是利用宿主机的操作系统,省略了这个复杂的过程

Docker的常用命令

帮助命令

1
2
3
docker version 			# docker版本 
docker info # 显示docker的系统信息,包括镜像和容器的数量
docker [命令] --help # 查看某个具体的命令

镜像命令

查看下载的所有镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.6 8de95e6026c3 20 hours ago 302MB
redis latest 36304d3b4540 12 days ago 104MB
mysql latest 30f937e841c8 2 weeks ago 541MB
centos/mysql-57-centos7 latest f83a2938370c 8 months ago 452MB
# 解释
REPOSITORY 镜像的仓库名
TAG 镜像的标签
IMAGE ID 镜像ID
CREATED 镜像创建时间
SIZE 镜像的大小

# 可选项
Options:
-a, --all # 列出所有镜像
-q, --quiet # 只显示镜像ID

搜索镜像

1
2
3
4
5
6
7
8
# docker search 

[root@CZP ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 9604 [OK]

# 可选项,通过收藏来过滤
--filter=stars=3000 # 搜索出来的镜像收藏就是大于3000的

下载镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# docker pull 

[root@CZP ~] # docker pull nginx [:tag]
Using default tag: latest # 如果不写tag 默认使用最新版本
latest: Pulling from library/nginx
8559a31e96f4: Pull complete # 分层下载,docker image核心 联合文件系统
8d69e59170f7: Pull complete
3f9f1ec1d262: Pull complete
d1f5ff4f210d: Pull complete
1e22bfa8652e: Pull complete
Digest: sha256:21f32f6c08406306d822a0e6e8b7dc81f53f336570e852e25fbe1e3e3d0d0133 # 签名
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest # 真实地址

# docker pull nginx 等价于 dicker pull docker.io/library/nginx:latest


#指定版本下载

删除镜像

1
2
3
4
5
6
# docker rmi 

# 删除指定的容器
[root@CZP ~]# docker rm -f 8de95e6026c3
# 删除全部的容器
[root@CZP ~]# docker rm -f $(docker -ap)

容器命令

说明 : 有了镜像才可以创建容器,

1
docker pull [image]

新建容器并启动

1
2
3
4
5
6
7
8
# docker run  [可选参数] image

# 参数说明
--name="" 容器名字 用于区分容器
-d 后台方式运行
-it 使用交互方式运行,进入容器查看内容
-p 指定容器的端口 -p 80:8080 主机端口:容器端口
-P(大写) 随机指定容器的端口

列出所有运行的容器

1
2
3
4
5
6
7
8
9
10
11
12
# docker ps 命令	(不加参数)列出当前正在运行的容器

# 参数说明
-a # 列出当前正在运行的容器+历史运行过的容器
-n=? # 显示最近创建的容器
-q # 只显示容器的编号

[root@CZP ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@CZP ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
919e58ff5521 redis "docker-entrypoint.s…" 20 hours ago Exited (0) 16 hours ago redis

退出容器

1
2
exit 		 # 直接容器停止并退出
ctrl + p + q # 直接退出容器

删除容器

1
2
3
docker rm 容器id						# 删除指定容器(可一次删除多个,各id之间用空格隔开)
docker rm -f[递归] $(docker ps -aq) # 递归删除所有的容器
docker ps -a | xargs docker rm # 递归删除所有的容器

启动和停止容器的操作

1
2
3
4
docker start 容器id  		# 启动容器
docker restart 容器id # 重启容器
docker stop 容器id # 停止当前正在运行的容器
docker kill 容器id # 强制停止当前容器

常用的其他命令

后台启动容器

1
2
3
4
# 命令docker run -d 镜像名

# 常见的坑: docker容器后台运行,就必须要有一个前台进程,docker发现没有应用,就会自动停止
#nginx, 容器启动后,发现自己没有提供服务,就会立刻停止,就是没有程序了

查看日志

1
2
3
4
5
6
7
8
9
10
11
12
docker logs -f -t --tail 容器

# 参数说明
-tf # 显示日志
--tail number # 要显示的日志条数

[root@localhost ~]# docker logs -tf --tail 10 centosv1
2021-06-26T01:38:12.739000465Z [root@ef24fc0af36a /]# docker ps
2021-06-26T01:38:12.739180686Z bash: docker: command not found
2021-06-26T01:38:21.504914487Z [root@ef24fc0af36a /]# exit
2021-06-26T01:38:21.504931415Z exit

查看容器中进程信息

1
2
3
4
5
docker top 容器id 

[root@CZP ~]# docker top 63d4c4115212
UID PID PPID C STIME
polkitd 2319 2301 0 12:33

查看镜像元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# 命令
docker inspect 容器id


# 测试
[root@CZP ~]# docker inspect 63d4c4115212
[
{
"Id": "63d4c41152126cae276b69e1100520f9d6d867f950e488b5488de68181b7870b",
"Created": "2020-06-10T04:33:13.666827714Z",
"Path": "docker-entrypoint.sh",
"Args": [
"redis-server"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 2319,
"ExitCode": 0,
"Error": "",
"StartedAt": "2020-06-10T04:33:14.008260846Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:36304d3b4540c5143673b2cefaba583a0426b57c709b5a35363f96a3510058cd",
"ResolvConfPath": "/var/lib/docker/containers/63d4c41152126cae276b69e1100520f9d6d867f950e488b5488de68181b7870b/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/63d4c41152126cae276b69e1100520f9d6d867f950e488b5488de68181b7870b/hostname",
"HostsPath": "/var/lib/docker/containers/63d4c41152126cae276b69e1100520f9d6d867f950e488b5488de68181b7870b/hosts",
"LogPath": "/var/lib/docker/containers/63d4c41152126cae276b69e1100520f9d6d867f950e488b5488de68181b7870b/63d4c41152126cae276b69e1100520f9d6d867f950e488b5488de68181b7870b-json.log",
"Name": "/redis",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {
"6379/tcp": [
{
"HostIp": "",
"HostPort": "6379"
}
]
},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"Capabilities": null,
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/db63749d3abd5e587a88360e27fc9b5b0db7069b45e2bd8c48c75e25eba89100-init/diff:/var/lib/docker/overlay2/30d298e0d46edd68a8cbb588247384b9516d1140f5ca592b7f0b1c04618111f0/diff:/var/lib/docker/overlay2/af6963cd652870740eec10549b2a6c8f08b94edb3a3cea1a42d727026bb6d5a0/diff:/var/lib/docker/overlay2/af514c78199cdcfb30194921b892782dacbe1a3f439167f2f434b2f5a55ab5c3/diff:/var/lib/docker/overlay2/b1e020f1a03a66483794af0cf20d59e8dfa471f692ecaa145398d30500370a2a/diff:/var/lib/docker/overlay2/e55685d1f4ea504c3df5c3cbe822ab000f6412d3eff50b6b8ba097fb551ad922/diff:/var/lib/docker/overlay2/df78a0d3f0dbc449650ce5605d67ff45e93dbadc4fe93d2b7552203a31ab46a8/diff",
"MergedDir": "/var/lib/docker/overlay2/db63749d3abd5e587a88360e27fc9b5b0db7069b45e2bd8c48c75e25eba89100/merged",
"UpperDir": "/var/lib/docker/overlay2/db63749d3abd5e587a88360e27fc9b5b0db7069b45e2bd8c48c75e25eba89100/diff",
"WorkDir": "/var/lib/docker/overlay2/db63749d3abd5e587a88360e27fc9b5b0db7069b45e2bd8c48c75e25eba89100/work"
},
"Name": "overlay2"
},
"Mounts": [
{
"Type": "volume",
"Name": "f544b94e9e0129e7f438b851edbfc94a82c96e6fdda44179d38e20f800878a6a",
"Source": "/var/lib/docker/volumes/f544b94e9e0129e7f438b851edbfc94a82c96e6fdda44179d38e20f800878a6a/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Config": {
"Hostname": "63d4c4115212",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"6379/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"REDIS_VERSION=6.0.4",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.4.tar.gz",
"REDIS_DOWNLOAD_SHA=3337005a1e0c3aa293c87c313467ea8ac11984921fab08807998ba765c9943de"
],
"Cmd": [
"redis-server"
],
"Image": "36304d3b4540",
"Volumes": {
"/data": {}
},
"WorkingDir": "/data",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "5f8321169dfd067e45f2921d73984d0d76e953cd642d93990d1ed8a3227ad53f",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"6379/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "6379"
}
]
},
"SandboxKey": "/var/run/docker/netns/5f8321169dfd",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "33eafd9f86d5e7a3b29ed09fe75d8eb27e0227a07607960550dfef7f620a6872",
"Gateway": "172.18.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:12:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "228826a97a0b066a01811c098e4c4985804598571b9c8d9ad4205a0ee75c8b5b",
"EndpointID": "33eafd9f86d5e7a3b29ed09fe75d8eb27e0227a07607960550dfef7f620a6872",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:02",
"DriverOpts": null
}
}
}
}

进入当前正在运行的容器

1
2
3
4
5
6
7
8
# 我们通常容器都是使用后台方式运行的,需要进入容器,修改一些配置

# 命令
docker exec -it 容器id bashshell 默认命令行

docker attach 容器id
# docker exec # 进入容器后开启一个新的终端,可以在里面操作(常用)
# docker attach # 进入容器正在执行的终端,不会启动新的进程

从容器内拷贝文件到主机上

1
docker cp 容器id:容器内路径 目的主机路径

在这里插入图片描述

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
attach      Attach  to a running container 											# 进入容器内部,不启动新的进程
build Build an image from a Dockerfile # 通过dockerfile 定制镜像
commit Create a new image from a container's changes # 提交当前容器为新的镜像
cp Copy files/folders between a container and the local filesystem # 从容器中拷贝指定的文件或者目录到宿主机上
create Create a new container # 创建一个新的容器,同run 但不启动容器
diff Inspect changeson a container's filesystem # 查看docker容器变化
events Get real time events from the server # 从docker服务获取容器实时事件
exec Run a command in a running container # 在已存在的容器上运行命令
export Export a container's filesystem as a tar archive # 导出容器的内容流作为一个tar归档文件[对应import]
history Show the history of an image # 展示一个镜像形成历史
images List images # 列出系统当前镜像
import Import the contents from a tarball to create a filesystem image # 从tar包中的内容创建一个新的文件系统映像[对应export]
info Display system-wide information # 显示系统相关信息
inspect Return low-level information on Docker objects # 查看容器详细信息
kill Kill one or more running containers # kill指定docker容器
load Load an image from a tar archive or STDIN # 从一个tar包加载一个镜像[对应save]
login Log in to a Docker registry # 注册或者登录一个docker源服务器
logout Log out from a Docker registry # 从当前docker registry退出
logs Fetch the logs of a container # 输出当前容器日志信息
pause Pause all processes within one or more containers # 暂停容器
port List port mappings or a specific mapping for the container # 查看映射端口对应的容器内部源端口
ps List containers # 列出容器列表
pull Pull an image or a repository from a registry # 从docker镜像源服务器拉取指定镜像或库镜像
push Push an image or a repository to a registry # 推送指定镜像或库镜像至docker源服务器
rename Rename a container # 重命名容器
restart Restart one or more containers # 重启运行的容器
rm Remove one or more containers # 移除一个或多个容器
rmi Remove one or more images # 移除一个或多个镜像(没有容器使用当前镜像才可删除,否则需要删除相关容器或者使用-f参数强制删除)
run Run a command in a new container # 创建一个新的容器并运行一个命令
save Save one or more images to a tar archive (streamed to STDOUT by default) # 保存一个镜像为一个tar包[对应load]
search Search the Docker Hub for images # 在docker hub中搜索镜像
start Start one or more stopped containers # 启动容器
stats Display a live stream of container(s) resource usage statistics # 查看cpu的状态
stop Stop one or more running containers # 停止容器
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE # 给源中镜像打标签
top Display the running processes of a container # 查看容器中运行的进程信息
unpause Unpause all processes within one or more containers # 取消暂停容器
update Update configuration of one or more containers # 更新一个或多个容器的配置
version Show the Docker version information # 查看docker版本号
wait Block until one or more containers stop, then print their exit codes # 截取容器停止时的退出状态值

Docker 安装Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
#1. 搜索镜像 search 建议大家去docker搜索,可以看到帮助文档
#2. 拉取镜像 pull
#3、运行测试
# -d 后台运行
# --name 给容器命名
# -p 宿主机端口:容器内部端口
➜ ~ docker run -d --name nginx00 -p 82:80 nginx
75943663c116f5ed006a0042c42f78e9a1a6a52eba66311666eee12e1c8a4502
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
75943663c116 nginx "nginx -g 'daemon of…" 41 seconds ago Up 40 seconds 0.0.0.0:82->80/tcp nginx00
➜ ~ curl localhost:82 #测试
<!DOCTYPE html>,,,,

思考问题:我们每次改动nginx配置文件,都需要进入容器内部?十分的麻烦,要是可以在容器外部提供一个映射路径,达到在容器修改文件名,容器内部就可以自动修改数据卷!

img

作业:docker 来装一个tomcat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 官方的使用
docker run -it --rm tomcat:9.0
# 之前的启动都是后台,停止了容器,容器还是可以查到, docker run -it --rm image 一般是用来测试,用完就删除
--rm Automatically remove the container when it exits
#下载
docker pull tomcat
#启动运行
docker run -d -p 8080:8080 --name tomcat01 tomcat
#测试访问有没有问题
curl localhost:8080

#进入容器
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
db09851cf82e tomcat "catalina.sh run" 28 seconds ago Up 27 seconds 0.0.0.0:8080->8080/tcp tomcat01
➜ ~ docker exec -it db09851cf82e /bin/bash
root@db09851cf82e:/usr/local/tomcat#
# 发现问题:1、linux命令少了。 2.没有webapps

思考问题:我们以后要部署项目,如果每次都要进入容器是不是十分麻烦?要是可以在容器外部提供一个映射路径,webapps,我们在外部放置项目,就自动同步内部就好了!

作业:部署es+kibana

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
# es 暴露的端口很多!
# es 的数据一般需要放置到安全目录!挂载
# --net somenetwork ? 网络配置

# 启动elasticsearch
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2
# 测试一下es是否成功启动
➜ ~ curl localhost:9200
{
"name" : "d73ad2f22dd3",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "atFKgANxS8CzgIyCB8PGxA",
"version" : {
"number" : "7.6.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
"build_date" : "2020-03-26T06:34:37.794943Z",
"build_snapshot" : false,
"lucene_version" : "8.4.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
➜ ~ docker stats # 查看docker容器使用内存情况

CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
bd4094db247f elasticsearch 1.57% 1.226GiB / 3.7GiB 33.13% 0B / 0B 0B / 0B 42
94b00b6f6172 tomcat 0.18% 78.58MiB / 3.7GiB 2.07% 1.69kB / 2.47kB 0B / 0B 37
d458bc50a808 nginx01 0.00% 1.883MiB / 3.7GiB 0.05% 5.22kB / 6.32kB 0B / 0B 3
63d4c4115212 redis 0.14% 9.637MiB / 3.7GiB 0.25% 10.8kB / 14.2kB 0B / 0B 7
1
2
3
# elasticsearch十分占用内存,需要修改配置文件 -e 限制其启动的内存
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e ES_JAVA_OPTS=
"-Xms64m -Xmx 512m" elasticsearch:7.6.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@CZP ~]# curl localhost:9200
{
"name" : "bd4094db247f",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "U3TfPp1rQ6uitn0WMh6pRQ",
"version" : {
"number" : "7.6.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
"build_date" : "2020-03-26T06:34:37.794943Z",
"build_snapshot" : false,
"lucene_version" : "8.4.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}

docker和kibana如何连接

在这里插入图片描述

可视化

  • portainer(先用这个)
1
2
docker run -d -p 8080:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
  • Rancher(CI/CD再用)

什么是portainer?

Docker图形化界面管理工具! 提供一个后台面板供我们操作

1
2
docker run -d -p 8080:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer

访问测试: 外网: 8088 http://外网ip:8088/

在这里插入图片描述

进入之后的面板

在这里插入图片描述

Docker镜像讲解

镜像是什么

镜像就是一个轻量级的,可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码,运行时,库,环境变量和配置文件。

所有的应用,直接打包docker镜像,就可以直接跑起来!

如何得到镜像:

  • 从远程仓库下载
  • 朋友拷贝给你
  • 自己制作一个镜像 DockerFile

Docker镜像加载原理

UnionFs(联合文件系统查询)

我们下载的时候看到的一层一层就是这个

UnionFs(联合文件系统): Union文件系统(UnionFS)是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,Union文件系统是Docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像

特性: 一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录结构

Docker镜像加载原理

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS

bootfs(boot file system)主要包含bootlloader和kernel,bootfs主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,在docker镜像的最底层是bootfs,这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核,当boot加载完成之后整个内核就在内存中了,此时内存的使用权已由bootfa转交给内核,此时系统也会卸载bootfs

rootfs(root file system),在bootfs之上,包含的就是典型Linux系统中的/dev, /proc,/bin, /etc等标准目录和文件,rootfs就是各种不同的操作系统发行版,比如Ubuntu, CentOS等等
在这里插入图片描述

平时我们安装进虚拟机的CentOS都是好几个G,为什么Docker这里才200M?

在这里插入图片描述

对于一个精简的OS,rootfs可以很小,只需要包含基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootFS就可以了。由此可见对于不同的linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以共用bootfs

分层理解

分层的镜像

我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层一层的在下载!

在这里插入图片描述

思考: 为什么Docker镜像要采用这种分层的结构呢?

最大好处,我觉得莫过于资源共享了!比如有多个镜像都从相同的Base镜像构建而来,那么宿主机

只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享

查看镜像分层的方式可以通过 docker image inspect 命令!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@CZP ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 2622e6cca7eb 32 hours ago 132MB
portainer/portainer latest cd645f5a4769 9 days ago 79.1MB
redis latest 36304d3b4540 13 days ago 104MB
mysql latest 30f937e841c8 2 weeks ago 541MB
tomcat 9.0 1b6b1fe7261e 3 weeks ago 647MB
elasticsearch 7.6.2 f29a1ee41030 2 months ago 791MB
elasticsearch latest 5acf0e8da90b 20 months ago 486MB
[root@CZP ~]# docker image inspect redis
[
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:ffc9b21953f4cd7956cdf532a5db04ff0a2daa7475ad796f1bad58cfbaf77a07",
"sha256:d4e681f320297add0ede0554524eb9106d8c3eb3a43e6e99d79db6f76f020248",
"sha256:59bd5a888296b623ae5a9efc8f18285c8ac1a8662e5d3775a0d2d736c66ba825",
"sha256:c112794a20c5eda6a791cbec8700fb98eab30671a2248ac7e2059b475c46c45f",
"sha256:bf8b736583f08c02b92f8a75ac5ea181e4d74107876177caa80ddad8b6b57a72",
"sha256:6ef422d19214800243b28017d346c7ab9bfe63cb198a39312d1714394b232449"
]
}
]

理解:

所有的镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建一个新的镜像层,

举一个简单的例子,假如基于Ubuntu Linux 16.64创建一个新的镜像,这就是新镜像的第一层,如果在该镜像中添加python包,就会在该镜像之上创建第二个镜像层; 如果继续添加一个安全补丁,就会创建第三个镜像层

该镜像已经包含3个镜像层,如下图所示(这只是一个简单的例子)

在这里插入图片描述
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要,下图举了一个简单的例子,每个镜像层包含3个文件,而镜像包含了两个镜像层的6个文件
在这里插入图片描述

上图中的镜像层跟之前图中的略有区别,主要是便于展示文件

下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层的文件7是文件5的一个更新版本

在这里插入图片描述

这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件,这样就使得文件的更新版本作为一个新镜像层添加到镜像当中

Docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多层镜像层对外展示为统一的文件系统

Lunux上可用的存储引擎有AUFS,Overlay2,Device Mapper,Btrfs以及ZFS,顾名思义,每种存储引擎都是基于Linux对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点

Docker在Windows上仅支持windosfilter一种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW[1]

下图展示了与系统显示相同的三层镜像,所有的镜像层堆叠合并,对外提供统一的视图层
在这里插入图片描述

特点

Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!

这一层就是我们通常所说的容器层,容器之下的都叫镜像层
在这里插入图片描述

如何提交一个自己的镜像

commit镜像

1
2
3
4
docker commit 提交容器成为一个新的镜像

# 命令和git原理类似
docker commit -m="提交的描述信息" -a="作者" 容器ID 目标镜像名:[tag]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@CZP ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
db186da947d7 portainer/portainer "/portainer" 16 hours ago Up 16 hours 0.0.0.0:8088->9000/tcp interesting_shockley
bd4094db247f elasticsearch:7.6.2 "/usr/local/bin/dock…" 17 hours ago Up 17 hours 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch
94b00b6f6172 tomcat:9.0 "catalina.sh run" 17 hours ago Up 17 hours 0.0.0.0:8080->8080/tcp tomcat
d458bc50a808 nginx "/docker-entrypoint.…" 18 hours ago Up 18 hours 0.0.0.0:80->80/tcp nginx01
63d4c4115212 36304d3b4540 "docker-entrypoint.s…" 22 hours ago Up 22 hours 0.0.0.0:6379->6379/tcp redis
[root@CZP ~]# docker commit -a="czp" -m="add basic webapps app" 94b00b6f6172 tomcat_9.0:1.0
sha256:75e6ea173695b146c9ddf9d5865e7bdeb78e69c84d2d3520e516cd9f498a1e9a
[root@CZP ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat_9.0 1.0 75e6ea173695 8 seconds ago 652MB
nginx latest 2622e6cca7eb 33 hours ago 132MB
portainer/portainer latest cd645f5a4769 9 days ago 79.1MB
redis latest 36304d3b4540 13 days ago 104MB
mysql latest 30f937e841c8 2 weeks ago 541MB
tomcat 9.0 1b6b1fe7261e 3 weeks ago 647MB
elasticsearch 7.6.2 f29a1ee41030 2 months ago 791MB
elasticsearch latest 5acf0e8da90b 20 months ago 486MB

容器数据卷

什么是容器数据卷

docker的理念回顾

将应用和环境打包成一个镜像!

如果数据都在容器中,那么我们容器删除,数据就会丢失! ==需求: 数据可以持久化==

MYSQL, 容器删了,删库跑路! ==需求: mysql数据可以存储在本地!==

容器之间可以有一个数据共享的技术! Docker 容器中产生的数据,同步到本地!

这就是卷技术! 目录的挂载,将容器内的目录挂载到Linux上面!

在这里插入图片描述

总结一句话: 容器的持久化和同步操作! 容器间也可以数据共享的!

使用数据卷

方式一: 直接使用命令来挂载 -v

1
2
3
docker run -it -v 主机目录: 容器内目录  -p 主机端口: 容器端口

# 启动起来我们可以使用 docker inspect 容器id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 将宿主机的/root/test挂载到tomcat的/home目录
[root@CZP ~]# docker run -d -p 9999:8080 -v /root/test:/home --name="tomcat01" 1b6b1fe7261e
015001911b67f5e357b93c6bb05ebaf07aebe4f3abc455f9aa439afd83b9af78
[root@CZP ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
015001911b67 1b6b1fe7261e "catalina.sh run" 16 seconds ago Up 15 seconds 0.0.0.0:9999->8080/tcp tomcat01
db186da947d7 portainer/portainer "/portainer" 17 hours ago Up 17 hours 0.0.0.0:8088->9000/tcp interesting_shockley
bd4094db247f elasticsearch:7.6.2 "/usr/local/bin/dock…" 18 hours ago Up 17 hours 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch
94b00b6f6172 tomcat:9.0 "catalina.sh run" 18 hours ago Up 18 hours 0.0.0.0:8080->8080/tcp tomcat
d458bc50a808 nginx "/docker-entrypoint.…" 18 hours ago Up 18 hours 0.0.0.0:80->80/tcp nginx01
63d4c4115212 36304d3b4540 "docker-entrypoint.s…" 22 hours ago Up 22 hours 0.0.0.0:6379->6379/tcp redis

# 进入tomcat内部
[root@CZP test]# docker exec -it tomcat01 /bin/bash
root@015001911b67:/usr/local/tomcat# cd /home
# 在home目录创建b.java
root@015001911b67:/home# touch b.java
root@015001911b67:/home# read escape sequence
[root@CZP test]# cd /root/test
[root@CZP test]# ll
total 0
-rw-r--r-- 1 root root 0 Jun 11 11:03 b.java # b.java显示挂载成功

在这里插入图片描述

实战: 安装Mysql

思考: mysql的数据持久化的问题, data目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 获取镜像
[root@CZP ~]# docker pull mysql:5.7


# 运行容器,需要做数据挂载! # 安装启动mysql,需要配置密码,这是官方的
# 官方测试: docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=密码 -d mysql:tag

# 启动mysql
-d 后台运行
-p 端口映射
-v 端口映射
-e 环境配置
--name 容器名

[root@CZP czp]# docker run -d -p 3306:3306 -v /usr/czp/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=sa --name mysql mysql:5.7

# 启动成功之后,我们在本地使用sqlyog来连接测试一下

# sqlyog-连接到服务器的端口 ---服务器端口和容器端口映射,这个时候我们就可以连接上了

假设我们将容器删除

在这里插入图片描述

发现,我们挂载到本地的数据卷依旧没有丢失,这就实现了容器数据持久化功能

具名挂载和匿名挂载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 匿名挂载
-v 容器内路径!
docker -run -P -name nginx01 -v /etc/nginx nginx

# 查看所有的卷的情况
[root@CZP data]# docker volume ls

local 2c04226b82b31e3cddb80b5fffa17685883ff8c256024525b3a65b07b8281110

# 这里发现,这种就是匿名挂载, 我们在 -v只写了容器内的路径,没有写容器外的路径


# 具名挂载
[root@CZP data]# docker run -d -p 9099:80 -v nginxConfig:/etc/nginx 2622e6cca7eb
bd7ebf502166e5569ea3fb5eddaf41f4ff9a70df62b9143861dd702ae8c1cb31
[root@CZP data]# docker volume ls
DRIVER VOLUME NAME
local nginxConfig
# 通过 -v 卷名:容器内路径
# 查看一下这个卷

在这里插入图片描述

所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/卷名/_data

我们通过具名挂载可以方便的找到一个卷,大多数情况在使用的’具名挂载’

1
2
3
4
# 如何确定是具名挂载还是匿名挂载,还是指定路径挂载
-v 容器内路径 # 匿名挂载
-v 卷名:容器内路径 # 具名挂载
-v 宿主机路径 : 容器内路径 # 指定路径挂载

扩展:

1
2
3
4
5
6
7
8
# 通过 -v  容器内路径: ro rw 改变读写权限
ro read only
read and write

# 一旦设置了容器权限,容器对挂载出来的内容就有限定了!
docker -run -P -name nginx01 -v /etc/nginx:ro nginx
docker -run -P -name nginx01 -v /etc/nginx:rw nginx
ro : 只要看到ro就说明这个路径只能通过宿主机来改变,容器内部无法操作

初始Dockerfile

Dockerfile就是用来构建Dockerfile镜像的文件! 命令脚本!

1
2
3
4
5
6
7
8
9
# 创建一个dockerfile文件,名字可以随机 建议 dockerfile
# 文件中的内容

FROM centos

VOLUME ["volume01","volume02"]

CMD echo "---end---"
CMD /bin/hash

在这里插入图片描述

在这里插入图片描述

这个卷和外部一定有一个同步的目录

在这里插入图片描述

查看一下卷挂载的路径

在这里插入图片描述

测试一下刚才的文件是否同步出去了

这种方式我们未来使用的十分多,因为我们通常会构建自己的镜像!

假设构建镜像时没有挂载卷,要手动镜像挂载 -v 卷名: 容器内路径

数据卷容器

两个Mysql同步数据!

在这里插入图片描述

1
# 启动三个容器,通过我们刚才自己的镜像启动

在这里插入图片描述

在这里插入图片描述

1
2
# 测试: 可以删除docker01,查看一下docker02和docker03是否还可以访问这个文件
# 测试依旧可以访问

在这里插入图片描述

结论:

容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有人使用为止

但是一旦你持久化到了本地,这个时候,本地的数据是不hi删除的

Dockerfile

DockerFile是用来构建docker镜像的文件!命令参数脚本!

构建步骤:

  1. 编写一个dockerfile脚本
  2. docker build 构建成为一个镜像
  3. docker run 运行镜像
  4. docker push发布镜像(Docker hub , 阿里云镜像仓库! )

在这里插入图片描述

在这里插入图片描述

很多官方镜像都是基础包, 很多功能没有,我们通常会自己搭建自己的镜像!

官方可以制作镜像,那么我们也可以!

Dockerfile构建过程

很多指令:

  1. 每个保留关键字(指令)都是必须要大写
  2. 执行从上到下顺序执行
  3. ‘#’ 表示注释
  4. 每一个指令都会创建提交一个新的镜像层,并提交 !

在这里插入图片描述

dockerfile是面向开发的,我们以后要发布项目,做镜像就需要编写dockerfile文件,这个文件十分简单

Docker镜像 逐渐成为了一个企业交付的标准,必须要掌握 !

步骤: 开发,部署,上线,运维…缺一不可

DockerFIle: 构建文件,定义了一切的步骤 ,源代码

DockerImages: 通过DockerFile构建生成的镜像,最终发布运行的产品,原来是一个jar,war

Docker容器: 容器就是镜像运行起来提供服务的

DockerFile的指令

以前的话我们是使用的别人的,现在我们知道了这些指令后,我们来练习自己写一个镜像!

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM 			# 基础镜像, 一切从这里开始构建
MANTAINER # 镜像是谁写的, 姓名+邮箱
RUN # 镜像构建的时候需要运行的命令
ADD # 步骤, tomcat镜像,压缩包! 添加内容
WORKDIR # 镜像的工作目录
VOLUME # 挂载的目录
EXPOSE # 暴露端口配置
RUN # 运行
CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD # 当构建一个被继承 DockerFile 这个时候就会运行ONBUILD的指令,触发指令
COPY # 类似ADD,将我们文件拷贝到镜像中
ENV # 构建的时候设置环境变量!

img

实战测试

Docker Hub 中99%镜像都是从centos基础镜像过来的,然后配置需要的软件

在这里插入图片描述

创建一个自己的centos

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
# 1 编写一个DOckerfile的文件

FROM centos

MAINTAINER czp<2432688105@qq.com>

ENV MYPATH /usr/local

WORKDIR $MYPATH

RUN yum -y install vim
RUN yum -y install net-tools

EXPOSE 80

CMD echo $MYPATH
CMD echo "---end---"


# 2. 通过这个文件构建镜像
# 命令 docker build -f dockerfile文件路径 -t
Successfully built 5ebc296aad5a
Successfully tagged mycentos:1.0

# 3. 测试运行

增强之后的镜像

在这里插入图片描述

我们可以列出本地进程的历史

在这里插入图片描述

我们平时拿到一个镜像,可以研究一下它是怎么做的

CMD 和ENTRYPOINT的区别

1
2
CMD 		# 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令

测试cmd命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编写
[root@CZP dockerfile]# cat dockerfile-centos-test
FROM centos
CMD ["ls","-a"]


# 构建镜像
[root@CZP dockerfile]# docker build -f dockerfile-centos-test -t centostest .

# 想要追加一个命令 -l ls -al
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.
ERRO[0000] error waiting for container: context canceled

# cmd的情况下 替换了CMD["ls","-a"]命令,-不是命令追加

ENTRYPOINT是往命令之后追加

实战:Tomcat镜像

  1. 准备镜像文件. tomcat压缩包, jdk压缩包!
  2. 在这里插入图片描述
  3. 编写Dockerfile文件, 官方命名 Dockerfile, build会自动寻找这个文件,就不需要 -f 指定了!
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
FROM centos
MAINTAINER czp<2432688105@qq.com>

COPY readme.txt /usr/local/readme.txt

ADD apache-tomcat-9.0.33.tar.gz /usr/local/

ADD jdk-8u221-linux-x64.rpm /usr/local/


RUN yum -y install vim

ENV MYPATH /usr/local

WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.33

ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.33

# 配置环境变量
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:/CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.33/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.33/bin/logs/catalina.out
  1. 构建镜像
1
# docker build -t diytomcat .
  1. 本地测试

curl localhost:9090

发布自己的镜像

DockerHub

  1. 地址hub.docker.com 注册自己的账号!
  2. 确定这个账号可以登录
  3. 在服务器上提交自己的镜像
1
2
3
4
5
6
7
8
9
10
11
[root@CZP ~]# docker login --help

Usage: docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
  1. 登录完毕就可以提交镜像了,就是一步 docker push
1
2
3
4
5
#push自己的镜像到服务器上一定要带上版本号
[root@CZP ~]# docker push czp/centos:1.0


docker tag [id] [tag] 为容器添加一个版本

提交到阿里云镜像仓库

  1. 登录阿里云
  2. 找到容器镜像服务
  3. 创建命名空间

在这里插入图片描述

  1. 创建容器镜像

在这里插入图片描述

  1. 浏览阿里云

在这里插入图片描述

小结

在这里插入图片描述

Docker网络

理解docker0

测试

在这里插入图片描述

三个网络

1
# 问题: docker 是如何处理容器网络访问的?

在这里插入图片描述

在这里插入图片描述

原理

  1. 我们每启动一个docker容器,docker就会给docker容器分配一个ip,我们只要安装了docker,就会有一个网卡docker0,桥接模式,使用的技术是evth-pair技术!

再次测试 ip addr

在这里插入图片描述

  1. 再启动一个容器

在这里插入图片描述

1
2
3
4
# 我们发现这个容器带来网卡, 都是一对对的
# evth-pair 就是一对虚拟机设备接口,他们都是成对出现的,一端连着协议,一端彼此相连
# 正因为有这个特性,veth-pair 充当桥梁,连接各种虚拟网络设备的
# openStac,Docker容器之间的连接,OVS的连接,都是使用 evth-pair 技术
  1. 我们来测试一下

在这里插入图片描述

在这里插入图片描述

结论: tomcat01和tomcat02是共用的一个路由器,docker0

所有的容器不指定网络的情况下,都是docker0路由的,docker会给我们的容器分配一个默认的可用IP

小结

Docker 使用的是Linux的桥接,宿主机中是一个Docker容器的网桥,docker0

在这里插入图片描述

Docker中所有的网络接口都是虚拟的,虚拟的转发效率高

只要容器删除,对应网桥的一对就没了

在这里插入图片描述

思考一个场景,我们编写了一个微服务,database url=ip: 项目不重启,数据库IP换掉了,我们希望可以通过名字来访问服务

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
[root@CZP ~]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known

# 如何可以解决呢?


# 通过 --link 就可以解决网络问题
[root@CZP ~]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.18.0.4) 56(84) bytes of data.
64 bytes from tomcat02 (172.18.0.4): icmp_seq=1 ttl=64 time=0.128 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=2 ttl=64 time=0.097 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=3 ttl=64 time=0.091 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=4 ttl=64 time=0.109 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=5 ttl=64 time=0.097 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=6 ttl=64 time=0.096 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=7 ttl=64 time=0.092 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=8 ttl=64 time=0.094 ms
64 bytes from tomcat02 (172.18.0.4): icmp_seq=9 ttl=64 time=0.102 ms
^C
--- tomcat02 ping statistics ---
9 packets transmitted, 9 received, 0% packet loss, time 1007ms
rtt min/avg/max/mdev = 0.091/0.100/0.128/0.015 ms


# 反向是否可以ping通吗
[root@CZP ~]# docker exec -it tomcat02 ping tomcat03
1
/etc/hosts  配置端口和域名的绑定

在这里插入图片描述

本地探究 – link 就是我们在host配置中增加了一个172.18.0.3 tomcat02 312857784cd4

我们现在玩Docker已经不建议使用–link了!

自定义网络,不使用docker0!

docker0问题: 它不支持容器名连接访问!

自定义网络

查看所有的docker网络

在这里插入图片描述

网络模式

bridge : 桥接 docker 大桥

none: 不配置网络

host: 和宿主机共享网络

container: 容器内网络联通!

测试

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
# 直接启动的命令 --net brodge,默认docker0
docker run -d -P --name tomcat01 --net bridge tomcat

# docker0的特点: 默认的,域名是不能访问的, --link可以打通连接

# 自定义

[root@CZP ~]# docker network create --help

Usage: docker network create [OPTIONS] NETWORK

Create a network

Options:
--attachable Enable manual container attachment
--aux-address map Auxiliary IPv4 or IPv6 addresses used by Network driver (default map[])
--config-from string The network from which copying the configuration
--config-only Create a configuration only network
-d, --driver string Driver to manage the Network (default "bridge")
--gateway strings IPv4 or IPv6 Gateway for the master subnet
--ingress Create swarm routing-mesh network
--internal Restrict external access to the network
--ip-range strings Allocate container ip from a sub-range
--ipam-driver string IP Address Management Driver (default "default")
--ipam-opt map Set IPAM driver specific options (default map[])
--ipv6 Enable IPv6 networking
--label list Set metadata on a network
-o, --opt map Set driver specific options (default map[])
--scope string Control the network's scope
--subnet strings Subnet in CIDR format that represents a network segment

[root@CZP ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
677fae13a48c634dc03c56641b9ba31354846d31a196fdcb92c9ef6ddff73150
[root@CZP ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
228826a97a0b bridge bridge local
c3b4884cd4db host host local
677fae13a48c mynet bridge local
35885200f93d none null local

我们自己的网络就创建好了

在这里插入图片描述

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
[root@CZP ~]#  docker run -d -P --name tomcat-net-01 --net mynet tomcat:9.0
336dd072ca17ac1adf514c44c8dcbd3358146d6d60667f3a0f99dbbb3e305f09
[root@CZP ~]# docker run -d -P --name tomcat-net-02 --net mynet tomcat:9.0
2cea3bb29350ae99ce26c1bf6f8d1f2dcfb25bf8042193263ce275308e9eb42d
[root@CZP ~]# docker network inspect mynet
[
{
"Name": "mynet",
"Id": "677fae13a48c634dc03c56641b9ba31354846d31a196fdcb92c9ef6ddff73150",
"Created": "2020-06-14T16:49:14.554786193+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"2cea3bb29350ae99ce26c1bf6f8d1f2dcfb25bf8042193263ce275308e9eb42d": {
"Name": "tomcat-net-02",
"EndpointID": "ebff8e9ef22bd3d66d0de4229d1f3a3c610785b23005294f60f96f3089d52c3d",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
"336dd072ca17ac1adf514c44c8dcbd3358146d6d60667f3a0f99dbbb3e305f09": {
"Name": "tomcat-net-01",
"EndpointID": "69451bb0c95ed27d207cd2bade9c57fd2625c245b8b8cb3e5d0dea530a368683",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

现在不使用–link也可以ping名字了,推荐使用这种网络

1
2
3
4
5
6
7
[root@CZP ~]# docker exec tomcat-net-01 ping tomcat-net-02
PING tomcat-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.080 ms
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.096 ms
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq=3 ttl=64 time=0.086 ms
^C
[root@CZP ~]#

好处:

不同的集群使用不同的集群,保证集群之间是安全和健康的

网络联通

在这里插入图片描述

在这里插入图片描述

1
2
3
# 测试打通 tomcat01到tomcat-net-01
# 连通之后就是将 tomcat01 放到了mynet网络下
# 一个容器两个ip 阿里云: 公网ip 私网ip

在这里插入图片描述

这样容器之间就可以ping通了

实战 redis集群部署

在这里插入图片描述

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
# 创建网卡
docker network create --subnet 172.38.0.0/16 redis

# 通过脚本创建六个redis配置
for port in $(seq 1 6);\
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >> /mydata/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done



# 通过脚本运行六个redis
for port in $(seq 1 6);\
do \
docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \
-v /mydata/redis/node-${port}/data:/data \
-v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
done

# 停止redis并删除容器
for port in $(seq 1 6);\
do \
docker stop redis-${port}; \
docker rm redis-${port};
done

docker exec -it redis-1 /bin/sh #redis默认没有bash
redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

集群搭建成功

在这里插入图片描述

SpringBoot微服务打包Docker镜像

  1. 构建springBoot项目
  2. 打包应用
  3. 编写dockerfile
  4. 构建镜像
  5. 发布运行

DRF APIView请求生命周期流程图

img

img

drf入门规范

Web应用模式

前后端不分离

1
2
# 模板渲染在后端完成

前后端不分离

前后端分离(主流)

1
2
3
# 后端就只负责写接口,前端来调用,通信使用json格式

# 多端(web、app...)都可以使用同一个接口

前后端分离

API接口

通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介

四大特点

==url==

  • 长得像返回数据的url链接

==请求方式==

  • get、post、put、patch、delete

==请求参数==

  • json或xml(老项目)格式的key-value类型数据

==相应结果==

  • json或xml格式的数据

接口测试工具:Postman

官网下载https://www.getpostman.com/downloads/

RESTful API规范(3星)

写接口的规范,大部分接口都会按照这个规范去写(Web API接口的设计风格)

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
# 1 使用https协议进行传输数据(保证数据安全)

# 2 url中带关键字api
https://api.baidu.com
https://www.baidu.com/api

# 3 url中带版本信息
https://api.baidu.com/v1
https://api.baidu.com/v2

# 4 数据即资源,均使用名词(重要)
https://api.baidu.com/users
https://api.baidu.com/books
https://api.baidu.com/book

# 5 资源操作由请求方式决定(重要)
'查询操作' : get
'新增操作' : post
'修改操作' : put
'删除操作' : delete

https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书书
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书

# 6 请求url中带搜索筛选条件(重要)
https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置、
https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件

# 7 响应中要带状态码(自己定义,http响应状态码)
{
status:200
}

# 8 响应返回错误信息
{
status:200
msg: "无权限操作"
}

# 9 返回结果,遵循如下规范(大概率都没有遵循)
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

# 10 返回数据中带有链接地址
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"img": "https://image.baidu.com/kfc/001.png"
}
]
}

drf介绍与安装

1
2
3
4
5
6
7
8
9
# Django-Rest-Framework: 是django的一个app,可以借助它快速在django框架开发出符合restful规范的接口

# Django-Rest-Framework提供了: 认证、权限、限流、过滤、分页、接口文档等功能支持

# 官方文档: https://www.django-rest-framework.org/

# python Django版本的支持情况
Python(3.53.63.73.83.9)
Django(2.23.03.1) 2.x用的多,1.x(老项目)

CBV源码分析(2星)

1 views.Book.as_view()执行完,一定是一个函数的内存地址

2 as_view是View类的类方法,类来调用

image-20210712230515567.png

3 as_view中的view是一个闭包函数

t6yClDvJ1mSqBz7.png

4 执行了View类的dispatch

YnVcwmbhOkAz1NC.png

X4UrdcEtFkosxhm.png

拓展

1
2
3
4
5
6
7
8
# 如果在当前视图类中重写dispatch方法,则可以实现:在get、post等方法执行之前或执行之后去执行重写的代码,完成类似装饰器的效果

例如
def dispatch(self, request, *args, **kwargs):
# 代码(get、post等方法执行之前执行)
response = super().dispatch(request, *args, **kwargs)
# 代码(get、post等方法执行之后执行)
return response

drf基本使用及流程分析(3星)

继承APIView使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from rest_framework.views import APIView
from .models import Student
from .serializers import Student_serializers
from rest_framework.response import Response


class Student_serializers_APIView(APIView):
# 查询所有学生信息
def get(self, request, *args, **kwargs):
student_list = Student.objects.all()
ser = Student_serializers(instance=student_list, many=True)
return Response(ser.data)

# 新增学生信息
def post(self, request, *args, **kwargs):
ser = Student_serializers(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)

APIView的执行流程

==1 APIView继承了django的View==

EuNedGk5KbrJHwx.png

==2 APIView中重写了as_view==

rXMFCHUbcgnSOVh.png

==3 执行self.dispatch() —> APIView的dispatch==

ti937vJdkYojVL1.png

WwzN8m5tXhKVcID.png

==4 在视图类中使用的request对象是新的request对象,而老的则是request._request==

dhVZj9XbIBoweHx.png

dMvBYCriDjX93z4.png

==5 新的request对象中有一个属性:data==

1
2
3
4
5
6
'''
1 data是post请求携带的数据 ---> 字典
2 无论是什么编码格式,只要是post提交的数据,都在request.data中
3 以后再取值,都从request.data中取
'''

==6 结论:继承了APIView后,request对象变成新的request,其他大部分和原来一样使用==

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
1 以后如果使用了drf,继承APIView(drf提供了很多view,他们都是继承自APIView)
注意:
'''
包装出了一个新的request,在视图函数中使用时,跟原先没有区别

取post提交的数据,不要再从request.POST中取了,要从request.data中取

取get提交的参数,尽量不从request。GET中取了,要从request.query_params中取
'''

2 Request类(drf的)需要掌握

2.1 request.data # 方法包装成了数据属性
2.2 request.query_params # 就是request._request.GET
2.3 request.FILES # 上传的文件

# 大部分用起来和原来一样

3 APIView类
'''
包装了新的request
执行了认证、权限、频率...
处理了全局异常
包装了response对象
'''

drf配置

1
2
3
4
5
6
7
# 配置
# 1 后期,drf的所有配置,都写在这个字典中
# 2 drf有个默认配置文件(drf源码的 ---> setting.py),如果项目的配置文件配置了,优先使用项目的,如果没有配置,使用内置的
# 3 返回样式的配置,一般不配置
REST_FRAMEWORK={
...
}

序列化组件

介绍

作用

1
2
3
4
5
1 序列化,序列化器(类)会把模型对象(Book对象,Queryset对象)转换成字典,经过response以后变成json字符串

2 反序列化,把客户端发送过来的数据,经过request以后变成字典(request.data),序列化器(类)可以把字典转成模型

3 反序列化,完成数据校验功能

本质

1
# 本质就是写一个类,继承一个基类,可以完成序列化、反序列化和数据校验

序列化器之Serializer类的使用(跟表模型没有必然联系)(5星)

视图类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from django.shortcuts import render
from rest_framework.views import APIView
from .models import Student
from .serializers import Student_serializers
from rest_framework.response import Response


# Create your views here.


class Student_serializers_APIView(APIView):
# 查询所有学生信息
def get(self, request, *args, **kwargs):
student_list = Student.objects.all()
ser = Student_serializers(instance=student_list, many=True)
return Response(ser.data)

# 新增学生信息
def post(self, request, *args, **kwargs):
ser = Student_serializers(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)


class Student_Detial_serializers_APIView(APIView):
# 查询单个学生信息
def get(self, request, pk):
student = Student.objects.filter(pk=pk).first()
ser = Student_serializers(instance=student)
return Response(ser.data)

# 修改单个学生信息
def put(self, request, pk):
student = Student.objects.filter(pk=pk).first()
ser = Student_serializers(instance=student, data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)

# 删除单个学生信息
def delete(self, request, pk):
Student.objects.filter(pk=pk).delete()
return Response()

序列化类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rest_framework import serializers
from .models import Student


class Student_serializers(serializers.Serializer):
name = serializers.CharField(max_length=32)
age = serializers.IntegerField()

def create(self, validated_data):
student = Student.objects.create(**validated_data)
return student

def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.age = validated_data.get('age')
instance.save()
return instance

路由

1
2
3
4
5
6
7
8
9
10
11
from django.contrib import admin
from django.urls import path
from app01 import views

from django.views import View

urlpatterns = [
path('admin/', admin.site.urls),
path('student/', views.Student_serializers_APIView.as_view()),
path('student/<int:pk>', views.Student_Detial_serializers_APIView.as_view())
]

模型

1
2
3
4
5
6
7
8
from django.db import models

# Create your models here.

class Student(models.Model):
name = models.CharField('姓名', max_length=32)
age = models.IntegerField('年龄')

Serializers高级使用(5星)

1 source字段的作用

1
2
3
4
5
6
# source:可以对应表模型的字段和方法(返回结果是什么,字段就是什么)
# 一般用来做一对多,多对多的字段返回
'''
注:
source如果是字段,会显示字段,如果是方法,会执行方法,不用加括号
'''

2 SerializerMethodFidld使用方法

1
2
3
4
5
# 一般用来添加需要查询该表以外的其他表(或关联表)的字段、方法

course_detail = serializers.SerializerMethodField()
def get_course_detail(self, obj):
return {'name':obj.course.name,'teacher':obj.course.course_teacher}

3 数据校验

==反序列化时需要进行数据校验==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
'使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或者保存成模型类对象'

# 在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否者返回False

# 1 验证失败,可以通过序列化器对象的error属性获取错误信息,返回字典,包含了字段和字段的错误.如果是非字段错误,可以通过修改REST-framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的key值

# 2 验证成功,可以通过序列化器对象validdated_data属性获取数据


# 3 字段自己的校验规则

class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
id = serializers.IntegerField(label='ID', read_only=True)
btitle = serializers.CharField(label='名称', max_length=20)
bpub_date = serializers.DateField(label='发布日期', required=False)
bread = serializers.IntegerField(label='阅读量', required=False)
bcomment = serializers.IntegerField(label='评论量', required=False)
image = serializers.ImageField(label='图片', required=False)

# 3.1 通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进行验证(views.py)

def post(self, request, *args, **kwargs):
ser = Publish_modelserializers(request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response({msg:ser.errors.get('name')})

ps: is_valid()方法还会在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST-framework接收到此异常,会向前端返回HTTP 400 Bad Request

# 3.2 补充定义验证行为

'注意'
验证顺序是:字段自己的限制 ---> 自己的局部钩子 --->(全部字段以及局部验证完毕以后再验证全局钩子)

3.2.1 validate_字段名(局部钩子)

def validate_name(self,name):
if name.startswith('sb'):
raise ValidationError('不能以sb开头')
else:
return name

3.2.2 validate(全局钩子)

def validate(self, attrs):
bread = attrs['bread']
bcomment = attrs['bcomment']
if bread < bcomment:
raise serializers.ValidationError('阅读量小于评论量')
return attrs

3.2.3 validators(在字段中添加validators参数,补充验证行为)

def about_django(value):
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的")

class BookInfoSerializer(serializers.Serializer):
"""图书数据序列化器"""
id = serializers.IntegerField(label='ID', read_only=True)
btitle = serializers.CharField(label='名称', max_length=20, validators=[about_django])
bpub_date = serializers.DateField(label='发布日期', required=False)
bread = serializers.IntegerField(label='阅读量', required=False)
bcomment = serializers.IntegerField(label='评论量', required=False)
image = serializers.ImageField(label='图片', required=False)

模型类序列化器ModelSerializer(跟表模型有关联)(5星)

1 介绍

1
drf为我们提供了一个ModelSerializer模型序列化器来帮助我们快速创建一个Serializer类

2 ModelSerializer与Serializer的区别

  • 基于模型类自动生成一系列字段
  • 基于模型类自动为Serializer生成validators,比如unique_together
  • 包含默认的create()和update()的实现

3 使用

1
2
3
4
5
6
7
class Publish_modelserializers(serializers.ModelSerializer):	# 继承ModelSerializer
class Meta:
model = Publish # 表模型
fields = '__all__' # 1 序列化的字段
fields = ['id','name'] # 2 用来手动指定字段
exclude = ['id'] # 3 排除的字段(1、2、3只能同时使用一个)
depth # 深度,一般不用

4 重写字段

1
2
3
name = serializers.SerializerMethodField()
def get_name(self, obj):
return "出版社 : " + obj.name

5 扩写字段(表模型中没有的字段)

1
2
3
4
5
6
7
8
9
'''
注意:
嵌套反序列化时正常只能传pk值,需要两个字段(一个展示(read_only),一个添加数据(write_only))
否则反序列化嵌套数据时报错
'''

name2 = serializers.SerializerMethodField()
def get_name2(self, obj):
return " 出版社2:东京出版社"

6 反序列化时,字段自己的校验规则,是映射表模型的

7 局部钩子与全局钩子(与Serializer一样)

8 write_only与read_only

1
2
3
name=serializers.CharField(write_only=True)		# 反序列化的时候使用,序列化时不用(只读)

name=serializers.CharField(read_only=True) # 序列化的时候使用,反序列化时不用(只写)

9 extra_kwargs(添加额外参数选项)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Meta:
model = Publish
fields = '__all__'
extra_kwargs = {
'name':{
'required':True,
'min_length':3,
'write_only':True
'error_messages':{
'required':'必须填',
'min_length':'最少三位'
},
'addr':{
'read_only':True
'required':True
}
}
}

序列化器字段和字段参数(2星)

常用字段类型:

字段 字段构造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices) choices与Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)

选项参数:

参数名称 作用
max_length 最大长度
min_lenght 最小长度
allow_blank 是否允许为空
trim_whitespace 是否截断空白字符
max_value 最小值
min_value 最大值

通用参数:

参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息

局部钩子和全局钩子源码分析(2星)

1 入口是ser.is_valid(),是BaseSerializer的方法

7dILKc5ksVWO4NR.png

2 找到全局钩子

OdbuBatSUYpG73C.png

3 找到局部钩子

OHmK9NrponcjDXq.png

ImHyxpcjvD14gXC.png

序列化组件部分源码分析(2星)

1
2
3
4
5
6
# 前戏:对象的实例化过程:__new__在__init__之前执行

# 1 序列化类在实例化的时候,先调用的是BaseSerializer中的__new__方法

# 2 ListSerializer与自己Serializer有什么联系?
[ser1,ser2,ser3] ser

jgpMR7V3YW6BIdn.png

HxlAO1sVUpLuiq6.png

请求与响应(3星)

drf请求全局和局部配置

请求默认支持三种编码格式

  • urlencoded
  • json
  • formdata
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 默认支持三种

'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser', # 解析application/json格式
'rest_framework.parsers.FormParser', # 解析application/x-www-form-urlencoded
'rest_framework.parsers.MultiPartParser' # multipart/form-data
]

# 全局配置

from rest_framework.parsers import JSONParser

class BookView(ViewSetMixin,ListAPIView,CreateAPIView):
parser_classes = [JSONParser,]

Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# rest-framework基于Django的request封装了一个新的Request类的request对象
rest-framework传入视图的request对象不再是Django默认的HttpRequest对象,而是rest-framework提供的扩展的HttpRequest类的Request类的对象

# Request对象的数据是根据前端发送数据的格式进行解析之后的结果(无论前端发送哪种格式的数据,都可以用统一的方式读取数据)
rest-framework提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单类型数据等)将请求数据进行parse解析,解析为类字典(QueryDict)对象保存在Request对象中

# 常用属性
1 request.data:
包含了对POST、PUT、PATCH请求方式解析后的数据
利用了rest-framework的parsers解析器,不仅支持表单类型数据,也支持JSON类型数据

2 request.query_params
request.query_params与Django的request.GET相同,只是更换了更正确的名字

'''
重点:
1 继承APIView后,视图类中的request对象是新的request对象
2 request.data、request.query_params的使用
'''

Response

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
# drf中的Response对象

# 继承关系:Response---> SimSimpleTemplateResponse ---> django的HttpResponse

# 属性、参数

data=None # ser.data,传字典或列表,序列化成json格式字符串给前端
status=None # 状态码,http响应的状态码
template_name=None # 模板名字,在浏览器访问,看到的好看的模板(自定制)(用得少)
headers=None # 响应头,字典
exception=False # 异常(不用管)
content_type=None # 响应编码类型

'''
重点:
data
status
headers
'''

# 使用
response={'code':100, 'msg':'查询成功', 'request':ser.data}
return Response(response,status=status.HTTP_201_CREATED,headers={'xxx':"xxx"})

# 配置
1 通过配置,设置响应格式(浏览器模板样子、纯json)
# 1 后期,drf的所有配置,都写在这个字典中
# 2 drf有个默认配置文件(drf源码的 ---> setting.py),如果项目的配置文件配置了,优先使用项目的,如果没有配置,使用内置的
# 3 返回样式的配置,一般不配置
REST_FRAMEWORK={
#配置响应格式,默认有俩(json,浏览器的)
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
]
}

2 局部配置(只针对某一个视图函数)
在视图类上写
renderer_classes = [JSONRenderer]
3 配置的三个层次(优先级)
局部 ---> 项目 ---> drf默认

视图组件(5星)

Uc5s1hblaJnoNqA.png

两个视图基类

APIView

1
2
3
# APIView 是rest-framework提供的所有视图的基类,继承自Django的View父类

# 在APIView中仍以常规的类视图定义方法来实现get()、post()...请求方式的方法

APIView与View的区别

  • 传入到视图方法中的是rest-framework的Request类的request对象,而不是Django的HttpResponse对象
  • 视图方法返回的是rest-framework的Response类的response对象,视图会因为响应数据设置(rander)符合前端要求的格式
  • 任何APIException异常都会被捕获到,并处理成合适的响应信息
  • 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制

支持定义的类属性

  • authentication_classes列表或元组(身份认证类)
  • permissoin_classes列表或元组(权限检查类)
  • throttle_classes列表或元组(流量/频率控制类)

GenericAPIView(通用视图类)

1
2
3
4
5
# 继承自APIView,主要增加了操作序列化器和数据库查询的方法,作用是为Mixin扩展类的执行提供方法支持,通常在使用时,可搭配一个或多个Mixin扩展类

'''
涉及到数据库操作,尽量选择GenericAPIView,减少代码量
'''

提供的关于序列化器使用的属性与方法

  • 属性
    • ==serializer_class== : 指明视图使用的序列化器
  • 方法
    • ==get_serializer_class(self)== : 当出现一个视图类中调用多个序列化器时,可以通过条件判断get_serializer_class方法中通过返回不同的序列器类名,就可以让视图方法执行不同的序列化器对象了
    • ==get_serializer(self, *args, **kwargs)== : 返回序列化器对象,主要用来提供给Mixin扩展类使用(该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用)(可以传instance、data、many)
      • request :当前视图的请求对象
      • view :当前请求的类视图对象
      • format :当前请求期望返回的数据格式

提供的关于数据库查询的属性与方法

  • 属性
    • ==queryset== :查询的表名.objects.all() (这里不写all会自动添加上all,但推荐都写上)
  • 方法
    • ==get_queryset(self)== :获取查询需要用到的所有数据
    • ==get_object(self)== :获取查询需要用到的单条数据 (若详情访问的模型类对象不存在,会返回404)

五个视图扩展类

这五个扩展类需要搭配GenericAPIView父类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法

CreateModelMixin

  • 内部有create方法 :新增

ListModelMixin

  • 内部有list方法 :查询所有

DestroyModelMixin

  • 内部有destroy方法 :删除单条

UpdateModelMixin

  • 内部有update方法 :修改一条

RetrieveModelMixin

  • 内部有retrieve方法 :查询单条

九个视图子类

ListAPIView

  • list

CreateAPIView

  • create

UpdateAPIView

  • update

DestroyAPIView

  • destroy

RetrieveAPIView

  • retrieve

ListCreateAPIView

  • list+create

RetrieveUpdateDestroyAPIView

  • retrieve+update+destroy

RetrieveDestroyAPIView

  • retrieve+destroy

RetrieveUpdateAPIView

  • retrieve+update

视图集

ViewSetMixin

1
2
3
4
5
6
7
8
# 核心:重写了as_view方法

# 能够实现:请求方式和视图类中方法的映射

# 结论:只要继承ViewSetMixin的视图类,路由中as_view()里就要传action参数(字典)


path('books/', views.BookView.as_view({'get':'list','post':'create'}))

Pt2UsRL61ycJu8W.png

ViewSet

  • 继承自APIView与ViewSetMixin,作用与APIView类似,提供了身份认证、权限校验、流量管理…
  • 主要通过继承ViewSetMixin来实现在调用as_view()时传入字典的映射处理
  • 没有提供任何动作action方法,需要我们自己实现action方法
  • 需要自己编写list、retrieve、create、update、destroy等方法

GeneriViewSet

  • 继承自GenericAPIView与ViewSetMixin
  • 实现了调用as_view()时传入字典的映射处理
  • 提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用

ModelViewSet

  • 继承自GenericViewSet,包括了五个视图扩展类

ReadOnlyModelViewSet

  • 继承自GenericViewSet,包括了ListModelMixin、RetrieveModelMixin

路由组件(5星)

Routers

对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来快速实现路由创建

  • SimpleRouter(常用)

  • DefaultRouter(多了一些花里胡哨的路由,用得少)

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.contrib import admin
from django.urls import path, include
from app01 import views
from rest_framework.routers import SimpleRouter


router = SimpleRouter()
router.register('book', views.Book_modelviewset)
router.register('publish', views.Publish_modelviewset)
router.register('', views.Login, basename='login')


urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)) # 方式一
]


urlpatterns += router.urls # 方式二(选一种使用)

试图类中派生的方法,自动生成路由(action)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Login(ViewSetMixin, APIView):
authentication_classes = []
permission_classes = []
@action(methods=['POST'], detail=False) # 自己扩展的方法(派生)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user_alive = models.User.objects.filter(username=username, password=password).first()
if user_alive:
token = uuid.uuid4()
models.User_token.objects.update_or_create(defaults={'token':token}, user=user_alive)
return Response({'code':100, 'msg':'登录成功', 'token':token}, headers={'token':token})
else:
return Response({'code':101, 'msg':'登录失败'})

# 这样自动生成的路由是(books/login/) (如果prefix参数传空字符串,则是/login/)

'''
参数解释:
method: 用来指定请求方式,默认GET
detail: 用来指定是否传入pk值(如:查所有或查单条),False:不传pk,True:传pk
url_path: 用来指定url的路径,默认方法名
url_name: 用来指定url的别名
'''

'''
注意:
1 如果继承了APIView,那么想要自动创建路由,则必须写action动作并在urls.py中传basename参数来指定视图

2 必须继承ViewSetMixin
'''


认证组件(5星)

判断用户是否登录

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# auth.py(自己建的任意名称的py文件)

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models


class Login_authentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
user_token = models.User_token.objects.filter(token=token).first()
if user_token:
return user_token.user, token
else:
raise AuthenticationFailed('请先登录')

# views.py

class Login(ViewSetMixin, APIView):
authentication_classes = [Login_authentication] # 局部使用
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user_alive = models.User.objects.filter(username=username, password=password).first()
if user_alive:
token = uuid.uuid4()
models.User_token.objects.update_or_create(defaults={'token':token}, user=user_alive)
return Response({'code':100, 'msg':'登录成功', 'token':token}, headers={'token':token})
else:
return Response({'code':101, 'msg':'登录失败'})

'''
全局配置:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':[
'app01.auth.Login_authentication'
]
}

局部禁用:
authentication_classes = []

'''

源码分析

YcjEfW3CunFB9K4.png

image-20210705201337157.png

image-20210705201527608.png

image-20210705201731741.png

image-20210705205949346.png

image-20210705210247174.png

image-20210705210417976.png

image-20210705210543266.png

image-20210705210958562.png

权限组件(4星)

简单使用

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
# 自己建的任意名字的py文件(auth.py)

from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission
from app01 import models

class User_permission(BasePermission):
def has_permission(self, request, view):
if models.User.user_type == 1:
return True
else:
raise PermissionDenied(request.user.get_user_type_display()+'权限不够')

# views.py

class Login(ViewSetMixin, APIView):
permission_classes = [User_permission] # 局部使用
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user_alive = models.User.objects.filter(username=username, password=password).first()
if user_alive:
token = uuid.uuid4()
models.User_token.objects.update_or_create(defaults={'token':token}, user=user_alive)
return Response({'code':100, 'msg':'登录成功', 'token':token}, headers={'token':token})
else:
return Response({'code':101, 'msg':'登录失败'})

'''
全局配置:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES':[
'app01.auth.User_permission'
]
}

局部禁用:
permission_classes = []
'''

源码分析

image-20210706172319765.png

image-20210706173120291.png

频率组件(5星)

自定义频率类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 自定义的逻辑
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
class MyThrottles():
VISIT_RECORD = {}
def __init__(self):
self.history=None
def allow_request(self,request, view):
#(1)取出访问者ip
# print(request.META)
ip=request.META.get('REMOTE_ADDR')
import time
ctime=time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip]=[ctime,]
return True
self.history=self.VISIT_RECORD.get(ip)
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while self.history and ctime-self.history[-1]>60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history)<3:
self.history.insert(0,ctime)
return True
else:
return False
def wait(self):
import time
ctime=time.time()
return 60-(ctime-self.history[-1])

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# until.py(任意名称py文件)

class User_throttle(SimpleRateThrottle):
scope = 'user'
def get_cache_key(self, request, view):
return request.user.username # 返回谁,就以谁做限制

# views.py
throttle_classes = [User_base_throttle,]

# 全局配置
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'user':'5/m'
},
}

# 局部禁用
throttle_classes = []

源码分析

image-20210706172319765.png

image-20210706185151028.png

image-20210706185449845.png

image-20210706185753175.png

image-20210706190151150.png

image-20210706190316846.png

过滤与排序(4星)

简单使用

==查询所有==才需要过滤(根据条件过滤),排序(按某个规则排序)

内置类使用

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
# 内置过滤类(在视图类中配置)

from rest_framework.filters import SearchFilter

class BookView(ViewSetMixin,ListAPIView):
# 在视图类中配置,最顶层的类至少是GenericAPIView
filter_backends = [SearchFilter,]
# 过滤条件,按名字过滤
search_fields=['name','publish']

# 查询使用
http://127.0.0.1:8000/books/?search=达 # 出版社中或名字中有 达 就能查询出来

# 内置排序类(既有排序,又有过滤)

from rest_framework.filters import OrderingFilter,SearchFilter

class BookView(ViewSetMixin,ListAPIView):

# 在视图类中配置,最顶层的类至少是GenericAPIView
filter_backends = [SearchFilter,OrderingFilter,]
# 过滤条件,按名字过滤
search_fields=['name']
# 按哪个字段排序
ordering_fields=['price','id']

# 查询使用
http://127.0.0.1:8000/books/?ordering=price,-id

第三方插件使用(django-filter)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 视图类中

from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin,ListAPIView):

# 在视图类中配置,最顶层的类至少是GenericAPIView
filter_backends = [DjangoFilterBackend,]
# 按名字、价格过滤
filter_fields=['name','price']

# 查询时
http://127.0.0.1:8000/books/?name=红楼梦
http://127.0.0.1:8000/books/?name=四方达&price=15

异常处理(4星)

简单使用

全局统一捕获异常,返回固定的格式

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
# 使用步骤
1 写一个函数
2 在配置文件中配置

1 写函数:

from rest_framework.views import exception_handler
from rest_framework.response import Response

def comment_exception_handler(exc, context):
response = exception_handler(exc, context)
if response:
data = {
'code':1001,
'msg':'错误',
'detail':response.data
}
return Response(data)
else:
data = {
'code':1002,
'msg':'未知错误'
}
return Response(data)

2 在配置文件中配置

REST_FRAMEWORK = {
'EXCEPTION_HANDLER':'utils.comment_exception_handler'
}

分页功能(5星)

查询所有,才有分页功能(例如网站的下一页功能,app的下滑加载更多)

PageNumberPagination基本分页

  • 重要类属性
    • page_size = api_settings.PAGE_SIZE (每页显示条数)
    • page_query_param = ‘page’ (查询时用的参数)
    • page_size_query_param = None (更改返回条数)
    • max_page_size = None (每页最大显示条数)
  • 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1 写一个类,继承PageNumberPagination,重写4个类属性

class CommonPageNumberPagination(PageNumberPagination):
page_size = 2
page_query_param = 'page'
page_size_query_param = 'search'
max_page_size = 5

# 2 在视图类中配置写好的分页类

class BookAPIView(ViewSetMixin, ListAPIView):
queryset = models.Books.objects.all()
serializer_class = BookModelSerializer
pagination_class = CommonPageNumberPagination

LimitOffsetPagination偏移分页

  • 重要的类属性
    • default_limit = api_settings.PAGE_SIZE (每页显示条数)
    • limit_query_param = ‘limit’ (查询时用的参数)
    • offset_query_param = ‘offset’ (offset偏移的查询参数)
    • max_limit = None (每页最大显示条数)
  • 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1 写一个类,继承LimitOffsetPagination,重写4个类属性

class CommonLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 5

# 2 在视图类中配置写好的分页类

class BookAPIView(ViewSetMixin, ListAPIView):
queryset = models.Books.objects.all()
serializer_class = BookModelSerializer
pagination_class = CommonLimitOffsetPagination

CursorPagination游标分页

  • 重要的类属性
    • cursor_query_param = ‘cursor’ (查询条件)
    • page_size = api_settings.PAGE_SIZE (每页显示条数)
    • ordering = ‘-created’ (排序)
    • page_size_query_param = None (更改每页显示条数)
    • max_page_size = None (每页最大显示条数)
  • 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1 写一个类,继承LimitOffsetPagination,重写4个类属性

class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor'
page_size = 2
ordering = '-id'
page_size_query_param = 'offset'
max_page_size = 5

# 2 在视图类中配置写好的分页类

class BookAPIView(ViewSetMixin, ListAPIView):
queryset = models.Books.objects.all()
serializer_class = BookModelSerializer
pagination_class = CommonCursorPagination
  • 优缺点
1
2
3
# 优点:速度最快,数据量越大,越有优势(没有那么大的数据量,用的不多)

# 缺点:只能前一页和后一页,不能直接跳到某一页

继承APIView实现三种分页方式

1
2
3
4
5
6
7
class BookAPIView2(APIView):
def get(self, request, *args, **kwargs):
book_list = models.Books.objects.all()
pagination = CommonPageNumberPagination()
book_list2 = pagination.paginate_queryset(book_list, request, self)
ser = BookModelSerializer(instance=book_list2, many=True)
return pagination.get_paginated_response(ser.data)

自动生成接口文档(3星)

  • coreapi
  • swagger

使用coreapi自动生成

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
# 第一步:安装coreapi

# 第二步:路由中配置

from rest_framework.documentation import include_docs_urls

urlpatterns = [
path('docs/', include_docs_urls(title='查询所有')),
]

# 第三步:配置文件中配置

REST_FRAMEWORK = {
## 接口文档配置
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}

ps:'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',

# 第四步:写接口,加注释

# 第五步:访问

ps:extra_kwargs = {
'字段':{'help_text':'xxxx'}
}

RBAC(4星)

image-20210708172129848.png

img

RBAC:Role-Based Access Control (基于角色的访问控制)

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 权限与角色(组)相关联,用户通过称为适当角色(组)的成员而得到这些角色(组)的权限

# 极大的简化了权限的管理(相互依赖)

# Django的Auth组件(app)采用的认证规则就是RBAC

1 User表 :存用户信息
2 Permission表 :存权限
3 Role表 :存角色(组)

4 Group_Role中间表 :权限赋予角色(多对多)
5 User_Group中间表 :角色赋予用户(多对多)
6 User_Permission中间表 :权限临时赋予角色(多对多)

'''
ps:
1 Django后台管理admin自带RBAC
2 基于admin做二次开发(simple-ui)
3 基于前后端分离实现RBAC(https://github.com/liqianglog/django-vue-admin)
4 前后端混合的后台前端模板(X-admin--->快速开发后台管理系统)
5 智慧大屏(https://gitee.com/kevin_chou/dataVIS.git)
'''

JWT(5星)

介绍(5星)

Json Web Token(JWT):一种前后端的认证方式

构成和工作原理

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
# 三段式:header,payload,signature

header(头):认证方式,加密方式,公司名字...
{
'typ':'JWT',
'alg':'HS256'
}

payload(荷载):用户信息,过期时间,签发时间...
{
"userid":"2",
"name":"Jerry",
"exp":121456
}

signature(签名):把前面的头和荷载通过设置好的加密方式得到的串

# 签发和校验

1 用户登录成功:
1.1 构造token的头(固定)
1.2 构造荷载
1.3 使用设置好的加密方式对头和荷载加密,得到签名,三者用.拼接起来,生成一个token串(使用base64编码)
例如: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

2 用户携带token,来到后端,把头和荷载再使用相同的加密方式加密,得到签名,比较新签名和旧签名是否一致,如果一致,说明该token可以信任,解析出当前用户(荷载),继续往后走

base64编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 编码

import base64
import json
dic={'name':'lqz','id':1}
user_info_str=json.dumps(dic)
# res=base64.b64encode(bytes(user_info_str,encoding='utf-8'))
res=base64.b64encode(user_info_str.encode('utf-8'))
print(res) # eyJuYW1lIjogImxxeiIsICJpZCI6IDF9

# 解码

res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ='.encode('utf-8'))
print(res)


'''
ps:
base64长度是4的倍数,如果不足,需要用=号补齐
'''

JWT快速使用(2星)

使用第三方:

签发

1
2
3
4
5
6
7
# 只需要在路由中加入,向这个地址发送post请求,携带用户名密码,就可以签发token

path('login/', obtain_jwt_token)

# 内部使用的是Django自带的auth组件(app)的user表

# 只需要在前端向配置的地址,发送post请求,携带用户名密码,就可以签发token

认证

1
2
3
4
5
6
7
8
9
10
11
12
# 在视图类中配置,认证类和权限类(simplejwt只需要认证类)

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated

class BookView(ViewSetMixin,ListAPIView):
authentication_classes = [JSONWebTokenAuthentication,]
permission_classes = [IsAuthenticated,]

# 前端访问,需要在请求头中加入,如果不携带,或者篡改了,认证就无法通过
key:Authorization
value:jwt xxx(以空格切割取token)

JWT使用auth表签发token,自定制格式(3星)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 只需要写一个函数,在配置文件中配置

# 函数

def jwt_response_payload_handler(token, user=None, request=None):
return {
'code':100,
'msg':'登录成功',
'username':user.username,
'token': token,
}

# 配置文件

JWT_AUTH ={
# token的过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
# 自定义认证结果:见下方序列化user和自定义response
# 如果不自定义,返回的格式是固定的,只有token字段
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}

obtain_jwt_token源码分析(2星)

image-20210708200119777.png

image-20210708201115115.png

image-20210708202015404.png

image-20210708202554768.png

image-20210708202924385.png

image-20210708203058131.png

image-20210708203535421.png

image-20210708204823746.png

基于jwt的认证类(5星)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'''
重点逻辑在authenticate方法中:
1 取出客户端传入的token(后端自己规定是从头/路径中拿)
2 验证jwt的签名(使用模块提供的)
3 通过payload得到当前登录用户对象(使用模块提供的)
4 返回user,token
'''

class JWTAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
print(request.META)
# token=request.query_params.get('HTTP_AUTHORIZATION',None)
token=request.META.get('HTTP_AUTHORIZATION',None)
if token:
# 校验token是不是过期了,是不是合法,
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:

raise AuthenticationFailed('token过期')
except jwt.DecodeError:

raise AuthenticationFailed('token认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token不合法')
else:
raise AuthenticationFailed('token没有携带')
user = self.authenticate_credentials(payload)

return (user, token)

'''
三种方式得到user:
1 继承,直接使用该类的方法
2 把方法copy出来,导入一箩筐
3 完全自己写(copy出来改吧改吧)
'''

基于自定义User表,签发token(5星)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# urls.py

router=SimpleRouter()
router.register('books',views.BookView)
router.register('user',views.UserInfoView,basename='user')
urlpatterns = [
path('', include(router.urls)),
]

# views.py

class UserInfoView(ViewSet):
@action(methods=['POST'],detail=False)
def login(self,request):
username=request.data.get('username')
password=request.data.get('password')
res={'code':'100','msg':'登录成功'}
user=User.objects.filter(username=username,password=password).first()
if user:
# 登录成功,生成token,提供了(去找)
payload = jwt_payload_handler(user)
token=jwt_encode_handler(payload)
res['token']=token

else:
res['code']=101
res['msg']='用户名或密码错误'
return Response(res)

# until.py(名字随意)

class JWTMyUserAuthentication(BaseAuthentication):
def authenticate(self, request):
token=request.META.get('HTTP_AUTHORIZATION',None)
if token:
try:
payload = jwt_decode_handler(token)
print(payload)
except jwt.ExpiredSignature:

raise AuthenticationFailed('token过期')
except jwt.DecodeError:

raise AuthenticationFailed('token认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token不合法')
else:
raise AuthenticationFailed('token没有携带')

user=User.objects.get(pk=payload.get('user_id'))
# user=User(id=payload.get('user_id'),username=payload.get('username'))
# 优化,减少数据库压力()
# user={'id':payload.get('user_id'),'username':payload.get('username')}
return (user, token)

多方式登录(5星)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
1 使用用户名、邮箱、手机号 + 密码都能登录成功

2 可以使用auth的User表,也可以自定义用户表

3 扩写auth的User表(没有迁移之前使用--->继承AbstractUser)
硬写:
3.1 删库(要有备份)
3.2 删除迁移记录(app的迁移记录,auth、admin的迁移记录(源码中))



# views.py

class Userinfo(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
response = {'code':1000, 'msg':'登录成功', 'token':None}
ser = UserModelSerializer(data=request.data)
if ser.is_valid():
response['token'] = ser.context.get('token')
else:
response['code'] = 1001
response['msg'] = ser.errors
return Response(response)


# serializer.py(名字随意)

class UserModelSerializer(serializers.ModelSerializer):
username = serializers.CharField()
class Meta:
model = Userinfo
fields = ['username', 'password']

def validate(self, attrs):
user = self._get_user(attrs)
token = self._get_token(user)
self.context['token'] = token
return attrs

def _get_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if re.match('^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$', username):
user = Userinfo.objects.filter(phone=username).first()
elif re.match('^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', username):
user = Userinfo.objects.filter(email=username).first()
else:
user = Userinfo.objects.filter(username=username).first()
if user:
if user.check_password(password):
return user
else:raise ValidationError('用户名或密码错误')
else:
raise ValidationError('用户名或密码错误')


def _get_token(self, user):
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return token


# until.py(名字随意)

class User_jwt_authentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
# print(request.META)
token = request.META.get('HTTP_AUTHORIZATION')
# return None
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期,请重新登录')
except jwt.DecodeError:
raise AuthenticationFailed('认证错误')
except jwt.InvalidTokenError:
raise AuthenticationFailed('认证不合法')
user = self.authenticate_credentials(payload)
return (user, token)

def authenticate_credentials(self, payload):
username = payload.get('username')
if not username:
raise AuthenticationFailed('用户名不存在')

try:
user = Userinfo.objects.filter(username=username).first()
except Userinfo.DoesNotExist:
raise AuthenticationFailed('无效的签名')

if not user.is_active:
raise AuthenticationFailed('用户已注销')

return user

版本要求

  • Linux Center OS 7

安装Docker

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
#Docker 要求 CentOS 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。通过 uname -r 命令查看你当前的内核版本
uname -r

#确保 yum 包更新到最新
yum update

#如果安装过旧版本的话,卸载旧版本
yum remove docker docker-common docker-selinux docker-engine

#安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2

#设置yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

#查看所有仓库中所有docker版本,并选择特定版本安装
yum list docker-ce --showduplicates | sort -r

#安装Docker
yum install docker-ce

#如果你想安装自定义版本可以输入以下命令:
yum install docker-ce-17.12.0.ce

#启动并加入开机启动
systemctl start docker
systemctl enable docker

#验证安装是否成功(有client和service两部分表示docker安装启动都成功了)
docker version

安装docker-compose

1
2
3
4
5
curl -L https://get.daocloud.io/docker/compose/releases/download/1.26.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
#查看docker-compose版本
docker-compose --version

创建自动化容器部署

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
cd ~
mkdir docker
cd docker
vi docker-compose.yml

#docker-compose.yml文件内容如下
version: '3'
services:
yapi-web:
image: jayfong/yapi:latest
container_name: yapi-web
ports:
- 3000:3000
environment:
- YAPI_ADMIN_ACCOUNT=123123123@qq.com #用户名邮箱
- YAPI_ADMIN_PASSWORD=admin123 #密码
- YAPI_CLOSE_REGISTER=true
- YAPI_DB_SERVERNAME=yapi-mongo
- YAPI_DB_PORT=27017
- YAPI_DB_DATABASE=yapi
- YAPI_MAIL_ENABLE=false
- YAPI_LDAP_LOGIN_ENABLE=false
- YAPI_PLUGINS=[]
depends_on:
- yapi-mongo
links:
- yapi-mongo
restart: unless-stopped
yapi-mongo:
image: mongo:latest
container_name: yapi-mongo
volumes:
- ./data/db:/data/db
expose:
- 27017
restart: unless-stopped

开始部署容器

1
2
3
docker-compose up -d	# 启动
docker-compose stop # 停止
docker-compose rm # 删除

内容概要

  • 我们自己写一个简易版本的web框架(代码无需掌握,重点在于理解思路)
  • django框架

内容详细

Django生命周期流程图

img

软件开发架构

1
2
3
cs架构
bs架构
# 本质bs也是cs

纯手撸web框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# HTTP协议
"""
网络协议
HTTP协议 数据传输是明文
HTTPS协议 数据传输是密文
websocket协议 数据传输是密文


四大特性
1.基于请求响应
2.基于TCP、IP作用于应用层之上的协议
3.无状态
4.短/无链接

数据格式
请求首行
请求头
\r\n
请求体

响应状态码
1XX
2XX 200
3XX
4XX 403 404
5XX 500
"""
# 如何做到后缀的不同返回不同的内容
# 拿到用户输入的后缀 做判断


# 不足之处
1.代码重复(服务端代码所有人都要重复写)
2.手动处理http格式的数据 并且只能拿到url后缀 其他数据获取繁琐(数据格式一样处理的代码其实也大致一样 重复写)
3.并发的问题

借助于wsgiref模块

1
2
3
4
5
6
"""
urls.py 路由与视图函数对应关系
views.py      视图函数(后端业务逻辑)
templates文件夹                    专门用来存储html文件
"""
# 按照功能的不同拆分之后 后续添加功能只需要在urls.py书写对应关系然后取views.py书写业务逻辑即可

动静态网页

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
"""
静态网页
页面上的数据是直接写死的 万年不变
动态网页
数据是实时获取的
eg:
1.后端获取当前时间展示到html页面上
2.数据是从数据库中获取的展示到html页面上
"""

# 动态网页制作
import datetime
def get_time(env):
current_time = datetime.datetime.now().strftime('%Y-%m-%d %X')
# 如何将后端获取到的数据"传递"给html文件?
with open(r'templates/03 mytime.html','r',encoding='utf-8') as f:
data = f.read()
# data就是一堆字符串
data = data.replace('dwadasdsadsadasdas',current_time) # 在后端将html页面处理好之后再返回给前端
return data

# 将一个字典传递给html文件 并且可以在文件上方便快捷的操作字典数据
from jinja2 import Template
def get_dict(env):
user_dic = {'username':'jason','age':18,'hobby':'read'}
with open(r'templates/04 get_dict.html','r',encoding='utf-8') as f:
data = f.read()
tmp = Template(data)
res = tmp.render(user=user_dic)
# 给get_dict.html传递了一个值 页面上通过变量名user就能够拿到user_dict
return res

# 后端获取数据库中数据展示到前端页面

模版语法之Jinja2模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pip3 install jinja2
"""模版语法是在后端起作用的"""

# 模版语法(非常贴近python语法)
{{ user }}
{{ user.get('username')}}
{{ user.age }}
{{ user['hobby'] }}


{% for user_dict in user_list %}
<tr>
<td>{{ user_dict.id}}</td>
<td>{{ user_dict.username}}</td>
<td>{{ user_dict.password}}</td>
<td>{{ user_dict.hobby}}</td>
</tr>
{% endfor%}

自定义简易版本web框架请求流程图

1
2
3
4
5
6
"""
wsgiref模块
1.请求来的时候解析http格式的数据 封装成大字典
2.响应走的时候给数据打包成符合http格式 再返回给浏览器

"""

python三大主流web框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"""
django
特点:大而全 自带的功能特别特别特别的多 类似于航空母舰
不足之处:
有时候过于笨重

flask
特点:小而精 自带的功能特别特别特别的少 类似于游骑兵
第三方的模块特别特别特别的多,如果将flask第三方的模块加起来完全可以盖过django
并且也越来越像django
不足之处:
比较依赖于第三方的开发者

tornado
特点:异步非阻塞 支持高并发
牛逼到甚至可以开发游戏服务器
不足之处:
你不会~
"""

注意事项

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
# 如何让你的计算机能够正常的启动django项目
1.计算机的名称不能有中文
2.一个pycharm窗口只开一个项目 # 不要文件夹套文件夹
3.项目里面所有的文件也尽量不要出现中文
4.python解释器尽量使用3.4~3.6之间的版本
(如果你的项目报错 你点击最后一个报错信息
去源码中把逗号删掉)

# django版本问题
1.X 2.X 3.X(最新版本)

1.X和2.X本身差距也不大
公司之前用的1.8 有一些项目用的2.0

django版本图

# django安装
pip3 install django==1.11.11
如果已经安装了其他版本 无需自己卸载
直接重新装 会自动卸载安装新的

如果报错 看看是不是timeout 如果是 那么只是网速波动
重新安装即可

验证是否安装成功的方式1
终端输入django-admin看看有没有反应

django基本操作

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
# 命令行操作
# 1.创建django项目
"""
你可以先切换到对应的D盘 然后再创建
"""
django-admin startproject mysite

mysite文件夹
manage.py
mysite文件夹
__init__.py
settings.py
urls.py
wsgi.py
# 2.启动django项目
"""
一定要先切换到项目目录下
cd /mysite
"""
python3 manage.py runserver
# http://127.0.0.1:8000/

# 3.创建应用
"""
Next, start your first app by running python manage.py startapp [app_label].
"""
python manage.py startapp app01
应用名应该做到见名知意
user
order
web
...
但是我们教学统一就用app01/02/03/04

有很多文件

# pycharm操作
# 1 new project 选择左侧第二个django即可

# 2 启动
1.还是用命令行启动
2.点击绿色小箭头即可

# 3 创建应用
1.pycharm提供的终端直接输入完整命令
2.pycharm
tools
run manage.py task提示(前期不要用 给我背完整命令)
# 4 修改端口号以及创建server
edit confi....

应用

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
"""
django是一款专门用来开发app的web框架

django框架就类似于是一所大学(空壳子)
app就类似于大学里面各个学院(具体功能的app)
比如开发淘宝
订单相关
用户相关
投诉相关
创建不同的app对应不同的功能

选课系统
学生功能
老师功能

一个app就是一个独立的功能模块
"""
***********************创建的应用一定要去配置文件中注册**********************
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config', # 全写
'app01', # 简写
]
# 创建出来的的应用第一步先去配置文件中注册 其他的先不要给我干
ps:你在用pycharm创建项目的时候 pycharm可以帮你创建一个app并且自动注册
***********************************************************************

主要文件介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-mysite项目文件夹
--mysite文件夹
---settings.py 配置文件
---urls.py 路由与视图函数对应关系(路由层)
---wsgi.py wsgiref模块(不考虑)
--manage.py django的入口文件
--db.sqlite3 django自带的sqlite3数据库(小型数据库 功能不是很多还有bug)
--app01文件夹
---admin.py django后台管理
---apps.py 注册使用
---migrations文件夹 数据库迁移记录
---models.py 数据库相关的 模型类(orm)
---tests.py 测试文件
---views.py 视图函数(视图层)

命令行与pycharm创建的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1 命令行创建不会自动有templatew文件夹 需要你自己手动创建而pycharm会自动帮你创建并且还会自动在配置文件中配置对应的路径
# pycharm创建
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
}
]
# 命令行创建
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
}
]
"""
也就意味着你在用命令创建django项目的时候不单单需要创建templates文件夹还需要去配置文件中配置路径
'DIRS': [os.path.join(BASE_DIR, 'templates')]
"""

django小白必会三板斧

1
2
3
4
5
6
7
8
9
10
11
12
"""
HttpResponse
返回字符串类型的数据

render
返回html文件的

redirect
重定向
return redirect('https://www.mzitu.com/')
return redirect('/home/')
"""

内容概要

  • 用户认证模块auth
  • auth模块补充
  • auth_user表扩展字段

内容详细

auth模块

1
2
3
4
5
6
7
8
主要是用来做用户相关的功能
注册 登录 验证 修改密码 注销

访问admin需要管理员账号
该账号数据均来源于数据库迁移之后生成的auth_user表

如何创建admin管理员账号
createsuperuser

具体操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.contrib import auth

# 校验用户名密码是否正确
auth.authenticate(request,username=username,password=password)
# 保存用户状态
auth.login(request,user_obj)
# 查看用户是否登录
request.user.is_authenticated()
# 获取用户对象
request.user
# 校验原密码是否正确
request.user.check_password()
# 修改密码
request.user.set_password()
request.user.save()
# 校验是否登录装饰器
from django.contrib.auth.decorators import login_required
"""
跳转全局配置
LOGIN_URL = '/lg/'
跳转局部配置
@login_required(login_url='/lg/')
"""

auth模块补充

1
2
3
4
5
6
7
8
9
10
1.用户注册
from django.contrib.auth.models import AbstractUser,User
# 操作auth_user写入数据不能使用create方法 密码不会自动加密
# User.objects.create(username=username,password=password,email=email)
# 创建普通用户
User.objects.create_user(username=username,password=password,email=email)
# 创建超级用户
User.objects.create_superuser(username=username, password=password, email=email)
2.用户注销
auth.logout(request)

auth模块用户表扩展字段

1
2
3
4
5
6
7
8
9
10
11
12
在auth_user表的基础之上还想增加额外的字段 并且还可以使用auth模块所有的功能
# 配合文件配置
# 告诉django使用我们自己定义的表来取代auth_user表
AUTH_USER_MODEL = 'app01.Userinfo' # 应用名.表名

# 一对一表关联(了解)
# 面向对象继承(掌握)
from django.contrib.auth.models import AbstractUser
class Userinfo(AbstractUser):
# 扩展AbstractUser表中没有的字段
phone = models.BigIntegerField()
info = models.CharField(max_length=255)

小结

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
1:校验用户名与密码

auth.authenticate(username=username, ...)

2:保存用户信息
auth.login(request, user_obj)

3:判断用户是否登录
request.user.is_authenticated()

4:装饰器
form django.contrib.auth.decorators import login_required
局部:@login_required(login_url='/login/') (灵活)
全局:配置--->LOGIN_URL = '/login/' (省事)

5:验证原密码(修改密码)
request.user.check_password(原密码)

6:修改密码(两步)
request.user.set_password(新密码)
request.user.save()

7:创建用户
  命令行创建:createsupperuser(超级用户)(管理一般不会使用逻辑代码创建)
from django.contrib.auth.models import User
  User.objexts.create()(创建的用户密码是明文)(不推荐)
User.objexts.create_user()(普通用户)

内容概要

  • Ajax异步提交

  • 前后端传输数据的编码格式

  • Ajax发送json格式数据

    1
    2
    3
    4
    5
    6
    7
    # python
    import json
    json.dumps
    json.loads
    # js
    JSON.stringfy
    JSON.parse
  • Ajax发送文件数据

  • Ajax结合sweetalert插件实现删除的二次确认

内容详细

Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
异步提交 局部刷新
"""
同步:提交任务之后原地等待任务结果期间不做任何事
异步:提交任务之后不原地等待任务结果 等任务产生结果通过异步回调机制
"""
前戏页面
博客园注册页面
github注册页面

能够发送网络请求的方法
a get请求
form表单 get请求、post请求
ajax get请求、post请求

ajax不是一门独立的语言 而是一个功能
能够实现ajax功能的书写方式有很多
eg:原生js代码 jQuery封装代码 组件框架代码...
我们这里学习jQuery版本

ajax前戏
需求:保证页面不刷新的情况下完成数字的和

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
// ajax发送给后端
$.ajax({
url:'', // 控制提交地址 规律与form标签的action参数一致
type:'post', // 控制请求方式
data:{'i1':i1Val,'i2':i2Val}, // 发送的数据
success:function (args) {
// 异步回调函数
{#alert(args)#}
// 查看input框并且写入数据
$('#i3').val(args)
}
})

参数补充

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
contentType
前后端数据传输格式主要有三种
application/x-www-form-urlencoded
multipart/form-data
application/json
朝后端发送数据的时候针对不同的方式后端有不同的处理措施

# form表单默认是urlencoded编码格式
数据格式username=jason&password=234
django后端针对符合urlencoded格式的数据会自动解析并封装到request.POST中
django后端针对form-data格式的数据文件自动封装到request.FILES中 普通的符合urlencoded还是封装到request.POST中

# ajax默认是urlencoded编码格式
1.ajax发送json格式数据
django后端不做任何处理 直接以二进制形式存放在request.body中
data
contentType
2.ajax发送文件数据
$('#d1').on('click',function () {
// 1.先生成一个内置对象
let formDataObj = new FormData();
// 2.添加符合urlencoded格式的普通键值对数据
formDataObj.append('name','jason');
formDataObj.append('password',123);
// 3.添加文件数据
formDataObj.append('myfile',$('#myfile')[0].files[0]);
// ajax发送给后端
$.ajax({
url:'', // 控制提交地址 规律与form标签的action参数一致
type:'post', // 控制请求方式
data:formDataObj, // 发送的数据

// 额外指定两个参数
contentType:false, // 不采用任何编码 后端能够直接识别formdata对象
processData:false, // 不处理formdata对象 直接发送即可

success:function (args) {
}
})
});
dataType
django后端如果使用JsonResponse给回调函数返回json格式字符串
回调函数会自动反序列成js中的自定义对象
django后端不使用JsonResponse而是自己通过json模块序列化的数据 回调函数接收到之后不会自动反序列

基于ajax实现二次确认

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script>
$('.del_link').on('click',function (a) {
var $aEle = $(this);
var deleteId = $(this).attr('delete_id');
// 提示二次确认
res = confirm('你确定真的要删吗?');
// 判断是否删除
if (res){
// 发送ajax请求
$.ajax({
url:"/book_delete/" + deleteId,
type:'get',
data:{},
success:function (args) {
// 1.页面刷新 不推荐使用
window.location.reload();
// 2.DOM操作 正规 用户体验好
$aEle.parent().parent().remove()
}
})
}else{
alert('怂逼不敢删啦')
}
})
</script>

cookie与session

由来及简介

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
HTTP协议四大特性
1.基于请求响应
2.基于TCP、IP作用于应用层之上
3.无连接
4.无状态

基于HTTP协议的通信无法记录客户端状态
但是现在很多软件都需要记录用户的状态 为了解决这个问题
发明了cookie session等一系列的技术

cookie
保存在客户端浏览器上面的键值对数据
"""
eg:当用户登录成功之后 浏览器保存用户的关键信息
以后访问的时候浏览器自动发送关键信息从而实现身份识别

关键型数据直接保存在浏览器上不安全
"""
session
保存在服务器上面的键值对数据(数据类型不固定)
"""
eg:当用户登录成功之后 服务端返回给浏览器一个随机字符串
之后访问都将随机字符串发送给服务端
服务端内部做比对
"""
# session需要依赖于cookie才可以工作
客户端浏览器可以保存服务端发送过来的cookie数据也可以选择拒绝

cookie操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
视图函数返回的HttpResponse对象
return HttpResponse()
return render()
return redirect()
...


obj = HttpResponse()
return obj
obj = render()
return obj
obj = redirect()
return obj

# 用户登录
设置cookie
obj = HttpResponse("登录成功")
obj.set_cookie('name','jason')
获取cookie
request.COOKIES.get('name')
删除cookie
obj.delete_cookie("name")

session操作

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
session设置
request.session['name'] = 'jason'
"""
1.自动产生一个随机字符串
2.将随机字符串和数据存入django_session表中
3.将随机字符串返回给客户端浏览器保存
"""

session读取
request.session.get('name')
"""
1.客户端请求中获取随机字符串
2.拿着随机字符串去django_session表中比对
3.如果比对成功获取对应的数据并且解析放到request.session中
"""
# django session默认的过期时间14d 可以人为修改


# 删除当前会话的所有Session数据
request.session.delete() # 只删客户端浏览器
  
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush() # 客户端浏览器和服务端都删


# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。

内容概要

  • django中间件
  • csrf跨站请求伪造

内容详细

django中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
类似于是django的门户 请求来的时候和响应走的时候都必须经过它

配置文件中
MIDDLEWARE = [
...
]
# django默认有七个中间件
'''每个中间件你可以简单的理解为具有不同的功能'''
# django中间件有五个我们需要知道的方法
需要掌握
process_request
process_response
需要了解
process_view
process_exception
process_template_response
# django支持用户自定义中间件
django中间件可以用于编写全局相关的功能
eg:全局身份校验 全局防爬校验

中间件使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.utils.deprecation import MiddlewareMixin


class MyMiddleware1(MiddlewareMixin):
def process_request(self,request):
print('from 自定义MyMiddleware1 process_request方法')
# 1.请求来的时候会依次(从上往下)执行配置文件中注册了的中间件里面的process_request方法 如果没有直接跳过
# 2.该方法如果直接返回了HttpResponse对象那么请求不再继续往下而是直接原路返回


def process_response(self,request,response):
print('from 自定义MyMiddleware1 process_response方法')
# 1.响应走的时候从下往上依次执行注册了的中间件里面的process_response方法如果没有则直接跳过
# 2.该方法需要将形参response返回 该response其实就是视图函数返回给浏览器的数据
# 3.该方法还可以拦截返回给浏览器的数据 并且还支持自定义返回内容

"""
当process_request自己返回HttpResponse对象之后
响应是从同级别的process_response依次返回
而不是所有的process_response
"""

了解方法

1
2
3
4
5
6
7
8
9
10
11
process_view
路由匹配成功之后执行视图函数之前自动触发
self,request,view_name,*args,**kwargs

process_exception
当视图函数报错之后自动执行
self,request,exception

process_template_response
self,request,response
图函数返回的对象有一个response()方法(或者表明该对象是一个TemplateResponse对象或等价方法)

csrf跨站请求伪造

1
2
3
4
5
6
7
8
9
10
11
钓鱼网站

理论:
做一个与正规网站一模一样的界面 用户在上面操作
比如转账
转账请求确实是发送给了正规的网站后台只不过收款人变成了
钓鱼网站指定的账户
原理:
获取用户输入的表单内
给用户展示了一个没有name属性的框
自己偷偷的写了一个既有name又有value的隐藏框

csrf使用

1
2
3
4
5
6
7
8
9
10
11
12
13
1.form表单
表单中直接书写下列的语句即可
{% csrf_token %}

2.ajax
方式1:借助于{% csrf_token %}生成的标签(不推荐)
$("[name = 'csrfmiddlewaretoken']").val()

方式2:借助于模板语法直接获取
'{{ csrf_token }}'

方式3:借助于官方提供的js文件自动获取
js文件导入即可

csrf相关装饰器

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
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# csrf_exempt 局部不校验csrf
# csrf_protect 局部校验csrf

# @csrf_protect
# @csrf_exempt
def index(request):
if request.method == 'POST':
username = request.POST.get('username')
money = request.POST.get('money')
target_user = request.POST.get('target_user')
print('%s 给 %s 转了 %s 元钱'%(username,target_user,money))
return render(request,'index.html')

from django.utils.decorators import method_decorator

from django import views
# @method_decorator(csrf_protect,name='post') # 第二种 行
# @method_decorator(csrf_exempt,name='post') # 第二种 不行
class MyLogin(views.View):
# @method_decorator(csrf_protect) # 第三种 所有的方法都会加上该功能
@method_decorator(csrf_exempt) # 第三种 所有的方法都会加上该功能
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)

def get(self,request):
return HttpResponse('from get')

# @method_decorator(csrf_protect) # 第一种 行
# @method_decorator(csrf_exempt) # 第一种 不行
def post(self,request):
return HttpResponse('from post')

"""
csrf_exempt该装饰器在CBV中只能给dispatch装才能生效
"""

内容概要

  • 查询关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    MySQL
    select
    from
    where
    group by
    having
    order by
    distinct
    limit
    regexp
    # SQL语句内也支持写流程控制
    Django ORM
  • 神奇的双下线查询

  • 多表查询

    1
    2
    3
    4
    5
    子查询
    基于对象的跨表查询
    连表操作
    基于双下划线的跨表查询
    ps:ORM远比SQL语句简单
  • 分组与聚合

  • F与Q查询

  • ORM查询优化(only与defer…)

  • ORM字段补充

内容详细

关键字

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
   # 增     1.create()
# models.Books.objects.create(title='三国演义',price=456.23)
# models.Books.objects.create(title='水浒传',price=876.45)
# models.Books.objects.create(title='聊斋志异',price=123.69)
# models.Books.objects.create(title='草堂笔记',price=456.96)

# 查 2.all()
# res = models.Books.objects.all()
# print(res) # QuerySet对象
# print(res.query) # 只要是QuerySet对象就可以点query查看内部SQL语句

# 查 3.filter()
# res1 = models.Books.objects.filter() # pk特指当前表的主键字段
# print(res1) # QuerySet对象
# print(res1.query)
# print(res1.first()) # 获取列表中第一个数据对象
# print(res1.last()) # 获取列表中最后一个数据对象

# 4.values与5.values_list
# res2 = models.Books.objects.values('title','price')
# print(res2) # QuerySet对象 可以看成列表套字典
# print(res2.query)

# res3 = models.Books.objects.values_list('title','price')
# print(res3) # QuerySet对象 可以看成列表套元祖
# print(res3.query)

# 6.查 get() 不推荐使用
# res4 = models.Books.objects.get(pk=1)
# print(res4) # 数据对象
# res5 = models.Books.objects.get(pk=100)
# print(res5) # 数据对象
# res6 = models.Books.objects.filter(pk=100)
# print(res6) # 数据对象

# 7.取反 exclude()
# res7 = models.Books.objects.exclude(pk=1)
# print(res7) # QuerySet
# print(res7.query)


# 8.排序 order_by() 默认是升序(asc) 字段前面加负号降序(desc)
# res8 = models.Books.objects.order_by('price')
# res8 = models.Books.objects.order_by('-price')
# select * from books order by price desc,price asc;
# res8 = models.Books.objects.order_by('-price','price')
# print(res8) # QuerySet对象
# print(res8.query)


# 9.反转 reverse() 必须先有顺序才可以反转
# res9 = models.Books.objects.all()
# res9 = models.Books.objects.order_by('price').reverse()
# print(res9)

# 10.去重 distinct()
# res10 = models.Books.objects.all().distinct()
# res10 = models.Books.objects.values('title','price').distinct()
# print(res10)

# 11.计数 count()
# res11 = models.Books.objects.count()
# print(res11) # 6

# 12.判断是否有数据 exists()
# res12 = models.Books.objects.filter(pk=999).exists()
# print(res12) # False

# 13.update()
# 14.delete()

神奇的双下划线

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
  
# 查询价格大于200的书籍
# res = models.Books.objects.filter(price__gt=200)
# print(res)
# res1 = models.Books.objects.filter(price__lt=200)
# print(res1)
# res2 = models.Books.objects.filter(price__gte=456.23)
# print(res2)
# res3 = models.Books.objects.filter(price__lte=456.23)
# print(res3)

# 成员运算
# res4 = models.Books.objects.filter(price__in=(456.23,111))
# print(res4)
# 范围查询
# res5 = models.Books.objects.filter(price__range=(100,456.23))
# print(res5)
# 模糊查询
# 查询书籍名称中含有字母a的书
# res6 = models.Books.objects.filter(title__contains='a')
# print(res6) # 区分
# res7 = models.Books.objects.filter(title__icontains='a')
# print(res7) # 忽略

# 日期相关
# 查看出版月份是五月的书
# res8 = models.Books.objects.filter(publish_time__month=5)
# print(res8)
# print(res8.query)
# res9 = models.Books.objects.filter(publish_time__year=2021)
# print(res9)

外键字段增删改查

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
# 增
# models.Book.objects.create(title='三国演义',price=345.43,publish_id=1)
# models.Book.objects.create(title='红楼梦',price=678.31,publish_id=2)

# publish_obj = models.Publish.objects.filter(pk=2).first()
# models.Book.objects.create(title='三国演义',price=345.43,publish=publish_obj)
# models.Book.objects.create(title='七龙珠',price=908.43,publish=publish_obj)

# 改
# models.Book.objects.filter(pk=2).update(publish_id=1)
# models.Book.objects.filter(pk=2).update(publish=publish_obj)

# 删 级联更新级联删除
# models.Publish.objects.filter(pk=1).delete()


# 多对多
# book_obj = models.Book.objects.filter(pk=3).first()
# 绑定关系
# book_obj.authors.add(1) # 去书与作者的关系表中绑定关系
# book_obj.authors.add(1,2) # 去书与作者的关系表中绑定关系
# book_obj.authors.add(author_obj1)
# book_obj.authors.add(author_obj1,author_obj2)

# 修改关系
# book_obj.authors.set([1,])
# book_obj.authors.set([1,2])
# book_obj.authors.set([author_obj,])
# book_obj.authors.set([author_obj1,author_obj2])

# 移除关系
# book_obj.authors.remove(1)
# book_obj.authors.remove(1,2)
# book_obj.authors.remove(author_obj,)
# book_obj.authors.remove(author_obj1,author_obj2)

# 清空关系
# book_obj.authors.clear()

重要概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 多表查询
"""
正向查询

反向查询

当前查询对象是否含有外键字段
如果有就是正向
没有无则是反向
口诀:
正向查询按外键字段
多对多需要额外再加一个.all()
一对多和一对一不需要加
反向查询按表名小写
一对多与多对多
_set.all()
一对一
不需要加
"""

多表查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#####################基于对象的跨表查询#####################
# 子查询:将一张表的查询结果当做另外一条SQL语句的条件(括号括起来)
# 1.查询书籍主键为4的出版社名称
# 先查询书籍对象
# book_obj = models.Book.objects.filter(pk=4).first()
# 外键字段在书这里 所以是正向查询
# res = book_obj.publish
# print(res)
# print(res.title)
# print(res.addr)

# 2.查询书籍主键为3的作者姓名
# 先查询书籍对象
# book_obj = models.Book.objects.filter(pk=3).first()
# 外键字段在书这里 所以是正向查询
# res = book_obj.authors
# print(res) # app01.Author.None
# res = book_obj.authors.all()
# print(res) # <QuerySet [<Author: 作者对象:oscar>, <Author: 作者对象:egon>]>


# 3.查询作者jason的地址
# 先查询jason数据对象
# author_obj = models.Author.objects.filter(name='jason').first()
# 外键字段在作者这里 所以是正向查询
# res = author_obj.author_detail
# print(res)
# print(res.addr)
# print(res.phone)


# 4.查询东方出版社出版的书籍
# 先查询出版社对象
# publish_obj = models.Publish.objects.filter(title='东方出版社').first()
# 外键字段在书那里自己没有 所以是反向
# res = publish_obj.book_set
# print(res) # app01.Book.None
# res = publish_obj.book_set.all()
# print(res)


# 5.查询jason写过的书籍
# 先查询jason数据对象
# author_obj = models.Author.objects.filter(name='jason').first()
# 外键字段在书那里自己没有 所以是反向
# res = author_obj.book_set
# print(res) # app01.Book.None
# res = author_obj.book_set
# print(res) # app01.Book.None
# res = author_obj.book_set.all()
# print(res) # app01.Book.None


# 6.查询电话是120的作者
# 先查询120数据对象
# author_detail_obj = models.AuthorDetail.objects.filter(phone=120).first()
# 外键字段在作者那里本身没有 所以是反向
# res = author_detail_obj.author
# print(res)
# print(res.name)
# print(res.age)



########################基于双下滑线的跨表查询
# 1.查询书籍主键为4的出版社名称
# res = models.Book.objects.filter(pk=4).values('publish__title','publish__addr','title')
# print(res)

# 2.查询书籍主键为3的作者姓名
# res = models.Book.objects.filter(pk=3).values('authors__name')
# print(res)

# 3.查询作者jason的地址
# res = models.Author.objects.filter(name='jason').values('author_detail__addr','name','age')
# print(res)

# 4.查询北方出版社出版的书籍名称
# res = models.Publish.objects.filter(title='北方出版社').values('book__title')
# print(res)

# 5.查询jason写过的书籍
# res = models.Author.objects.filter(name='jason').values('book__title','name','age')
# print(res)

# 6.查询电话是120的作者
# res = models.AuthorDetail.objects.filter(phone=120).values('author__name','author__age','addr')
# print(res)



############进阶操作#############
# 1.查询书籍主键为4的出版社名称
# res = models.Book.objects.filter(pk=4).values('publish__title','publish__addr','title')
# print(res)
# 反向查询高阶部分
# res = models.Publish.objects.filter(book__pk=4)
# print(res)

# 2.查询书籍主键为3的作者姓名
# res = models.Book.objects.filter(pk=3).values('authors__name')
# print(res)
# 反向查询高阶部分
# res = models.Author.objects.filter(book__pk=3)
# print(res)

# 3.查询作者jason的地址
# res = models.Author.objects.filter(name='jason').values('author_detail__addr','name','age')
# print(res)
# 反向查询高阶部分
# res = models.AuthorDetail.objects.filter(author__name='jason')
# print(res)


# 查询书籍主键为3的作者的电话号码
# res = models.Book.objects.filter(pk=3).values('authors__author_detail__phone')
# print(res)

# res = models.AuthorDetail.objects.filter(author__book__pk=3).values('phone')
# print(res)

# res = models.Author.objects.filter(book__pk=3).values('author_detail__phone')
# print(res)

F查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   # F查询
from django.db.models import F
# 1.查询库存数大于卖出数的书籍
# res = models.Book.objects.filter(kucun__gt=F('maichu'))
# print(res)
# 2.将所有的书籍价格上涨100块
# res = models.Book.objects.update(price=F('price') + 100)
# print(res)
# 3.将所有的书籍名称加上"爆款"后缀
'''针对字符串不能直接拼接 需要额外导入模块操作'''
# models.Book.objects.update(title=F('title') + '爆款')
# from django.db.models.functions import Concat
# from django.db.models import Value
# ret3 = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))

Q查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   # 1.查询书名是三国演义爆款或者库存是100的书籍
'''filter()括号内可以写多个参数 逗号隔开 默认只支持and连接'''
from django.db.models import Q
# res = models.Book.objects.filter(title='三国演义爆款',kucun=100)
# print(res)
# res1 = models.Book.objects.filter(Q(title='三国演义爆款'),Q(kucun=100)) # and
# res1 = models.Book.objects.filter(Q(title='三国演义爆款')|Q(kucun=100)) # or
# res1 = models.Book.objects.filter(~Q(title='三国演义爆款')|Q(kucun=100)) # not
# print(res1.query)
'''Q进阶用法'''
# condition = input('请输入你需要按照什么字段查询数据>>>:')
# data = input('请输入你需要查询的数据名称>>>:')
# q = Q() # 生成一个Q对象
# q.children.append((condition,data))
# res = models.Book.objects.filter(q)
# print(res)
q = Q()
q.connector = 'or' # 可以修改连接条件
q.children.append(('title__contains','三'))
q.children.append(('price__gt',200)) # 可以添加多个条件 并且也是and关系
res = models.Book.objects.filter(q)
print(res)
print(res.query)

事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
1.事务四大特性 ACID
原子性
一致性
独立性
持久性
2.数据库设计三大范式
课下百度搜索自己概括

MySQL
start transcation
commit
rollback
"""
from django.db import transaction
try:
with transaction.atomic():
# 创建一条订单数据
models.Order.objects.create(num="110110111", product_id=1, count=1)
# 能执行成功
models.Product.objects.filter(id=1).update(kucun=F("kucun") - 1, maichu=F("maichu") + 1)
except Exception as e:
print(e)

执行原生SQL语句

1
2
3
4
res = models.Book.objects.raw('select * from app01_book')
for i in res:
print(i)

模型层字段及参数

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
models.AutoField(primary_key=True)
models.CharField(max_length=32) # varchar(32)
models.IntergeField() # int()
models.DateField() # date
models.DateTimeField() # datetime
auto_now
auto_now_add
models.DecimalField() # float()
models.BooleanField()
给这个字段传布尔值会自动转换成数字01
一般用在状态二选一
TextField()
存储大段文本(bbs项目会使用)
EmailField()
存储邮箱格式数据
FileField()
存储数据路径(bbs项目会使用)

"""自定义字段"""
from django.db.models import Field


class MyCharField(Field):
def __init__(self,max_length,*args,**kwargs):
self.max_length = max_length
super().__init__(max_length=max_length,*args,**kwargs)

def db_type(self, connection):
return 'char(%s)'%self.max_length

常见参数

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
max_length
varbose_name
default
null
auto_now
auto_now_add
to
unique
db_index
choices

# choices参数
创建用户表
性别
两到三种状态
学历
也是有限个
在职状态
也是有限个
婚姻
也是有限个
...
"""针对某个字段可以列举完全的情况 一般都是使用choices参数"""
class Server(models.Model):
host = models.CharField(max_length=32)

status_choices = (
(1,'在线'),
(2,'待上线'),
(3,'宕机'),
(4,'待上架')
)
status = models.IntegerField(choices=status_choices)

desc_choices = (
('哈哈','哈哈哈哈哈哈'),
('呵呵','呵呵呵呵呵呵'),
('嘿嘿','嘿嘿嘿嘿嘿嘿'),
('嘻嘻','嘻嘻嘻嘻嘻嘻'),
)
desc = models.CharField(max_length=32,choices=desc_choices)

# 获取对应关系
.get_字段名_display()

ORM查询优化

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
# 惰性查询
用不到的数据即使写了orm语句也不会执行
1.only与defer
2.select_related与prefech_related


# 1.only与defer

# res = models.Book.objects.values('title') # 列表套字典

# res1 = models.Book.objects.only('title') # 列表套对象
# print(res1)
# for i in res1:
# # print(i.title)
# print(i.price)
"""
only括号内写什么字段
生成的对象就含有对应的属性 在查找该属性的时候不再走数据库查询
但是一旦查找括号内没有的字段属性 则每次都会走数据库查询

"""
# res1 = models.Book.objects.defer('title')
# # print(res1) # 列表套对象
# for i in res1:
# # print(i.title)
# print(i.title)
"""
defer与only刚好相反
生成的对象就不含有对应的属性 在查找该属性的时候需要每次走数据库查询
但是一旦查找括号内没有的字段属性 则不需要走数据库查询
"""
# res = models.Book.objects.filter(pk=3).first()
# print(res.publish.title)


# res = models.Book.objects.select_related('publish')
# for i in res:
# print(i.publish.title)
"""
select_related相当于连表操作
先将models后面的表和括号内外键字段关联的表连接起来
之后一次性把所有的数据封装到数据对象中
"""
res = models.Book.objects.prefetch_related('publish')
for i in res:
print(i.publish.title)
"""
prefetch_related相当于子查询
先查询models后面的表的所有的数据
然后将括号内关联的表的数据全部查询出来
之后整合到一起
"""

常用及非常用字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
AutoField(Field)
- int自增列,必须填入参数 primary_key=True

BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True

注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models

class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)

class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)

SmallIntegerField(IntegerField):
- 小整数 -3276832767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 032767
IntegerField(Field)
- 整数列(有符号的) -21474836482147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 02147483647

BigIntegerField(IntegerField):
- 长整型(有符号的) -92233720368547758089223372036854775807

BooleanField(Field)
- 布尔值类型

NullBooleanField(Field):
- 可以为空的布尔值

CharField(Field)
- 字符类型
- 必须提供max_length参数, max_length表示字符长度

TextField(Field)
- 文本类型

EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制

IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹

FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)

DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD

TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]

DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

FloatField(Field)
- 浮点型

DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度

BinaryField(Field)
- 二进制类型

对应关系

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
对应关系:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',

ORM字段参数

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

用于表示某个字段可以为空。

#### **unique**

如果设置为unique=True 则该字段在此表中必须是唯一的 。

#### **db_index**

如果db_index=True 则代表着为此字段设置索引。

#### **default**

为该字段设置默认值。

### DateField和DateTimeField

#### auto_now_add

配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。

#### auto_now

配置上auto_now=True,每次更新数据记录的时候会更新该字段。



null 数据库中字段是否可以为空
db_column 数据库中字段的列名
db_tablespace
default 数据库中字段的默认值
primary_key 数据库中字段是否为主键
db_index 数据库中字段是否可以建立索引
unique 数据库中字段是否可以建立唯一索引
unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
unique_for_year 数据库中字段【年】部分是否可以建立唯一索引

verbose_name Admin中显示的字段名称
blank Admin中是否允许用户输入为空
editable Admin中是否可以编辑
help_text Admin中该字段的提示信息
choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)

error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
如:{'null': "不能为空.", 'invalid': '格式错误'}

validators 自定义错误验证(列表类型),从而定制想要的验证规则
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
max_length=32,
error_messages={
'c1': '优先错信息1',
'c2': '优先错信息2',
'c3': '优先错信息3',
},
validators=[
RegexValidator(regex='root_\d+', message='错误了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
EmailValidator(message='又错误了', code='c3'), ]
)

关系字段

ForeignKey

外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 ‘一对多’中’多’的一方。

ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。

to

设置要关联的表

to_field

设置要关联的表的字段

反向操作时,使用的字段名,用于代替原反向查询时的’表名_set’。

例如:

1
2
3
4
5
6
class Classes(models.Model):
name = models.CharField(max_length=32)

class Student(models.Model):
name = models.CharField(max_length=32)
theclass = models.ForeignKey(to="Classes")

当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:

1
models.Classes.objects.first().student_set.all()

当我们在ForeignKey字段中添加了参数 related_name 后,

1
2
3
class Student(models.Model):
name = models.CharField(max_length=32)
theclass = models.ForeignKey(to="Classes", related_name="students")

当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:

1
models.Classes.objects.first().students.all()

反向查询操作时,使用的连接前缀,用于替换表名。

on_delete

  当删除关联表中的数据时,当前表与其关联的行的行为。

  models.CASCADE
  删除关联数据,与之关联也删除

  models.DO_NOTHING
  删除关联数据,引发错误IntegrityError

  models.PROTECT
  删除关联数据,引发错误ProtectedError

  models.SET_NULL
  删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)

  models.SET_DEFAULT
  删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)

  models.SET

  删除关联数据,
  a. 与之关联的值设置为指定值,设置:models.SET(值)
  b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

1
2
3
4
5
6
7
8
9
def func():
return 10

class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id"
on_delete=models.SET(func)
)

db_constraint

是否在数据库中创建外键约束,默认为True。

OneToOneField

一对一字段。

通常一对一字段用来扩展已有字段。

一对一的关联关系多用在当一张表的不同字段查询频次差距过大的情况下,将本可以存储在一张表的字段拆开放置在两张表中,然后将两张表建立一对一的关联关系。

1
2
3
4
5
6
7
8
class Author(models.Model):
name = models.CharField(max_length=32)
info = models.OneToOneField(to='AuthorInfo')


class AuthorInfo(models.Model):
phone = models.CharField(max_length=11)
email = models.EmailField()

to

设置要关联的表。

to_field

设置要关联的字段。

on_delete

同ForeignKey字段。

ManyToManyField

用于表示多对多的关联关系。在数据库中通过第三张表来建立关联关系

to

设置要关联的表

同ForeignKey字段。

同ForeignKey字段。

symmetrical

仅用于多对多自关联时,指定内部是否创建反向操作的字段。默认为True。

举个例子:

1
2
3
class Person(models.Model):
name = models.CharField(max_length=16)
friends = models.ManyToManyField("self")

此时,person对象就没有person_set属性。

1
2
3
class Person(models.Model):
name = models.CharField(max_length=16)
friends = models.ManyToManyField("self", symmetrical=False)

此时,person对象现在就可以使用person_set属性进行反向查询。

through

在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。

但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。

through_fields

设置关联的字段。

db_table

默认创建第三张表时,数据库中表的名称。