用习惯了linux之后,总是喜欢玩root用户了,主要是方便且可以为所欲为。碰到windows系统也一样,必须用administrator账号,这样才能感觉自己是系统的主人而不是用户。
以管理员身份运行CMD终端
激活administrator账号
net user administrator /active:yes
配置administrator密码
net user administrator 123456
切换administrator
]]>点击左下角主菜单按钮>>>点击账号>>>切换administrator账号
mariadb安装后默认是不需要密码的,mysql -u root -p 回车即可
备注:如果刚安装完,执行mysql -uroot -p 回车提示输入密码,是因为还没启动mariadb服务,systemctl status mariadb查看一下是否没启动服务。systemctl enable –now mariadb 启动mariadb并设置开机启动。
编译安装的执行这句:
/usr/local/mysql/bin/mysql_secure_installation
yum安装的执行这句:
/usr/bin/mysql_secure_installation
然后按照提示选择即可,默认全选y
mysqladmin -u root -p password 新密码
注意,这里前面的password是告诉 mysqladmin 你要做的事是设置密码,后面跟的是你要设置的密码。更加注意的是,这个配置方法的指令是明文的,要记得删除history记录,否则容易密码泄露。
这条命令输入后,会出现一个要求输入密码的提示,这是在要求 mariadb 当前的密码,而你还没有设置过密码,所以这里直接回车。到此密码就设置好了。
mairadb默认一开始是没有密码的,mysql -uroot 直接登录,然后修改密码即可
[root@localhost ~]# mysql -urootMariaDB [(none)]> use mysql;MariaDB [mysql]> SET password=PASSWORD('newpassward');MariaDB [mysql]> FLUSH PRIVILEGES;
systemctl stop mariadb ==>停止mariadb数据库
mysqld_safe –skip-grant-table & ==>进入单机模式
mysql
use mysql; ==>进入mysql库
update user set password=password(新密码) where user=’root’ and host=’localhost’; ==>设置新密码
flush privileges; ==>刷新
新开窗口 mysqladmin -uroot -p shutdown ==>新密码测试关掉数据库,成功关闭就证明修改成功
systemctl restart mariadb ==>重启服务
skip_name_resolve = ON
vim /etc/my.cnf
[mysqld]
skip-grant-tables ==> 添加这一行,然后保存,重启服务,,mysql可以直接进去修改了。同上。
]]>sudo pacman -S samba nautilus-share manjaro-settings-samba
cp /etc/samba/smb.conf /etc/samba/smb.conf.bak
sudo vim /etc/samba/smb.conf
[global]
workgroup = WORKGROUP
dns proxy = no
log file = /var/log/samba/%m.log
max log size = 1000
client min protocol = SMB2
server role = standalone server
passdb backend = tdbsam
obey pam restrictions = yes
unix password sync = yes
passwd program = /usr/bin/passwd %u
passwd chat = NewUNIXpassword %n\n ReTypenewUNIX*password %n\n passwd:allauthenticationtokensupdated*successfully
pam password change = yes
map to guest = Bad Password
usershare allow guests = yes
name resolve order = lmhosts bcast host wins
security = user
guest account = nobody
usershare path = /var/lib/samba/usershare
usershare max shares = 100
usershare owner only = yes
force create mode = 0070
force directory mode = 0070[homes] —-> 这一段要注释掉,否则会暴露家目录
comment = Home Directories
browseable = no
read only = yes
create mask = 0700
directory mask = 0700
valid users = %S[printers]
comment = All Printers
browseable = no
path = /var/spool/samba
printable = yes
guest ok = no
read only = yes
create mask = 0700[print$]
comment = Printer Drivers
path = /var/lib/samba/printers
browseable = yes
read only = yes
guest ok = no[share] # 分享目录,可自定义名字
path = /home/unknown/samba_share # 分享实际目录
browseable = yes # 共享的目录是否让所有人可见
read only = no # 是否只读
create mask = 0700 # 客户端上传文件的默认权限
directory mask = 0700 # 客户端创建目录的默认权限
writable = yes # 是否可写
guest ok = yes #是否允许匿名(guest)访问,等同于public
sudo smbpasswd -a unknown
sudo pdbedit -L
sudo smbclient -L 127.0.0.1
mkdir /home/unknown/samba_share
chmod 777 -R /home/unknown/samba_share
sudo systemctl restart smb nmb
testparm
smbclient //192.168.0.10/share
\192.168.0.10
mount -t cifs -o username=xxx //192.168.0.10/share /mnt/tmp
目前samba有三种后台:smbpasswd、tdbsam和ldapsam。sam应该是security account manager(安全账户管理)的简写。
1.smbpasswd:该方式是使用smb自己的工具smbpasswd来给系统用户(真实
用户或者虚拟用户)设置一个Samba密码,客户端就用这个密码来访问Samba的资源。smbpasswd文件默认在/etc/samba目录下,不过有时候要手工建立该文件。
2.tdbsam:该方式则是使用一个数据库文件来建立用户数据库。数据库文件叫passdb.tdb,默认在/etc/samba目录下。passdb.tdb用户数据库可以使用smbpasswd –a来建立Samba用户,不过要建立的Samba用户必须先是系统用户。我们也可以使用pdbedit命令来建立Samba账户。pdbedit命令的参数很多,我们列出几个主要的。
pdbedit –a username:新建Samba账户。
pdbedit –x username:删除Samba账户。
pdbedit –L:列出Samba用户列表,读取passdb.tdb数据库文件。
pdbedit –Lv:列出Samba用户列表的详细信息。
pdbedit –c “[D]” –u username:暂停该Samba用户的账号。
pdbedit –c “[]” –u username:恢复该Samba用户的账号。
3.ldapsam:该方式则是基于LDAP的账户管理方式来验证用户。首先要建立LDAP服务,然后设置“passdb backend = ldapsam:ldap://LDAP Server”客户端就用这个密码来访问Samba的资源。smbpasswd文件默认在/etc/samba目录下,不过有时候要手工建立该文件。
]]>ipmi lan print
modprobe ipmi_msghandler
modprobe ipmi_devintf
modprobe ipmi_si
yum install ipmitool -y
ipmi lan print
配置ipmi地址(一般选channel 1)
ipmitool lan set 1 ipsrc static
ipmitool lan set 1 ipaddr 200.201.10.1
ipmitool lan set 1 netmask 255.255.248.0
ipmitool lan set 1 defgw ipaddr 200.201.17.254
esxi主机开机ssh服务,登录esxi主机>点击管理>服务>开启ssh服务
下载esxi_ipmitool-1.8.15-1.vib,然后将此文件拷贝到esxi主机下的/var/log/vmware目录下。
如果设备能够连外网的情况下,则直接下载
wget dl.90.vc/tools/esxi_ipmitool-1.8.15-1.vib -O /var/log/vmware/ipmitool-1.8.15-1.vib
远程登录到esxi主机。ssh root@ip
执行 esxcli software acceptance set –level=CommunitySupported
执行 esxcli software vib install-v esxi_ipmitool-1.8.15-1.vib
先查看一下ipmi配置
/opt/ipmitool/bin/ipmitool lan print
配置ipmi地址(一般选channel 1)
配置channel 1 为静态地址
/opt/ipmitool/bin/ipmitool lan set 1 ipsrc static
配置ipmi ip地址
/opt/ipmitool/bin/ipmitool lan set 1 ipaddr 200.201.10.1
配置ipmi 子网掩码
/opt/ipmitool/bin/ipmitool lan set 1 netmask 255.255.248.0
配置ipmi 网关
]]>/opt/ipmitool/bin/ipmitool lan set 1 defgw ipaddr 200.201.17.254
NFS (network file system 网络文件系统)是当前主流异构平台共享文件系统之一。
NFS的工作原理是使用客户端/服务器架构,由一个客户端程序和服务器程序组成。
NFS与NAS的区别:
NFS 是一种文件传输协议
NAS (Network Attached Storage) NAS是存储的一种方式,可以是设备,也可以是存储网络的架构。
以centos系统为例
yum install -y nfs-utils
mkdir /data
编辑exports文件,添加从机
vim /etc/exports
填入:
/data 192.168.203.0/24 (rw,sync,no_root_squash)
备注: no_root_squash 允许root用户访问挂载上来的NFS卷
先启动rpcbind服务,再启动nfs服务
systemctl start rpcbind.service
systemctl start nfs-server.service
exportfs -v
yum install -y nfs-utils
systemctl start rpcbind.service
showmount -e <服务端的ip地址>
创建一个共享目录
mkdir /data
开始挂载
mount -t nfs <服务端ip或者域名>:/data /data
mount -a
查看挂载情况
df -h
如果想卸载:
unmount /data
vim /etc/fstab
填入:
]]><服务端ip或者域名>:/data /data nfs defaults 0 0
Docker是一个开源的容器引擎,它基于LXC容器技术,使用Go语言开发。
源代码托管在Github上,并遵从Apache2.0协议。
Docker采用C/S架构,其可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。
简单来说:Docker就是一种快速解决业务稳定环境的一种技术手段(快速搭建环境)。
一个完整的Docker有以下几个部分组成:
Docker Engine是一个C/S架构的应用程序,主要包含下面几个组件:
dockerd进程:常驻后台进程Dockerd
REST API Server:用来和 Dockerd 交互的
命令行CLI接口:和 REST API 进行交互(常用docker 命令)
分工合作
Docker 使用 C/S 体系的架构
Docker 客户端与 Docker 守护进程通信
Docker 守护进程负责构建,运行和分发 Docker 容器。
Docker 客户端和守护进程可以在同一个系统上运行,也可以将 Docker 客户端连接到远程 Docker 守护进程。
Docker 客户端和守护进程使用 REST API 通过UNIX套接字或网络接口进行通信。
注意:如果安装方法无法就上网查
ubuntu:sudo apt install docker-ce -y
如果安装不成功:
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \ $(lsb_release -cs) stable"
sudo apt install docker-ce -y
centos: yum install docker-ce docker-ce-cli containerd.io
启动:systemctl start docker
重启:systemctl restart docker
停止:systemctl stop docker
开机自动启动:systemctl enable docker
关闭自动启动:systemctl disable docker
修改docker镜像源:
echo '{"registry-mirrors": ["http://hub-mirror.c.163.com"]}' > /etc/docker/daemon.json
配置docker免sudo:
sudo usermod -aG docker $USER
sudo systemctl restart docker
备注:有的系统会无效,自行上网搜索解决方法
查找镜像:
docker search 镜像名
下载镜像:
docker pull 镜像名
从本地安装镜像:
docker image load -i 镜像包
简写:docker load -i 镜像包 或者 docker load < 镜像包
删除本地镜像:
docker image rm 镜像ID 或者 镜像名称
简写:docker rmi image_id
删除全部镜像:
docker rmi $(docker images -q)
镜像历史:
docker history image_id
标签:
docker tag old_image:old_version new_image:new_version
制作镜像:
docker commit -a ‘作者’ -m ‘描述’ 容器ID 新镜像名:版本
打包导出:
docker image save -o 镜像包.tar.gz 镜像:版本 # 保存到当前目录下
简写:docker save -o 镜像包.tar.gz 镜像
查看正在运行的容器:
docker container ls
简写:docker ps
查看所有容器
docker container ls -a
简写:docker ps -a
关闭/启动/重启:
docker container stop | start | restart 容器ID
停止所有容器:
docker stop $(docker ps -a -q)
删掉容器:
docker container rm 容器ID
简写:docker rm 容器ID
删除全部容器:
docker rm $(docker ps -a -q)
强制删除容器:
docker container rm -f 容器ID
简写:docker rm -f 容器ID
查看容器运行日志:
docker logs 容器ID
创建新容器并运行容器:
docker run -itd 镜像名:版本
交互式容器(登录容器):
docker exec -it ID /bin/bash
数据卷(映射目录):(重要)
docker run -itd -v [宿主机文件]:[容器文件] [镜像id/镜像名]
注意:-v 宿主机文件:容器文件 可以存在多个,表示同时挂载多个
宿主机文件尽量用绝对路径,容器文件即使不存在,Docker自动创建
端口映射(随机) 网络模式默认为bridge模式
docker run -itd –name 别名 -P image_id # -P 大写P
端口映射(指定) 网络模式默认为bridge模式
docker run -itd –name 别名 -p 8080:80 image_id # -p 小写p
host模式
docker run -itd –name 别名 –netword=host image_id
Dockerfile简而言之就是 编写Dockerfile脚本实现自动化构建自己想要的镜像
使用准则
1、大: 首字母必须大写D
2、空: 尽量将Dockerfile放在空目录中。
3、单: 每个容器尽量只有一个功能。
4、少: 执行的命令越少越好。
基础指令
FROM
: 指定基础镜像(镜像要已存在)MAINTAINER
: 维护者信息(姓名/邮箱)RUN
: 镜像操作指令ADD:
上传文件,一般用于上传压缩包,会自动解压压缩包COPY:
上传一般文件EXPOSE:
开放端口WORKDIR:
切换目录ENTRYPOINT
: 容器启动时执行指令构建镜像:
cd 到Dockerfile所在的目录
docker build -t 新镜像名:版本 ./
执行完后会生成一个新的镜像
# 选择基础镜像FROM centos:latest# 作者MAINTAINER caijinbo# shell指令安装软件RUN yum install -y nginx python3-devel net-tools openssh-server openssh-clients passwd gcc mariadb-devel# 把工程上传到容器ADD front_end_pc.tar.gz /data/ADD project_admin.tar.gz /data/ADD project.tar.gz /data/COPY requirements.txt /data/# 切换到工程目录,并安装依赖WORKDIR /data/project/RUN pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir --default-timeout=1000 -r /data/requirements.txtCOPY 8000.conf /etc/nginx/conf.d/COPY 8080.conf /etc/nginx/conf.d/COPY 8081.conf /etc/nginx/conf.d/# 设置环境变量,作用:指定系统语言为UTF-8,否则,uwsgi运行无法处理中文语言# ENV LC_ALL=zh_CN.UTF-8# 容器开放端口8080和8081EXPOSE 8080 8081# 发送容器启动脚本COPY command.sh /data/# 切换容器目录WORKDIR /data# 指定容器运行的启动脚本ENTRYPOINT ["/bin/bash","/data/command.sh"]
在Dockerfile同级目录下新建一个脚本
vim command.sh
cd /data/project/ uwsgi --ini uwsgi.ini nginx -g 'daemon off;'
注意:所有要操作的文件或压缩包均放在Dockerfile同级目录下
]]>前端客户端(浏览器、APP、ajax、爬虫程序)
–HTTP请求–>
服务器程序(如:gunicorn、uwsgi)接收和解析HTTP请求报文
–WSGI协议–>
框架程序(如:flask、Django)HTTP请求对象(request)–>中间层处理–>具体视图处理-业务处理(如数据库、模板、表单)–>中间层处理–>HTTP响应对象(response)
–WSGI协议–>
服务器程序(如:gunicorn、uwsgi)构建和返回HTTP响应报文
–HTTP响应–>
前端客户端(浏览器、APP、ajax、爬虫程序)
1. 重量级框架
2. 遵守MVT设计模式
在开发过程中, 当需要使用python的某些工具包/框架时需要联网安装
比如联网安装Django框架 django==2.2.16 版本
sudo pip install django==2.2.16
提示:使用如上命令, 会将django==2.2.16
安装到/usr/local/lib/python版本/dist-packages
路径下
问题 : 如果在一台电脑上, 想开发多个不同的项目, 需要用到同一个包的不同版本, 如果使用上面的命令, 在同一个目录下安装或者更新, 新版本会覆盖以前的版本, 其它的项目就无法运行了。
解决方案: 虚拟环境
虚拟环境
可以搭建独立的python运行环境
, 使得单个项目的运行环境与其它项目互不影响.虚拟环境
都位于/home/
下的隐藏目录.virtualenvs
下安装虚拟环境的命令 :
sudo pip install virtualenvsudo pip install virtualenvwrapper
创建虚拟环境的命令 :
例如:
使用默认的python创建虚拟环境
mkvirtualenv 虚拟环境名称例 :mkvirtualenv py_django
指定python版本创建虚拟环境
mkvirtualenv -p python3 虚拟环境名称例 :mkvirtualenv -p python3 py_django # 使用的是默认的python3版本mkvirtualenv -p python3.5 py_django # 使用的是python3.5版本,注意,所使用的python版本在本机已经安装才行
提示 :
查看所有虚拟环境的命令 :
workon 两次tab键
使用虚拟环境的命令 :
workon 虚拟环境名称 例: workon py_django
退出虚拟环境的命令 :
deactivate
删除虚拟环境的命令 :
rmvirtualenv 虚拟环境名称 先退出:deactivate 再删除:rmvirtualenv 虚拟环境名称
虚拟环境中安装框架、包命令 :
pip install 框架、包名称 例 : 安装`django==2.2.16` pip install django==2.2.16
框架、包安装的位置 :
~/.virtualenvs/虚拟环境名称/lib/python版本/site-packages
查看虚拟环境中安装的包 :
pip freeze 或者 pip list
- 在虚拟环境中,直接使用
pip install
安装Django框架或者扩展包时,速度特别慢,甚至报红色警告。- 这主要是因为Django框架和很多的扩展包都是从国外服务器进行下载安装的。
指定镜像源:加速下载安装Django框架或者扩展包
pip install django==2.2.16 -i https://pypi.tuna.tsinghua.edu.cn/simple/
如果还是下载安装比较慢,可以把上面的镜像源链接换为下面的任意一个:
https://mirrors.aliyun.com/pypi/simple/http://pypi.douban.com/simple/http://pypi.mirrors.ustc.edu.cn/simple/
创建工程的命令为:
django-admin startproject 工程名称例子:cd ~/Desktop/django-admin startproject Django_test
执行后,会多出一个新目录名为 Django_test,此即为新创建的工程目录。
在开发阶段,为了能够快速预览到开发的效果,django提供了一个纯python编写的轻量级web服务器,仅在开发阶段使用。
运行服务器命令如下:
python manage.py runserver ip:端口例:python manage.py runserver 127.0.0.1:9000或:python manage.py runserver # 可以不写IP和端口,默认IP是127.0.0.1,默认端口为8000。
在浏览器中输入网址“127.0.0.1:8000”便可看到效果。
提示:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
当前工程的根目录,Django会依此来定位工程内的相关文件,我们也可以使用该参数来构造文件路径。
调试模式,创建工程后初始值为True,即默认工作在调试模式下。
作用:
注意:部署线上运行的Django不要运行在调式模式下,记得修改DEBUG=False。
Django支持本地化处理,即显示语言与时区支持本地化。
本地化是将显示的语言、时间等使用本地的习惯,这里的本地化就是进行中国化,中国大陆地区使用简体中文,时区使用亚洲/上海时区,注意这里不使用北京时区表示。
初始化的工程默认语言和时区为英语和UTC标准时区
LANGUAGE_CODE = 'en-us' # 语言:英语TIME_ZONE = 'UTC' # UTC标准时区
将语言和时区修改为中国大陆信息
LANGUAGE_CODE = 'zh-hans' # 语言:简体中文TIME_ZONE = 'Asia/Shanghai' # 亚洲上海
提示:
在Django中,创建子应用仍然可以通过命令来操作,即:
cd 项目工程django-admin startapp 子应用名称(推荐)或者python manage.py startapp 子应用名称
cd ~/Desktop/Django_test/django-admin startapp users或者python manage.py startapp users
创建出来的子应用目录文件虽然被放到了工程项目目录中,但是django工程并不能立即直接使用该子应用,需要注册安装后才能使用。
在工程配置文件settings.py中,INSTALLED_APPS项保存了工程中已经注册安装的子应用,初始工程中的INSTALLED_APPS如下:
注册安装一个子应用的方法,即是将子应用的配置信息文件apps.py中的Config类添加到INSTALLED_APPS列表中。
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users', # 用户模块子应用]
经验值分享
参考知识点:请求HttpRequest
user = request.user
request.user
获取到的不一定是当前登录用户对象request.user
获取到的才是当前登录用户对象request.user
获取到的会是一个AnonymousUser对象(匿名用户,没有任何用户信息,没有使用价值)。request.user
?request.user
需要搭配用户访问的限制来使用。request.user
。参考知识点:中间件Middleware
多个中间件注册顺序:MIDDLEWARE = [ 'Middleware1', 'Middleware2', 'Middleware3',]请求时:按照顺序由上而下进入中间件 [1 ---> 2 ---> 3]响应时:先进入的中间件后执行完的 [3 ---> 2 ---> 1]经验: 中间件中请求优先的逻辑,中间件一定要放在最前注册 中间件中响应优先的逻辑,中间件一定要放在最后注册例子: 解决前后端分离时请求跨域的问题 每个请求都要解决跨域的问题,所以需要用到中间件 而且需要在请求处理时最先处理跨域的问题,所以解决请求跨域时的中间件需要最先注册
函数视图定义方式:
1. 函数视图它是一个标准的Python函数。2. 函数视图中,第一个参数必须定义:第一个参数为请求对象,用于接收用户发送的请求报文。3. 函数视图中,必须返回响应对象:用于构造响应报文,并响应给用户。4. 说明: 请求对象:HttpRequest() 对应的对象 响应对象:HttpResponse() 对应的对象from django.shortcuts import renderfrom django import http# Create your views here.def register(request): """ 用户注册函数视图 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ # 响应数据 return http.HttpResponse('这里假装返回注册页面')
提示:
问题:
解决:
需求:
http://127.0.0.1:8000/users/register/
访问用户注册视图1. 新建子路由文件
- 在
子应用
中新建一个urls.py
文件用于定义该应用的所有路由信息
2. 注册子路由
- 在
子应用/urls.py
文件中定义路由信息
from django.urls import pathfrom . import views# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息urlpatterns = [ # 函数视图路由语法: # path('网络地址正则表达式', 函数视图名), # 用户注册:http://127.0.0.1:8000/users/register/ path('users/register/', views.register),]
3. 注册总路由
- 在工程总路由
工程同名目录/urls.py
中包含子应用的路由数据
from django.contrib import adminfrom django.urls import path, includeurlpatterns = [ # 自带的后台管理系统的总路由:可以忽略 path('admin/', admin.site.urls), # 总路由包含子路由语法 # path('网络地址前缀/', include('子应用.urls')), # 或者 # path('', include('子应用.urls')), # 用户模块:http://127.0.0.1:8000/users/register/ path('', include('users.urls')),]
总路由说明:
include()
来将users子应用
里的所有路由都包含进工程总路由中。4. 启动运行测试
重新启动django程序
python manage.py runserver
使用postman进行请求测试: http://127.0.0.1:8000/users/register/
# GET http://127.0.0.1:8000/users/register/def register(request): """ 用户注册函数视图 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ # 响应数据 return http.HttpResponse('这里假装返回注册页面')
需求:
http://127.0.0.1:8000/users/register/
发送GET请求,用来获取注册页面。http://127.0.0.1:8000/users/register/
发送POST请求,用来实现注册逻辑。需求实现:
def register(request): """ 用户注册函数视图 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ # 获取请求方法,判断是GET还是POST请求 if request.method == 'GET': # 处理GET请求,返回注册页面 return HttpResponse('这里假装返回注册页面') else: # 处理POST请求,实现注册逻辑 return HttpResponse('这里假装实现注册逻辑')
函数视图问题说明:
解决方案:
类视图定义方式:
1. 类视图它是一个标准的Python类。2. 类视图需要继承自Django提供的父类视图View。3. 在类视图中, 3.1 需要定义跟请求方法同名的函数来对应不同请求方式 3.2 在请求方法同名的函数中,还必须定义一个接收请求的参数(同函数视图) 3.3 在请求方法同名的函数中,还必须返回一个响应对象(同函数视图)from django.views import Viewclass RegisterView(View): """用户注册类视图 http://127.0.0.1:8000/users/register/ """ def get(self, request): """ 处理GET请求,返回注册页面 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ return http.HttpResponse('这里假装返回注册页面') def post(self, request): """ 处理POST请求,实现注册逻辑 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ return http.HttpResponse('这里假装实现注册逻辑')
类视图的好处:
- 代码可读性好
- 类视图相对于函数视图有更高的复用性, 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可。
说明:
需求:
http://127.0.0.1:8000/users/register/
发送GET请求,用来获取注册页面。http://127.0.0.1:8000/users/register/
发送POST请求,用来实现注册逻辑。1. 注册子路由
- 在
子应用/urls.py
文件中定义路由信息- 由于当前代码还是编写在users子应用中的,所以总路由注册过一次之后,不用再注册
from django.urls import pathfrom . import views# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息urlpatterns = [ # 类视图路由语法: # path('网络地址正则表达式', 类视图.as_view()), # 用户注册:http://127.0.0.1:8000/users/register/ path('users/register/', views.RegisterView.as_view()),]
2. 启动运行测试
settings.py
文件中注释掉CSRF中间件
# 中间件MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉 # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',]
python manage.py runserver
http://127.0.0.1:8000/users/register/
class View: """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. # 为所有视图定义简单的父类,只实现了请求方法分派和简单的完整性检查。 """ # 定义Django允许接收的请求方法 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): """ Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things. # 类视图的初始化构造函数,创建类视图对象时会被调用,并可以接收额外的参数 """ # Go through keyword arguments, and either save their values to our # instance, or raise an error. for key, value in kwargs.items(): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): """Main entry point for a request-response process. # 请求-响应过程的主要入口点. """ for key in initkwargs: # 遍历as_view()接收的参数 # 省略...... def view(request, *args, **kwargs): """准备一个函数视图,将来作为as_view()的返回值,并用于路由匹配""" # 初始化类视图对象 self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get # 将路由中传入的参数,绑定到类视图对象中 self.setup(request, *args, **kwargs) # 检查类视图是否完整:类视图中必须要有'request' attribute if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) # 调用请求分发的方法(最核心):将请求分发给跟请求方法同名的函数 return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view def setup(self, request, *args, **kwargs): """Initialize attributes shared by all view methods. # 初始化所有视图方法共享的属性:将路由中传入的参数,绑定到类视图对象中 """ self.request = request self.args = args self.kwargs = kwargs def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. # 尽量采用正确的调度方法;如果方法不存在,请遵从错误处理程序。如果请求方法不在批准的列表中,也遵从错误处理程序。 # 先判断客户端的请求方法是否在允许接收的方法列表中 if request.method.lower() in self.http_method_names: # 如果客户端的请求方法在允许接收的方法列表中, # 取出类视图对象中的跟请求方法同名的函数名,赋值给handler # 比如:当前客户端发送的请求,请求方法是GET,那么,handler=get handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: # 如果客户端的请求方法不在允许接收的方法列表中,遵从错误处理程序 handler = self.http_method_not_allowed # 如果请求分发没有问题,那么就去调用该跟请求分发同名的函数 # 如果当前客户端发送的请求,请求方法是GET # handler(request, *args, **kwargs)等价于get(request, *args, **kwargs) # 如果handler()调用成功,那么跟请求分发同名的函数就会被调用执行 return handler(request, *args, **kwargs) def http_method_not_allowed(self, request, *args, **kwargs): """错误处理程序:请求方法不匹配时,响应的错误信息""" logger.warning( 'Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': request} ) return HttpResponseNotAllowed(self._allowed_methods())
提示:
示例:
class ListModelMixin(object): """list扩展类 """ def list(self, request, *args, **kwargs): passclass CreateModelMixin(object): """create扩展类 """ def create(self, request, *args, **kwargs): passclass TestMixinView(CreateModelMixin, ListModelMixin, View): """同时继承两个扩展类,复用list和create方法""" def get(self, request): self.list(request) pass def post(self, request): self.create(request) pass
提示:
需求:
http://127.0.0.1:8000/users/login/
访问用户登录视图class LoginView(View): """用户登录类视图 http://127.0.0.1:8000/users/login/ """ def get(self, request): """ 处理GET请求,返回登录页面 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ return http.HttpResponse('假装这是个登录页面') def post(self, request): """ 处理POST请求,实现登录逻辑 :param request: 请求对象,包含了请求报文信息 :return: 响应对象,用于构造响应报文,并响应给用户 """ return http.HttpResponse('假装实现登录逻辑')
1. 注册子路由
from django.urls import re_path, pathurlpatterns = [ # 函数视图re_path()路由语法: # re_path(r'^网络地址正则表达式$', 函数视图名), # 类视图re_path()路由语法: # re_path(r'^网络地址正则表达式$', 类视图.as_view()), # 用户登录:http://127.0.0.1:8000/users/login/ re_path(r'^users/login/$', views.LoginView.as_view()), #或者使用 path('users/login/', views.LoginView.as_view()),]
1. 注册子路由
from django.urls import re_pathurlpatterns = [ # 函数视图url()路由语法: # url(r'^网络地址正则表达式$', 函数视图名), # 类视图url()路由语法: # url(r'^网络地址正则表达式$', 类视图.as_view()), # 用户登录:http://127.0.0.1:8000/users/login/ url(r'^users/login/$', views.LoginView.as_view()),]
2. postman进行请求测试
- 使用postman分别向
http://127.0.0.1:8000/users/login/
发送GET和POST请求
path()
# 函数视图path()路由语法:# path('网络地址正则表达式', 函数视图名),# 类视图path()路由语法:# path('网络地址正则表达式', 类视图.as_view()),
path()
路由语法中,不需要定义正则表达式严格的开头和结尾,因为已经封装好了re_path()、url()
# 函数视图re_path()路由语法:# re_path(r'^网络地址正则表达式$', 函数视图名),# 类视图re_path()路由语法:# re_path(r'^网络地址正则表达式$', 类视图.as_view()),# 函数视图url()路由语法:# url(r'^网络地址正则表达式$', 函数视图名),# 类视图url()路由语法:# url(r'^网络地址正则表达式$', 类视图.as_view()),
re_path()和url()
路由语法中,必须要定义正则表达式严格的开头和结尾urlpatterns
列表中的。urlpatterns
列表中以由上至下的顺序查找对应路由规则。include
中包含了,则再进入被包含的urls
中的urlpatterns
列表由上至下进行查询。可能存在的问题:
提示:
re_path()、url()
定义路由时出现。path()
定义路由时,网络地址正则表达式默认就是严格的开头和结尾。例子:
class SayView(View): """测试路由屏蔽 http://127.0.0.1:8000/say/ """ def get(self, request): return http.HttpResponse('say')class SayHelloView(View): """测试路由屏蔽 http://127.0.0.1:8000/sayhello/ """ def get(self, request): return http.HttpResponse('say hello')# 测试路由屏蔽# http://127.0.0.1:8000/say/re_path(r'^say', views.SayView.as_view()),# http://127.0.0.1:8000/sayhello/re_path(r'^sayhello', views.SayHelloView.as_view()),
完整的、正确的路由定义方式:
# 测试路由屏蔽# http://127.0.0.1:8000/say/re_path(r'^say/$', views.SayView.as_view()),# # http://127.0.0.1:8000/sayhello/re_path(r'^sayhello/$', views.SayHelloView.as_view()),
提示:
利用HTTP协议向服务器传参有以下几种途径
?key1=value1&key2=value2
http://www.meiduo.site/list/115/1/?sort=price
中的?sort=price
http://www.meiduo.site/detail/2/
中的/2/
提示:
?k1=v1&k2=v2
request.GET
属性获取,并返回QueryDict类型的对象# 注册总路由urlpatterns = [ # 用户模块:http://127.0.0.1:8000/users/register/ path('', include('users.urls')), # 请求和响应 path('', include('request_response.urls')),]
class QSParamView(View): """测试提取查询字符串参数 http://127.0.0.1:8000/querystring/?name=zxc&age=18 """ def get(self, request): # 获取查询字符串参数name、age name = request.GET.get('name', '小明') age = request.GET.get('age', '0') return http.HttpResponse('查询字符串参数:%s--%s' % (name, age))
# 注册子路由urlpatterns = [ # 测试提取查询字符串参数:http://127.0.0.1:8000/querystring/?name=zxc&age=18 path('querystring/', views.QSParamView.as_view()),]
重要提示:
QueryDict
补充:
QueryDict
是由Django自己封装的一个数据类型,继承自python的字典Dict
它被定义在django.http.QueryDict
中
它专门用来存储请求中提取的查询字符串参数和请求体参数
QueryDict
的使用:
# 如果键不存在则返回None值,可以设置默认值进行后续处理query_dict.get('键',默认值)# 可简写为:query_dict['键']
提示:
表单类型数据和JSON字符串类型
,我们应区别对待前端发送的表单类型的请求体数据,可以通过
request.POST
属性获取,并返回QueryDict对象。
# 测试提取表单类型请求体数据:http://127.0.0.1:8000/formdata/path('formdata/', views.FormDataParamView.as_view()),class FormDataParamView(View): """测试提取表单类型请求体参数 http://127.0.0.1:8000/formdata/ """ def post(self, request): # 获取表单类型请求体参数中的username、password username = request.POST.get('username') password = request.POST.get('password') return http.HttpResponse('表单类型请求体参数:%s--%s' % (username, password))
重要提示:
request.POST
只能用来获取POST表单发送的请求体数据
提示:
request.body
属性获取最原始的请求体数据request.body
获取的是bytes类型
的请求体原始数据需求:
{ "username": "张三", "password": "123"}
可以进行如下方法操作:
# 测试提取非表单类型请求体参数:http://127.0.0.1:8000/json/path('json/', views.JSONParamView.as_view()),import jsonclass JSONParamView(View): """测试提取非表单类型请求体参数 http://127.0.0.1:8000/json/ """ def post(self, request): # 获取请求体中原始的JSON数据 json_str = request.body # 使用json模块将原始的JSON数据转字典 json_dict = json.loads(json_str) # 提取JSON数据中的参数 username = json_dict.get('username') password = json_dict.get('password') return http.HttpResponse('非表单类型请求体参数:%s--%s' % (username, password))
提示:
需求:
需求1:
http://127.0.0.1:8000/url_param1/18/
18
需求2:
http://127.0.0.1:8000/url_param2/18500001111/
18500001111
实现需求1
# 测试path()提取普通路径参数:http://127.0.0.1:8000/url_param1/18/path('url_param1/<int:age>/', views.URLParam1View.as_view()),class URLParam1View(View): """测试path()提取普通路径参数 http://127.0.0.1:8000/url_param1/18/ """ def get(self, request, age): """ :param age: 路由提取的关键字参数 """ return http.HttpResponse('测试path()提取普通路径参数:%s' % age)
重要提示:
思考:
<int:age>
是什么?结论:
默认的路由转换器:
- 位置在
django.urls.converters.py
DEFAULT_CONVERTERS = { 'int': IntConverter(), # 匹配正整数,包含0 'path': PathConverter(), # 匹配任何非空字符串,包含了路径分隔符 'slug': SlugConverter(), # 匹配字母、数字以及横杠、下划线组成的字符串 'str': StringConverter(), # 匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式 'uuid': UUIDConverter(), # 匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00}
实现需求2
http://127.0.0.1:8000/url_param2/18500001111/
18500001111
问题:
解决方案:
实现需求2:自定义路由转换器
在任意可以被导入的python文件中,都可以自定义路由转换器
比如:在工程根目录下,新建converters.py
文件,用于自定义路由转换器
class MobileConverter: """自定义路由转换器:匹配手机号""" # 匹配手机号码的正则 regex = '1[3-9]\d{9}' def to_python(self, value): # 将匹配结果传递到视图内部时使用 return int(value) def to_url(self, value): # 将匹配结果用于反向解析传值时使用 return str(value)
注册自定义路由转换器
在总路由中,注册自定义路由转换器
from django.urls import register_converterfrom converters import MobileConverter# 注册自定义路由转换器# register_converter(自定义路由转换器, '别名')register_converter(MobileConverter, 'mobile')urlpatterns = []
使用自定义路由转换器
# 测试path()中自定义路由转换器提取路径参数:手机号 http://127.0.0.1:8000/url_param2/18500001111/path('url_param2/<mobile:phone_num>/', views.URLParam2View.as_view()),
class URLParam2View(View): """测试path()中自定义路由转换器提取路径参数:手机号 http://127.0.0.1:8000/url_param2/18500001111/ """ def get(self, request, phone_num): """ :param phone_num: 路由提取的关键字参数 """ return http.HttpResponse('测试path()提取路径参数手机号:%s' % phone_num)
# 测试re_path()提取路径参数:http://127.0.0.1:8000/url_param3/18500001111/re_path(r'^url_param3/(?P<phone_num>1[3-9]\d{9})/$', views.URLParam3View.as_view()),class URLParam3View(View): """测试re_path()提取路径参数 http://127.0.0.1:8000/url_param3/18500001111/ """ def get(self, request, phone_num): """ :param phone_num: 路由提取的关键字参数 """ return http.HttpResponse('测试re_path()提取路径参数:%s' % phone_num)
可以通过request.META
属性获取请求头headers中的数据,request.META
为字典类型。
常见的请求头如:
CONTENT_LENGTH
– The length of the request body (as a string).CONTENT_TYPE
– The MIME type of the request body.HTTP_ACCEPT
– Acceptable content types for the response.HTTP_ACCEPT_ENCODING
– Acceptable encodings for the response.HTTP_ACCEPT_LANGUAGE
– Acceptable languages for the response.HTTP_HOST
– The HTTP Host header sent by the client.HTTP_REFERER
– The referring page, if any.HTTP_USER_AGENT
– The client’s user-agent string.QUERY_STRING
– The query string, as a single (unparsed) string.REMOTE_ADDR
– The IP address of the client.REMOTE_HOST
– The hostname of the client.REMOTE_USER
– The user authenticated by the Web server, if any.REQUEST_METHOD
– A string such as "GET"
or "POST"
.SERVER_NAME
– The hostname of the server.SERVER_PORT
– The port of the server (as a string).具体使用如:
class HeadersParamView(View): """测试提取请求头参数""" def get(self, request): # 获取请求头中文件的类型 ret = request.META.get('CONTENT_TYPE') return http.HttpResponse('OK')
提示:
HttpResponse()
:响应多种数据类型JsonResponse()
:响应JSONredirect()
:重定向render()
:渲染并响应HTML模板提示:
可以使用
django.http.HttpResponse
来构造响应对象。
response = HttpResponse(content=响应体, content_type=响应体数据类型,默认为text/html, status=状态码,默认为200)
示例:
# 测试HttpResponse:http://127.0.0.1:8000/response1/path('response1/', views.Response1View.as_view()),class Response1View(View): """测试HttpResponse http://127.0.0.1:8000/response1/ """ def get(self, request): # 使用HttpResponse构造响应数据 # return http.HttpResponse(content='itcast python', status=200) # 可简写 # return http.HttpResponse('itcast python') # 另外一种写法 response = http.HttpResponse('itcast python') return response
补充:HttpResponse子类
Django提供了一系列HttpResponse的子类,可以快速设置状态码
HttpResponseRedirect
默认响应状态码为 301HttpResponsePermanentRedirect
默认响应状态码为 302HttpResponseNotModified
默认响应状态码为 304HttpResponseBadRequest
默认响应状态码为 400HttpResponseNotFound
默认响应状态码为 404HttpResponseForbidden
默认响应状态码为 403HttpResponseNotAllowed
默认响应状态码为 405HttpResponseGone
默认响应状态码为 410HttpResponseServerError
默认响应状态码为 500提示:
在开发功能时,如果前端需要JSON数据,那么后端就需要构造并响应JSON数据
而Django提供了JsonResponse
来构造并响应JSON数据
JsonResponse
作用:
示例:
# 测试JSONResponse:http://127.0.0.1:8000/json_resp/path('json_resp/', views.JSONResponseView.as_view()),class JSONResponseView(View): """测试JSONResponse http://127.0.0.1:8000/json_resp/ """ def get(self, request): # 准备要响应的数据 dict_data = { 'city': 'beijing', 'subject': 'python' } # 使用JSONResponse构造并响应JSON数据 return http.JsonResponse(dict_data)
提示:
解决方案:
redirect()
:重定向需求:
LoginRedirectView
LoginRedirectView
时,如果其中的登录逻辑处理完成,我们将用户重定向到首页示例:
# 测试重定向path('login_redirect/', views.LoginRedirectView.as_view()),path('index/', views.IndexView.as_view()),from django.shortcuts import render, redirectclass IndexView(View): """测试重定向 http://127.0.0.1:8000/index/ """ def get(self, request): return http.HttpResponse('假装这是个网站首页')class LoginRedirectView(View): """测试重定向 http://127.0.0.1:8000/login_redirect/ """ def post(self, request): # 假装正在处理登录逻辑 # 假装登录逻辑处理完成 # ...... # 将用户通过重定向引导到首页 return redirect('/index/')
思考:
结论:
需求:
解决方案:
示例:
总路由中,给子应用的总路由起别名
urlpatterns = [ # 请求和响应 # path('', include(('子路由', '子应用名字'), namespace='总路由别名,可以随便命名')), path('', include(('request_response.urls', 'request_response'), namespace='request_response')),]
子路由中,给子应用的子路由起别名
# 测试重定向path('login_redirect/', views.LoginRedirectView.as_view()),path('index/', views.IndexView.as_view(), name='index'),
视图中,使用路由的别名,动态的解析出该路由中的真实地址
from django.shortcuts import render, redirect, reverseclass IndexView(View): """测试重定向 http://127.0.0.1:8000/index/ """ def get(self, request): return http.HttpResponse('假装这是个网站首页')class LoginRedirectView(View): """测试重定向 http://127.0.0.1:8000/login_redirect/ """ def post(self, request): # 假装正在处理登录逻辑 # 假装登录逻辑处理完成 # ...... # 将用户通过重定向引导到首页 # return redirect('/index/') # ret_url = reverse('总路由别名:子路由别名') ret_url = reverse('request_response:index') return redirect(ret_url)
概念:
使用场景:
设计思想:
默认的中间件
# 中间件MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉 # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',]
提示:
- Django在中间件中预置了六个方法,这六个方法会在不同的阶段自动执行,对输入或输出进行干预。
2.1 初始化方法:
启动Django程序,初始化中间件时,自动调用一次,用于确定是否启用当前中间件
def __init__(self, get_response=None): pass
2.2 处理请求前的方法:(重要)
在处理每个请求前,自动调用,返回None或HttpResponse对象
def process_request(self, request): pass
2.3 处理视图前的方法:(重要)
在处理每个视图前,自动调用,返回None或HttpResponse对象
def process_view(self, request, view_func, view_args, view_kwargs): pass
2.4 处理模板响应前的方法:
在处理每个模板响应前,自动调用,返回实现了render方法的响应对象
def process_template_response(self, request, response): pass
2.5 处理响应后的方法:(重要)
在每个响应返回给客户端之前,自动调用,返回HttpResponse对象
def process_response(self, request, response): pass
2.6 异常处理:
当视图抛出异常时,自动调用,返回一个HttpResponse对象
def process_exception(self, request,exception): pass
middlewares.py
文件来自定义中间件# 导入中间件的父类from django.utils.deprecation import MiddlewareMixinclass TestMiddleware1(MiddlewareMixin): """自定义中间件""" def process_request(self, request): """处理请求前自动调用""" print('process_request1 被调用') def process_view(self, request, view_func, view_args, view_kwargs): # 处理视图前自动调用 print('process_view1 被调用') def process_response(self, request, response): """在每个响应返回给客户端之前自动调用""" print('process_response1 被调用') return response
注册自定义的中间件
# 中间件MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉 # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middlewares.TestMiddleware1', # 注册自定义的中间件1]
准备两个自定义的中间件
from django.utils.deprecation import MiddlewareMixinclass TestMiddleware1(MiddlewareMixin): """自定义中间件""" def process_request(self, request): """处理请求前自动调用""" print('process_request1 被调用') def process_view(self, request, view_func, view_args, view_kwargs): # 处理视图前自动调用 print('process_view1 被调用') def process_response(self, request, response): """在每个响应返回给客户端之前自动调用""" print('process_response1 被调用') return responseclass TestMiddleware2(MiddlewareMixin): """自定义中间件""" def process_request(self, request): """处理请求前自动调用""" print('process_request2 被调用') def process_view(self, request, view_func, view_args, view_kwargs): # 处理视图前自动调用 print('process_view2 被调用') def process_response(self, request, response): """在每个响应返回给客户端之前自动调用""" print('process_response2 被调用') return response
注册多个自定义的中间件
# 中间件MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉 # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'middlewares.TestMiddleware1', # 注册自定义的中间件1 'middlewares.TestMiddleware2', # 注册自定义中的间件2]
重要提示:中间件执行顺序
提示:
- 模型是Django程序处理数据库数据的模块
- Django的模型是定义在子应用的
models.py
中的
经验值分享
1. 读取外键
class BookInfo(models.Model): """图书信息模型类""" btitle = models.CharField(max_length=20, verbose_name='名称')class HeroInfo(models.Model): hname = models.CharField(max_length=20, verbose_name='名称') # 外键 hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书')
hero.hbook.id
hero.hbook_id
hero.hbook.id
:不安全,如果hbook为空,会报错,因为空对象不能读取任何属性hero.hbook_id
:安全,如果hbook_id为空,不会报错,获取的是空值方式二
读取外键2. 查询集QuerySet缓存的特点
- 参考知识点:查询集QuerySet
在ubuntu系统的MySQL程序中创建一个数据库
mysql -uroot -pcreate database django_demo default charset=utf8;
在
settings.py
中配置数据库的连接信息
- 以下是默认的数据库配置信息
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }}
修改
DATABASES
配置信息
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '192.168.103.240', # 数据库主机 'PORT': 3306, # 数据库端口 'USER': 'root', # 数据库用户名 'PASSWORD': 'mysql', # 数据库用户密码 'NAME': 'django_demo' # 数据库名字 }}
安装mysqlclient
# 进入虚拟环境pip install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simple/
1. 更换ubuntu中默认的源为国内的源:提升软件下载速度2. 更新apt-get的源和升级3. 安装libmysqlclient-dev:因为mysqlclient依赖这个软件4. 虚拟环境中安装mysqlclient==1.4.65. 测试:重启Django程序
1.4.1 更换ubuntu中默认的源为国内的源
# 第一步:备份 /etc/apt/sources.list# 第二步:在/etc/apt/sources.list中添加以下阿里源deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiversedeb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse# 备份$ cp /etc/apt/sources.list /etc/apt/sources.list.bak# 添加阿里源$ sudo vim /etc/apt/sources.list
1.4.2 更新apt-get的源和升级
- 其中
upgrade
会执行很久,大家需要耐心等待
$ sudo apt-get update$ sudo apt-get upgrade
1.4.3 安装libmysqlclient-dev
$ sudo apt-get install libmysqlclient-dev
如何创建数据库表?
如何使用Django提供的模型类创建数据库表?
ORM框架介绍
提示:
ORM框架作用:
提示:模型类的定义思路
1. 先根据需求设计数据表2. 再根据数据表设计方案定义模型类
2.2.1 根据需求设计数据表
2.2.2 定义模型类
子应用/models.py
文件中django.db.models
中booktest
,并在其models.py
文件中定义模型类class BookInfo(models.Model): """图书信息:演示一对多,一方""" btitle = models.CharField(max_length=20, verbose_name='书名') bpub_date = models.DateField(verbose_name='发布日期') bread = models.IntegerField(default=0, verbose_name='阅读量') bcomment = models.IntegerField(default=0, verbose_name='评论量') is_delete = models.BooleanField(default=False, verbose_name='逻辑删除') class Meta: """模型类的元类:用于修改、配置模型类对应的数据表""" db_table = 'tb_books' # 自定义数据库表名 def __str__(self): """定义每个数据对象的显示信息""" return self.btitle # 输出该模型数据对象时,只输出书名class HeroInfo(models.Model): """英雄信息:演示一对多,多方""" # 确定性别字段的取值范围 GENDER_CHOICES = ( (0, 'female'), (1, 'male') ) hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='英雄属于的图书') hname = models.CharField(max_length=20, verbose_name='人名') hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别') hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息') is_delete = models.BooleanField(default=False, verbose_name='逻辑删除') class Meta: db_table = 'tb_heros' def __str__(self): return self.hname
2.3.1 关于主键
id
,可以使用pk
代替,pk全拼为primary key2.3.2 关于属性命名
不能是python的保留关键字
不允许使用连续的下划线,这是由Django的查询方式决定的
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性 = models.字段类型(选项)
2.3.3 关于数据库表名
db_table
自定义数据库表名2.3.4 关于字段类型
CREATE TABLE `tb_books` ( `id` int(11) NOT NULL AUTO_INCREMENT, `btitle` varchar(20) NOT NULL, `bpub_date` date NOT NULL, `bread` int(11) NOT NULL, `bcomment` int(11) NOT NULL, `is_delete` tinyint(1) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
类型 | 说明 |
---|---|
AutoField | 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性 |
BooleanField | 布尔字段,值为True或False |
NullBooleanField | 支持Null、True、False三种值 |
CharField | 字符串,参数max_length表示最大字符个数 |
TextField | 大文本字段,一般超过4000个字符时使用 |
IntegerField | 整数 |
DecimalField | 十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数 |
FloatField | 浮点数 |
DateField | 日期, 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于”最后一次修改”的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误 |
TimeField | 时间,参数同DateField |
DateTimeField | 日期时间,参数同DateField |
FileField | 上传文件字段 |
ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片 |
2.3.5 关于字段选项
选项 | 说明 |
---|---|
null | 如果为True,表示允许为空,默认值是False |
db_column | 字段的名称,如果未指定,则使用属性的名称 |
db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False |
default | 默认 |
primary_key | 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用 |
unique | 如果为True, 这个字段在表中必须有唯一值,默认值是False |
2.3.6 关于外键
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:
2.4.1 生成迁移文件
python manage.py makemigrations
2.4.2 同步到数据库中
python manage.py migrate
insert into tb_books(btitle,bpub_date,bread,bcomment,is_delete) values('射雕英雄传','1980-5-1',12,34,0),('天龙八部','1986-7-24',36,40,0),('笑傲江湖','1995-12-24',20,80,0),('雪山飞狐','1987-11-11',58,24,0);
insert into tb_heros(hname,hgender,hbook_id,hcomment,is_delete) values('郭靖',1,1,'降龙十八掌',0),('黄蓉',0,1,'打狗棍法',0),('黄药师',1,1,'弹指神通',0),('欧阳锋',1,1,'蛤蟆功',0),('梅超风',0,1,'九阴白骨爪',0),('乔峰',1,2,'降龙十八掌',0),('段誉',1,2,'六脉神剑',0),('虚竹',1,2,'天山六阳掌',0),('王语嫣',0,2,'神仙姐姐',0),('令狐冲',1,3,'独孤九剑',0),('任盈盈',0,3,'弹琴',0),('岳不群',1,3,'华山剑法',0),('东方不败',0,3,'葵花宝典',0),('胡斐',1,4,'胡家刀法',0),('苗若兰',0,4,'黄衣',0),('程灵素',0,4,'医术',0),('袁紫衣',0,4,'六合拳',0);
Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。
如果有需要,通过如下命令进入shell
python manage.py shell
导入两个模型类,以便后续使用
from booktest.models import BookInfo, HeroInfo
增加数据有两种方法。
1)save
通过创建模型类对象,模型对象.save()
方法保存到数据库中。
# 新增:方式一book = BookInfo()book.btitle = '西游记'book.bpub_date = '2020-05-18'book.bread = 20book.bcomment = 30book.save()
2)create
通过模型类.objects.create()
保存。
# 新增:方式二BookInfo.objects.create( btitle='三国演义', bpub_date='2020-05-20', bread=100, bcomment=200)
修改更新有两种方法
1)save
修改模型类对象的属性,然后执行save()
方法
hero = HeroInfo.objects.get(hname='猪八戒')hero.hname = '猪悟能'hero.save()
2)update
使用模型类.objects.filter().update()
,会返回受影响的行数
HeroInfo.objects.filter(hname='沙悟净').update(hname='沙僧')
删除有两种方法
1)模型类对象delete
hero = HeroInfo.objects.get(id=13)hero.delete()
2)模型类.objects.filter().delete()
HeroInfo.objects.filter(id=14).delete()
get 查询单一结果,如果不存在会抛出模型类.DoesNotExist异常。
all 查询多个结果。
count 查询结果数量。
>>> BookInfo.objects.all()<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 西游记>]>>>> book = BookInfo.objects.get(btitle='西游记')>>> book.id5>>> BookInfo.objects.get(id=3)<BookInfo: 笑傲江湖>>>> BookInfo.objects.get(pk=3)<BookInfo: 笑傲江湖>>>> BookInfo.objects.get(id=100)Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/delron/.virtualenv/dj/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/Users/delron/.virtualenv/dj/lib/python3.6/site-packages/django/db/models/query.py", line 380, in get self.model._meta.object_namedb.models.DoesNotExist: BookInfo matching query does not exist.>>> BookInfo.objects.count()6
实现SQL中的where功能,包括
过滤条件的表达语法如下:
属性名称__比较运算符=值# 属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线
1)相等
exact:表示判等。
例:查询编号为1的图书。
BookInfo.objects.filter(id__exact=1)可简写为:BookInfo.objects.filter(id=1)
2)模糊查询
contains:是否包含。
说明:如果要包含%无需转义,直接写即可。
例:查询书名包含’传’的图书。
BookInfo.objects.filter(btitle__contains='传')
startswith、endswith:以指定值开头或结尾。
例:查询书名以’部’结尾的图书
BookInfo.objects.filter(btitle__endswith='部')
以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.
3) 空查询
isnull:是否为null。
例:查询书名不为空的图书。
BookInfo.objects.filter(btitle__isnull=False)
4) 范围查询
in:是否包含在范围内。
例:查询编号为1或3或5的图书
BookInfo.objects.filter(id__in=[1, 3, 5])
5)比较查询
例:查询编号大于3的图书
BookInfo.objects.filter(id__gt=3)
不等于的运算符,使用exclude()过滤器。
例:查询编号不等于3的图书
BookInfo.objects.exclude(id=3)
6)日期查询
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
例:查询1980年发表的图书。
BookInfo.objects.filter(bpub_date__year=1980)
例:查询1980年1月1日后发表的图书。
BookInfo.objects.filter(bpub_date__gt=date(1990, 1, 1))
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。
语法如下:
F(属性名)
例:查询阅读量大于等于评论量的图书。
from django.db.models import FBookInfo.objects.filter(bread__gte=F('bcomment'))
可以在F对象上使用算数运算。
例:查询阅读量大于2倍评论量的图书。
BookInfo.objects.filter(bread__gt=F('bcomment') * 2)
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询阅读量大于20,并且编号小于3的图书。
BookInfo.objects.filter(bread__gt=20,id__lt=3)或BookInfo.objects.filter(bread__gt=20).filter(id__lt=3)
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值)
例:查询阅读量大于20的图书,改写为Q对象如下。
from django.db.models import QBookInfo.objects.filter(Q(bread__gt=20))
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))
Q对象前可以使用~操作符,表示非not。
例:查询编号不等于3的图书。
BookInfo.objects.filter(~Q(pk=3))
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 数量,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。
例:查询图书的总阅读量。
from django.db.models import SumBookInfo.objects.aggregate(Sum('bread'))
注意aggregate的返回值是一个字典类型,格式如下:
{'属性名__聚合类小写':值} 如:{'bread__sum':3}
使用count时一般不使用aggregate()过滤器。
例:查询图书总数。
BookInfo.objects.count()
注意count函数的返回值是一个数字。
使用order_by对结果进行排序
BookInfo.objects.all().order_by('bread') # 升序BookInfo.objects.all().order_by('-bread') # 降序
由一到多的访问语法:
一对应的模型类对象.多对应的模型类名小写_set
例:
b = BookInfo.objects.get(id=1)b.heroinfo_set.all()
由多到一的访问语法:
多对应的模型类对象.多对应的模型类中的关系类属性名
例:
h = HeroInfo.objects.get(id=1)h.hbook
访问一对应的模型类关联对象的id语法:
多对应的模型类对象.关联类属性_id
例:
h = HeroInfo.objects.get(id=1)h.hbook_id
Django的ORM中存在查询集的概念。
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
对查询集可以再次调用过滤器进行过滤,如
BookInfo.objects.filter(bread__gt=30).order_by('bpub_date')
也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。
从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。
判断某一个查询集中是否有数据:
创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集qs
qs = BookInfo.objects.all()
继续执行遍历迭代操作后,才真正的进行了数据库的查询
for book in qs: print(book.btitle)
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
情况一:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。
from booktest.models import BookInfo[book.id for book in BookInfo.objects.all()][book.id for book in BookInfo.objects.all()]
情况二:经过存储后,可以重用查询集,第二次使用缓存中的数据。
qs=BookInfo.objects.all()[book.id for book in qs][book.id for book in qs]
提示:
- 模板是Django程序渲染页面的模块
- Django的模板是定义在
templates
文件目录中的
1.1 准备模板文件目录
templates
1.2 配置模板
在
settings.py
配置文件中修改
TEMPLATES
配置项的
DIRS
值:
# 配置模板TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', #指定模板文件目录的路径 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },]
2.1 新建模板文件
templates
目录中新建一个模板文件,如:temp.html
2.2 响应模板文件
class TempView(View): """图书信息 http://127.0.0.1:8000/temp/ """ def get(self, request): return render(request, 'temp.html')
3.1 模板语法
模板语法如下:
变量名必须由字母、数字、下划线(不能以下划线开头)和点组成
3.2 使用模板语法渲染HTML模板
class BooksView(View): """图书信息 http://127.0.0.1:8000/books/ """ def get(self, request): # 构造上下文 context = { 'name': '张三', 'age': 18 } # 使用上下文渲染'book.html',并返回给客户端 return render(request, 'book.html', context)
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>图书信息页</title></head><body> <h1>{{ name }}</h1> <h1>{{ age }}</h1></body></html>
需求:
class BooksView(View): """测试模板 http://127.0.0.1:8000/books/ """ def get(self, request): # 查询所有图书信息 books = BookInfo.objects.all() # 构造上下文 context = { 'books': books } # 使用上下文渲染'book.html',并返回给客户端 return render(request, 'books.html', context)
<body> <ul> {% for book in books %} 《{{ book.btitle }}》 {% endfor %} </ul></body>
提示:
- Cookie和Session是Django程序中用来缓存数据的
经验值分享
1. 状态保持和Cookie、Session的关系
提示:
状态保持:
* 用于记录当前用户的状态,比如,用户登录后记录登录的状态。
Cookie、Session:
* Cookie、Session仅仅是Django提供的缓存机制而已,用于缓存数据的。* 比如: * Cookie和Session缓存购物车数据 * Cookie和Session缓存用户登录状态 (状态保持)
状态保持和Cookie、Session的关系
* Cookie和Session仅仅是状态保持的一种实现方式而已* 而能够实现状态保持的方式不仅仅只有Cookie和Session,比如JWT也可以实现状态保持
提示:
问题:
解决:
Cookie
Cookie的机制:
Cookie的特点:
Cookie,以键值对Key-Value
形式进行信息的存储
Cookie,基于域名安全,不同域名的
Cookie
是不能互相访问的
www.baidu.com
域名下面的Cookie
是不能被其他域名访问的Cookie,它会自动跟随当前的请求传递到对应域名所在的服务器中。
提示:
可以通过HttpResponse()对象中的set_cookie()
方法来设置Cookie。
# 创建响应对象response = HttpResponse()# 使用响应对象设置Cookieresponse.set_cookie(key, value, max_age=cookie有效期)
HttpResponse()
:响应多种数据类型JsonResponse()
:响应JSONredirect()
:重定向render()
:渲染并响应HTML模板max_age
设置为None
。max_age
设置为具体的秒数
。示例:
class BooksView(View): """测试模板 http://127.0.0.1:8000/books/ """ def get(self, request): # 查询所有图书信息 books = BookInfo.objects.all() # 构造上下文 context = { 'books': books } # 使用上下文渲染'book.html',并返回给客户端 response = render(request, 'books.html', context) # 设置Cookie response.set_cookie('name', 'itcast', max_age=None) # 响应结果,并写Cookie到浏览器 return response
可以通过HttpRequest对象的COOKIES
属性来读取本次请求携带的cookie值。
class TestCookieView(View): """测试Cookie http://127.0.0.1:8000/cookies/ """ def get(self, request): # 读取Cookie name = request.COOKIES.get('name') print(name) return http.HttpResponse('测试Cookie')
Session,是一种会话控制方式。由服务端创建,并且保存在服务端的数据存储形式。
Session,内部也是以key-value
键值对的形式存储数据。
Session,有些内容会被加密,所以可以存储敏感信息。
处理Session数据的子应用:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', <============ 'django.contrib.messages', 'django.contrib.staticfiles',
处理Session的中间件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', <============== 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',]
PS:如需禁用Session,将上图中的session中间件注释掉即可。
Session的机制:
session_key
:一个随机的唯一的不重复的字符串session_data
:用户的唯一标识信息(密文)session_key
'session_id': 'session_key'
session_key
,再使用它提取session_data
。session_data
来辨认用户状态Session的特点:
session_key
需要存储在Cookie中提示:
设置Session
和读取session
可以通过HttpRequest()对象中的session
属性来设置Session。
request.session['key'] = valueclass BooksView(View): """测试模板 http://127.0.0.1:8000/books/ """ def get(self, request): # 查询所有图书信息 books = BookInfo.objects.all() # 构造上下文 context = { 'books': books } # 使用上下文渲染'book.html',并返回给客户端 response = render(request, 'books.html', context) # 设置Cookie response.set_cookie('name', 'itcast', max_age=3600) # 设置Session request.session['name'] = 'itcast' # 响应结果,并写Cookie到浏览器 return response
根据键读取值
request.session.get('key', 默认值)class TestSessionView(View): """测试Session http://127.0.0.1:8000/session/ """ def get(self, request): # 读取Session name = request.session.get('name') print(name) return http.HttpResponse('测试Session')
清除所有Session,在存储中删除值部分。
request.session.clear()
清除session数据,在存储中删除session的整条数据。
request.session.flush()
删除session中的指定键及值,在存储中只删除某个键及对应的值。
del request.session['key']
设置session的有效期
request.session.set_expiry(value)
Session数据默认存储的位置是在
settings.py
的DATABASES
配置项指定的SQL数据库中
因为Session引擎默认的配置为:
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE='django.contrib.sessions.backends.cache'
优先从本机内存中存取,如果没有则从数据库中存取。
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
在Redis中保存Session,需要引入第三方扩展,我们可以使用django-redis来解决。
1)安装扩展
pip install django-redis
2)配置
在settings.py文件中做如下设置
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }}SESSION_ENGINE = "django.contrib.sessions.backends.cache"SESSION_CACHE_ALIAS = "default"
]]>官网:http://download.redis.io/releases/redis-6.0.8.tar.gz
安装方法一:源码编译安装
step1:下载
step2:解压
tar zxf redis-6.0.8.tar.gz
step3:移动,放到usr/local⽬录下
sudo mv ./redis-x.x.x /usr/local/redis/
step4:进⼊redis⽬录
cd /usr/local/redis/
step5:生成
sudo make
step6:测试,这段运⾏时间会较⻓
sudo make test
step7:安装,将redis的命令安装到/usr/local/bin/
⽬录
sudo make install
step8:安装完成后,我们进入目录/usr/local/bin
中查看
cd /usr/local/bin
ls -all
step9:配置⽂件,移动到/etc/
⽬录下
配置⽂件⽬录为/usr/local/redis/redis.conf
sudo cp /usr/local/redis/redis.conf /etc/redis/
安装方法二:
sudo apt install redis
提示:
Redis的配置信息在/etc/redis/redis.conf
下。
查看
sudo vi /etc/redis/redis.conf
绑定ip:如果需要远程访问,可将此⾏注释,或绑定⼀个真实ip
bind 127.0.0.1
端⼝,默认为6379
port 6379
是否以守护进程运⾏
daemonize yes
数据⽂件
dbfilename dump.rdb
数据⽂件存储路径
dir /var/lib/redis
⽇志⽂件
logfile “/var/log/redis/redis-server.log”
数据库,默认有16个
database 16
主从复制,类似于双机备份。
slaveof
服务器端的命令为redis-server
可以使⽤help查看帮助⽂档
redis-server –help
个人习惯
ps aux | grep redis 查看redis服务器进程
sudo kill -9 pid 杀死redis服务器
sudo redis-server /etc/redis/redis.conf 指定加载的配置文件
客户端的命令为redis-cli
可以使⽤help查看帮助⽂档
redis-cli –help
连接redis
redis-cli
运⾏测试命令
ping
切换数据库
数据库没有名称,默认有16个,通过0-15来标识,连接redis默认选择第一个数据库
select 10
redis是key-value的数据结构,每条数据都是⼀个键值对
键的类型是字符串
注意:键不能重复
值的类型分为五种:
点击中⽂官⽹查看命令⽂档http://redis.cn/commands.html
如果设置的键不存在则为添加,如果设置的键已经存在则修改
设置键值
set key value
例1:设置键为name
值为itcast
的数据
set name itcast
设置键值及过期时间,以秒为单位
setex key seconds value
例2:设置键为aa
值为aa
过期时间为3秒的数据
setex aa 3 aa
设置多个键值
mset key1 value1 key2 value2 …
例3:设置键为a1
值为python
、键为a2
值为java
、键为a3
值为c
mset a1 python a2 java a3 c
获取:根据键获取值,如果不存在此键则返回nil
get key
例5:获取键name
的值
get name
根据多个键获取多个值
mget key1 key2 …
例6:获取键a1、a2、a3
的值
mget a1 a2 a3
del key
查找键,参数⽀持正则表达式
keys pattern
例1:查看所有键
keys *
例2:查看名称中包含a
的键
keys a*
判断键是否存在,如果存在返回1
,不存在返回0
exists key1
例3:判断键a1
是否存在
exists a1
查看键对应的value
的类型
type key
例4:查看键a1
的值类型,为redis⽀持的五种类型中的⼀种
type a1
删除键及对应的值
del key1 key2 …
例5:删除键a2、a3
del a2 a3
设置过期时间,以秒为单位
如果没有指定过期时间则⼀直存在,直到使⽤DEL
移除
expire key seconds
例6:设置键a1
的过期时间为3秒
expire a1 3
查看有效时间,以秒为单位
ttl key
例7:查看键bb
的有效时间
ttl bb
设置单个属性
hset key field value
例1:设置键 user
的属性name
为itheima
hset user name itheima
设置多个属性
hmset key field1 value1 field2 value2 …
例2:设置键u2
的属性name
为itcast
、属性age
为11
hmset u2 name itcast age 11
获取指定键所有的属性
hkeys key
例3:获取键u2的所有属性
hkeys u2
获取⼀个属性的值
hget key field
例4:获取键u2
属性name
的值
hget u2 name
获取多个属性的值
hmget key field1 field2 …
例5:获取键u2
属性name
、age
的值
hmget u2 name age
删除整个hash键及值,使⽤del命令
删除属性,属性对应的值会被⼀起删除
hdel key field1 field2 …
例7:删除键u2
的属性age
hdel u2 age
在左侧插⼊数据
lpush key value1 value2 …
例1:从键为a1
的列表左侧加⼊数据a 、 b 、c
lpush a1 a b c
在右侧插⼊数据
rpush key value1 value2 …
例2:从键为a1
的列表右侧加⼊数据0、1
rpush a1 0 1
在指定元素的前或后插⼊新元素
linsert key before或after 现有元素 新元素
例3:在键为a1
的列表中元素b
前加⼊3
linsert a1 before b 3
返回列表⾥指定范围内的元素
start
、stop
为元素的下标索引-1
表示最后⼀个元素lrange key start stop
例4:获取键为a1
的列表所有元素
lrange a1 0 -1
索引从左侧开始,第⼀个元素为0
索引可以是负数,表示尾部开始计数,如-1
表示最后⼀个元素
lset key index value
例5:修改键为a1
的列表中下标为1
的元素值为z
lset a 1 z
删除指定元素
count
次出现的值为value
的元素移除lrem key count value
例6.1:向列表a2
中加⼊元素a、b、a、b、a、b
lpush a2 a b a b a b
例6.2:从a2
列表右侧开始删除2个b
lrem a2 -2 b
例6.3:查看列表a2
的所有元素
lrange a2 0 -1
修剪(截取) 在[start stop]区间内的元素,区间外的元素全部删除
ltrim key start stop
添加元素
sadd key member1 member2 …
例1:向键a3
的集合中添加元素zhangsan
、lisi
、wangwu
sadd a3 zhangsan sili wangwu
返回所有的元素
smembers key
例2:获取键a3
的集合中所有元素
smembers a3
删除指定元素
srem key
例3:删除键a3
的集合中元素wangwu
srem a3 wangwu
添加
zadd key score1 member1 score2 member2 …
例1:向键a4
的集合中添加元素lisi
、wangwu
、zhaoliu
、zhangsan
,权重分别为4、5、6、3
zadd a4 4 lisi 5 wangwu 6 zhaoliu 3 zhangsan
返回指定范围内的元素
start、stop为元素的下标索引
索引从左侧开始,第⼀个元素为0
索引可以是负数,表示从尾部开始计数,如-1
表示最后⼀个元素
zrange key start stop
例2:获取键a4
的集合中所有元素
zrange a4 0 -1
返回score
值在min
和max
之间的成员
zrangebyscore key min max
例3:获取键a4
的集合中权限值在5和6之间
的成员
zrangebyscore a4 5 6
返回成员member
的score
值
zscore key member
例4:获取键a4
的集合中元素zhangsan
的权重
zscore a4 zhangsan
删除指定元素
zrem key member1 member2 …
例5:删除集合a4
中元素zhangsan
zrem a4 zhangsan
删除权重在指定范围的元素
zremrangebyscore key min max
例6:删除集合a4
中权限在5、6之间
的元素
zremrangebyscore a4 5 6
安装Redis的有3种方式https://github.com/andymccurdy/redis-py
第一种:进⼊虚拟环境,联⽹安装包redis
pip install redis
第二种:进⼊虚拟环境,联⽹安装包redis
easy_install redis
第三种:到中⽂官⽹-客户端下载redis包的源码,使⽤源码安装
一步步执行 wget https://github.com/andymccurdy/redis-py/archive/master.zip
unzip master.zip
cd redis-py-master
sudo python setup.py install
引⼊模块
from redis import StrictRedis
这个模块中提供了StrictRedis对象
,⽤于连接redis服务器,并按照不同类型提供 了不同⽅法,进⾏交互操作
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #添加键name,值为itheima result=sr.set('name','itheima') #输出响应结果,如果添加成功则返回True,否则返回False print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #获取键name的值 result = sr.get('name') #输出键的值,如果键不存在则返回None print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #设置键name的值,如果键已经存在则进⾏修改,如果键不存在则进⾏添加 result = sr.set('name','itcast') #输出响应结果,如果操作成功则返回True,否则返回False print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #设置键name的值,如果键已经存在则进⾏修改,如果键不存在则进⾏添加 result = sr.delete('name') #输出响应结果,如果删除成功则返回受影响的键数,否则则返回0 print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #获取所有的键 result=sr.keys() #输出响应结果,所有的键构成⼀个列表,如果没有键则返回空列表 print(result) except Exception as e: print(e)
sr = StrictRedis(host='localhost', port=6379, db=0)简写sr=StrictRedis()
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #添加键name,值为itheima result=sr.set('name','itheima') #输出响应结果,如果添加成功则返回True,否则返回False print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #获取键name的值 result = sr.get('name') #输出键的值,如果键不存在则返回None print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #设置键name的值,如果键已经存在则进⾏修改,如果键不存在则进⾏添加 result = sr.set('name','itcast') #输出响应结果,如果操作成功则返回True,否则返回False print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #设置键name的值,如果键已经存在则进⾏修改,如果键不存在则进⾏添加 result = sr.delete('name') #输出响应结果,如果删除成功则返回受影响的键数,否则则返回0 print(result) except Exception as e: print(e)
from redis import *if __name__=="__main__": try: #创建StrictRedis对象,与redis服务器建⽴连接 sr=StrictRedis() #获取所有的键 result=sr.keys() #输出响应结果,所有的键构成⼀个列表,如果没有键则返回空列表 print(result) except Exception as e: print(e)
]]>使用web框架专门负责处理用户的动态资源请求,这个web框架其实就是一个为web服务器提供服务的应用程序,简称web框架。
关系说明:
不需要经常变化的资源,这种资源web服务器可以提前准备好,比如: png/jpg/css/js等文件。
和静态资源相反, 这种资源会经常变化,比如: 我们在京东浏览商品时经常会根据条件进行筛选,选择不同条件, 浏览的商品就不同,这种资源web服务器无法提前准备好,需要web框架来帮web服务器进行准备,在这里web服务器可以把.html的资源请求认为是动态资源请求交由web框架进行处理。
它是web服务器和web框架之间进行协同工作的一个规则,WSGI协议规定web服务器把动态资源的请求信息传给web框架处理,web框架把处理好的结果返回给web服务器。
web服务器程序(web.py)代码:
import socketimport threadingimport sysimport framework# 定义web服务器类class HttpWebServer(object): def __init__(self, port): # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", port)) # 设置监听 tcp_server_socket.listen(128) self.tcp_server_socket = tcp_server_socket # 处理客户的请求 @staticmethod def handle_client_quest(new_socket): # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) if len(recv_client_data) == 0: print("关闭浏览器了") # 关闭服务与客户端的套接字 new_socket.close() return # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) # 根据指定字符串进行分割, 最大分割次数指定2 request_list = recv_client_content.split(" ", maxsplit=2) # 获取请求资源路径 request_path = request_list[1] print(request_path) # 判断请求的是否是根目录,如果条件成立,指定首页数据返回 if request_path == "/": request_path = "/index.html" # 判断是否是动态资源请求 if request_path.endswith(".html"): """这里是动态资源请求,把请求信息交给框架处理""" # 字典存储用户的请求信息 env = { "request_path": request_path } # 获取处理结果 status, headers, response_body = framework.handle_request(env) # 使用框架处理的数据拼接响应报文 # 响应行 response_line = "HTTP/1.1 %s\r\n" % status # 响应头 response_header = "" # 遍历头部信息 for header in headers: # 拼接多个响应头 response_header += "%s: %s\r\n" % header response_data = (response_line + response_header + "\r\n" + response_body).encode("utf-8") # 发送数据 new_socket.send(response_data) # 关闭socket new_socket.close() else: """这里是静态资源请求""" try: # 动态打开指定文件 with open("static" + request_path, "rb") as file: # 读取文件数据 file_data = file.read() except Exception as e: # 请求资源不存在,返回404数据 # 响应行 response_line = "HTTP/1.1 404 Not Found\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" with open("static/error.html", "rb") as file: file_data = file.read() # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) else: # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) finally: # 关闭服务与客户端的套接字 new_socket.close() def start(self): while True: # 等待接受客户端的连接请求 new_socket, ip_port = self.tcp_server_socket.accept() sub_thread = threading.Thread(target=self.handle_client_quest, args=(new_socket,)) # 设置守护线程 sub_thread.setDaemon(True) sub_thread.start()# 程序入口函数def main(): # 获取命令行参数判断长度 if len(sys.argv) != 2: print("执行命令如下: python3 xxx.py 9000") return # 判断端口号是否是数字 if not sys.argv[1].isdigit(): print("执行命令如下: python3 xxx.py 9000") return # 需要转成int类型 port = int(sys.argv[1]) # 创建web服务器 web_server = HttpWebServer(port) # 启动web服务器 web_server.start()if __name__ == '__main__': main()
web框架程序(framework.py)代码:
"""miniweb框架,负责处理动态资源请求"""import time# 获取首页数据def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 处理后的数据 data = time.ctime() return status, response_header, data# 没有找到动态资源def not_found(): # 响应状态 status = "404 Not Found"; # 响应头 response_header = [("Server", "PWS2.0")] # 处理后的数据 data = "not found" return status, response_header, data# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) if request_path == "/index.html": # 获取首页数据 result = index() return result else: # 没有找到动态资源 result = not_found() return result
framework.py示例代码:
# 获取首页数据def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read()
framework.py示例代码:
# 获取首页数据def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 1. 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 2. 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result
接着上面程序的判断场景,假如咱们再处理一个个人中心的动态资源请求非常简单,再添加一个函数和更加一个分支判断就可以实现了。
framework.py 示例代码:
# 获取个人中心数据def center(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/center.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) if request_path == "/index.html": # 获取首页数据 result = index() return result elif request_path == "/center.html": # 获取个人中心数据 result = center() return result else: # 没有找到动态资源 result = not_found() return result
那如果咱们的框架处理的页面请求路径再多一些,比如:5个路径判断,大家可能感觉条件分支完全可以胜任,如果是40个甚至更多呢? 如果这是还是用普通的条件分支简直无法忍受。
解决办法: 可以使用路由
什么是路由?
路由就是请求的URL到处理函数的映射,也就是说提前把请求的URL和处理函数关联好。
路由列表
这么多的路由如何管理呢, 可以使用一个路由列表进行管理,通过路由列表保存每一个路由。
请求路径 | 处理函数 |
---|---|
/login.html | login函数 |
/index.html | index函数 |
/center.html | center函数 |
framework.py 示例代码:
# 定义路由列表route_list = [ ("/index.html", index), ("/center.html", center)]
framework.py 示例代码:
# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: # 没有找到动态资源 result = not_found() return result # if request_path == "/index.html": # # 获取首页数据 # result = index() # return result # elif request_path == "/center.html": # # 获取个人中心数据 # result = center() # return result # else: # # 没有找到动态资源 # result = not_found() # return result
前面我们已经实现了路由列表,但是每次添加路由都需要手动添加来完成,接下来我们想要完成路由的自动添加,可以通过装饰器来实现,在使用装饰器对处理函数进行装饰的时候我们需要知道装饰的函数和那个请求路径进行关联,也就是说装饰器需要接收一个url参数,这样我们定义的装饰器是一个带有参数的装饰器。
示例代码:
"""miniweb框架,负责处理动态资源请求"""import time# 定义路由列表route_list = []# 定义带有参数的装饰器def route(path): # 装饰器 def decorator(func): # 当执行装饰器装饰指定函数的时候,把路径和函数添加到路由列表 route_list.append((path, func)) def inner(): # 执行指定函数 return func() return inner # 返回装饰器 return decorator# 获取首页数据@route("/index.html")def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result# 获取个人中心数据@route("/center.html")def center(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/center.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result# 没有找到动态资源def not_found(): # 响应状态 status = "404 Not Found"; # 响应头 response_header = [("Server", "PWS2.0")] # 处理后的数据 data = "not found" return status, response_header, data# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: # 没有找到动态资源 result = not_found() return result
-- 创建数据库create database stock_db charset=utf8;-- 切换数据库use stock_db;-- 执行sql文件source stock_db.sql;
示例代码:
# 获取首页数据@route("/index.html")def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = "select * from info;" # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() print(result)
示例代码:
# 获取首页数据@route("/index.html")def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = "select * from info;" # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() print(result) data = "" for row in result: data += '''<tr> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td> </tr>''' % row # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result
# 个人中心数据接口开发@route("/center_data.html")def center_data(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0"), ("Content-Type", "text/html;charset=utf-8")] conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = '''select i.code, i.short, i.chg, i.turnover, i.price, i.highs, f.note_info from info as i inner join focus as f on i.id = f.info_id;''' # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() # 关闭游标 cursor.close() # 关闭数据库连接 conn.close() print(result)
# 个人中心数据接口开发@route("/center_data.html")def center_data(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0"), ("Content-Type", "text/html;charset=utf-8")] conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = '''select i.code, i.short, i.chg, i.turnover, i.price, i.highs, f.note_info from info as i inner join focus as f on i.id = f.info_id;''' # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() # 关闭游标 cursor.close() # 关闭数据库连接 conn.close() # 个人中心数据列表 center_data_list = list() # 遍历每一行数据转成字典 for row in result: # 创建空的字典 center_dict = dict() center_dict["code"] = row[0] center_dict["short"] = row[1] center_dict["chg"] = row[2] center_dict["turnover"] = row[3] center_dict["price"] = str(row[4]) center_dict["highs"] = str(row[5]) center_dict["note_info"] = row[6] # 添加每个字典信息 center_data_list.append(center_dict) # 把列表字典转成json字符串, 并在控制台显示 json_str = json.dumps(center_data_list,ensure_ascii=False) print(json_str) return status, response_header, json_str
代码说明:
# 获取个人中心数据@route("/center.html")def center(): # 响应状态 status = "200 OK" # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/center.html", "r") as file: file_data = file.read() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", "") return status, response_header, result
// 发送ajax请求获取个人中心页面数据// 路径写成 center_data.html,发送ajax的时候路径其实是http://ip地址:端口号/center.data.html$.get("center_data.html", function (data) { alert(data); }}, "json");
// 发送ajax请求获取个人中心页面数据$.get("center_data.html", function (data) { var data_array = data; // 获取table标签对象 var $table = $(".table") for(var i = 0; i < data_array.length; i++){ // 获取每一条对象 var center_obj = data_array[i]; var row_html = '<tr>' + '<td>'+ center_obj.code +'</td>' + '<td>'+ center_obj.short +'</td>' + '<td>'+ center_obj.chg +'</td>' + '<td>'+ center_obj.turnover +'</td>' + '<td>'+ center_obj.price +'</td>' + '<td>'+ center_obj.highs +'</td>' + '<td>'+ center_obj.note_info +'</td>' + '<td><a type="button" class="btn btn-default btn-xs" href="/update/000007.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a></td><td><input type="button" value="删除" id="toDel" name="toDel" systemidvaule="000007"></td></tr>'; // 为table标签添加每一行组装的html数据 $table.append(row_html); }}, "json");
在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在咱们python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?
可以使用 logging 这个包来完成
记录程序日志信息的目的是:
日志等级可以分为5个,从低到高分别是:
日志等级说明:
在 logging 包中记录日志的方式有两种:
日志信息输出到控制台的示例代码:
import logginglogging.debug('这是一个debug级别的日志信息')logging.info('这是一个info级别的日志信息')logging.warning('这是一个warning级别的日志信息')logging.error('这是一个error级别的日志信息')logging.critical('这是一个critical级别的日志信息')
运行结果:
WARNING:root:这是一个warning级别的日志信息ERROR:root:这是一个error级别的日志信息CRITICAL:root:这是一个critical级别的日志信息
说明:
logging日志等级和输出格式的设置:
import logging# 设置日志等级和输出日志格式logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')logging.debug('这是一个debug级别的日志信息')logging.info('这是一个info级别的日志信息')logging.warning('这是一个warning级别的日志信息')logging.error('这是一个error级别的日志信息')logging.critical('这是一个critical级别的日志信息')
运行结果:
2019-02-13 20:41:33,080 - hello.py[line:6] - DEBUG: 这是一个debug级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:7] - INFO: 这是一个info级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:8] - WARNING: 这是一个warning级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:9] - ERROR: 这是一个error级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:10] - CRITICAL: 这是一个critical级别的日志信息
代码说明:
日志信息保存到日志文件的示例代码:
import logginglogging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s', filename="log.txt", filemode="w")logging.debug('这是一个debug级别的日志信息')logging.info('这是一个info级别的日志信息')logging.warning('这是一个warning级别的日志信息')logging.error('这是一个error级别的日志信息')logging.critical('这是一个critical级别的日志信息')
web.py 程序使用logging日志示例:
程序入口模块设置logging日志的设置
import socket import threading import sys import framework import logging # logging日志的配置 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s', filename="log.txt", filemode="w")
INFO级别的日志输出,示例代码:
# 判断是否是动态资源请求 if request_path.endswith(".html"): """这里是动态资源请求,把请求信息交给框架处理""" logging.info("动态资源请求:" + request_path) ... else: """这里是静态资源请求""" logging.info("静态资源请求:" + request_path) ...
WARNING级别的日志输出,示例代码:
# 获取命令行参数判断长度 if len(sys.argv) != 2: print("执行命令如下: python3 xxx.py 9000") logging.warning("用户在命令行启动程序参数个数不正确!") return # 判断端口号是否是数字 if not sys.argv[1].isdigit(): print("执行命令如下: python3 xxx.py 9000") logging.warning("用户在命令行启动程序参数不是数字字符串!") return
framework.py 程序使用logging日志示例:
ERROR级别的日志输出,示例代码:
# 处理动态资源请求 def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: logging.error("没有设置相应的路由:" + request_path) # 没有找到动态资源 result = not_found() return result
说明:
framework.py 程序使用logging日志示例:
ERROR级别的日志输出,示例代码:
# 处理动态资源请求 def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: logging.error("没有设置相应的路由:" + request_path) # 没有找到动态资源 result = not_found() return result
说明:
安装MySQL:
sudo pacman -S mysql
修改配置文件:
sudo vim /etc/mysql/my.cnf
末行添加:
skip-grant-tables
终端输入:
$ mysql
修改密码,把passwd替换成想要修改的密码:mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'passwd';ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement报错的解决方法:mysql> flush privileges;再次修改密码:mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'passwd';Query OK, 0 rows affected (0.03 sec)刷新MySQL的系统权限相关表:mysql> flush privileges;Query OK, 0 rows affected (0.02 sec)退出MySQL:mysql> quitBye
注释最后一行
sudo vim /etc/mysql/my.cnf # skip-grant-tables 或者直接删除
重启mysql
sudo systemctl restart mysqld.service
连接mysql测试一下是否成功:
~ >>> mysql -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 10Server version: 8.0.21 Source distributionCopyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> quitBye
Welcome to the MySQL monitor.代表已成功登录MySQL;
刚安装完mysql时是默认没有密码的,终端直接输入mysql可以直接进入了
按照以下步骤输入:
1. ALTER USER 'root'@'localhost' IDENTIFIED BY 'password' PASSWORD EXPIRE NEVER;2. ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';3. FLUSH PRIVILEGES;4. alter user 'root'@'localhost' identified by '123456';重置密码用户名为root的密码
安装mysql:yum install mysql mysql-devel -y查看是否安装成功:yum list mysql-server
修改密码:
$ mysqlmysql> use mysql;mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';mysql> FLUSH PRIVILEGES;mysql> quit
]]>我们知道当函数调用完,函数内定义的变量都销毁了,但是我们有时候需要保存函数内的这个变量,每次在这个变量的基础上完成一些列的操作,比如: 每次在这个变量的基础上和其它数字进行求和计算,那怎么办呢?
我们就可以通过咱们今天学习的闭包来解决这个需求。
闭包的定义:
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
通过闭包的定义,我们可以得知闭包的形成条件:
# 定义一个外部函数def func_out(num1): # 定义一个内部函数 def func_inner(num2): # 内部函数使用了外部函数的变量(num1) result = num1 + num2 print("结果是:", result) # 外部函数返回了内部函数,这里返回的内部函数就是闭包 return func_inner# 创建闭包实例 f = func_out(1)# 执行闭包f(2)f(3)
运行结果:
结果是: 3结果是: 4
闭包执行结果的说明:
通过上面的输出结果可以看出闭包保存了外部函数内的变量num1,每次执行闭包都是在num1 = 1 基础上进行计算。
注意点:
需求: 根据配置信息使用闭包实现不同人的对话信息,例如对话:
张三: 到北京了吗?
李四: 已经到了,放心吧。
# 外部函数def config_name(name): # 内部函数 def say_info(info): print(name + ": " + info) return say_infotom = config_name("Tom")tom("你好!")tom("你好, 在吗?")jerry = config_name("jerry")jerry("不在!")
运行结果:
Tom: 你好!Tom: 你好, 在吗?jerry: 不在!
闭包案例说明:
修改闭包内使用的外部变量的错误示例:
# 定义一个外部函数def func_out(num1): # 定义一个内部函数 def func_inner(num2): # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1 num1 = 10 # 内部函数使用了外部函数的变量(num1) result = num1 + num2 print("结果是:", result) print(num1) func_inner(1) print(num1) # 外部函数返回了内部函数,这里返回的内部函数就是闭包 return func_inner# 创建闭包实例f = func_out(1)# 执行闭包f(2)
修改闭包内使用的外部变量的正确示例:
# 定义一个外部函数def func_out(num1): # 定义一个内部函数 def func_inner(num2): # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1 nonlocal num1 # 告诉解释器,此处使用的是 外部变量a # 修改外部变量num1 num1 = 10 # 内部函数使用了外部函数的变量(num1) result = num1 + num2 print("结果是:", result) print(num1) func_inner(1) print(num1) # 外部函数返回了内部函数,这里返回的内部函数就是闭包 return func_inner# 创建闭包实例f = func_out(1)# 执行闭包f(2)
就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
装饰器的功能特点:
# 添加一个登录验证的功能def check(fn): def inner(): print("请先登录....") fn() return innerdef comment(): print("发表评论")# 使用装饰器来装饰函数comment = check(comment)comment()# 装饰器的基本雏形# def decorator(fn): # fn:目标函数.# def inner():# '''执行函数之前'''# fn() # 执行被装饰的函数# '''执行函数之后'''# return inner
代码说明:
执行结果:
请先登录....发表评论
如果有多个函数都需要添加登录验证的功能,每次都需要编写func = check(func)这样代码对已有函数进行装饰,这种做法还是比较麻烦。
Python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰
# 添加一个登录验证的功能def check(fn): print("装饰器函数执行了") def inner(): print("请先登录....") fn() return inner# 使用语法糖方式来装饰函数@checkdef comment(): print("发表评论")comment()
说明:
执行结果:
请先登录....发表评论
装饰器本质上就是一个闭包函数,它可以对已有函数进行额外的功能扩展。
装饰器的语法格式:
# 装饰器# def decorator(fn): # fn:被装饰的目标函数.# def inner():# '''执行函数之前'''# fn() # 执行被装饰的目标函数# '''执行函数之后'''# return inner
装饰器的语法糖用法: @装饰器名称,同样可以完成对已有函数的装饰操作。
import time# 装饰器函数def get_time(func): def inner(): begin = time.time() func() end = time.time() print("函数执行花费%f" % (end-begin)) return inner@get_timedef func1(): for i in range(100000): print(i)func1()
执行结果:
...9999599996999979999899999函数执行花费0.329066
# 添加输出日志的功能def logging(fn): def inner(num1, num2): print("--正在努力计算--") fn(num1, num2) return inner# 使用装饰器装饰函数@loggingdef sum_num(a, b): result = a + b print(result)sum_num(1, 2)
运行结果:
--正在努力计算--3
# 添加输出日志的功能def logging(fn): def inner(num1, num2): print("--正在努力计算--") result = fn(num1, num2) return result return inner# 使用装饰器装饰函数@loggingdef sum_num(a, b): result = a + b return resultresult = sum_num(1, 2)print(result)
运行结果:
--正在努力计算--3
# 添加输出日志的功能def logging(fn): def inner(*args, **kwargs): print("--正在努力计算--") fn(*args, **kwargs) return inner# 使用语法糖装饰函数@loggingdef sum_num(*args, **kwargs): result = 0 for value in args: result += value for value in kwargs.values(): result += value print(result)sum_num(1, 2, a=10)
运行结果:
--正在努力计算--13
# 添加输出日志的功能def logging(fn): def inner(*args, **kwargs): print("--正在努力计算--") result = fn(*args, **kwargs) return result return inner# 使用语法糖装饰函数@loggingdef sum_num(*args, **kwargs): result = 0 for value in args: result += value for value in kwargs.values(): result += value return result@loggingdef subtraction(a, b): result = a - b print(result)result = sum_num(1, 2, a=10)print(result)subtraction(4, 2)
运行结果:
--正在努力计算--13--正在努力计算--2
通用装饰器的语法格式:
# 通用装饰器def logging(fn): def inner(*args, **kwargs): print("--正在努力计算--") result = fn(*args, **kwargs) return result return inner
def make_div(func): """对被装饰的函数的返回值 div标签""" def inner(): return "<div>" + func() + "</div>" return innerdef make_p(func): """对被装饰的函数的返回值 p标签""" def inner(): return "<p>" + func() + "</p>" return inner# 装饰过程: 1 content = make_p(content) 2 content = make_div(content)# content = make_div(make_p(content))@make_div@make_pdef content(): return "人生苦短"result = content()print(result)
代码说明:
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,…)
正确写法:
在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器,因为@符号后面必须是装饰器实例。
# 添加输出日志的功能def logging(flag): def decorator(fn): def inner(num1, num2): if flag == "+": print("--正在努力加法计算--") elif flag == "-": print("--正在努力减法计算--") result = fn(num1, num2) return result return inner # 返回装饰器 return decorator# 使用装饰器装饰函数@logging("+")def add(a, b): result = a + b return result@logging("-")def sub(a, b): result = a - b return resultresult = add(1, 2)print(result)result = sub(1, 2)print(result)
装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。
类装饰器示例代码:
class Check(object): def __init__(self, fn): # 初始化操作在此完成 self.__fn = fn # 实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。 def __call__(self, *args, **kwargs): # 添加装饰功能 print("请先登陆...") self.__fn()@Checkdef comment(): print("发表评论")comment()
代码说明:
执行结果:
请先登陆...发表评论
property属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用。
定义property属性有两种方式
class Person(object): def __init__(self): self.__age = 0 # 装饰器方式的property, 把age方法当做属性使用, 表示当获取属性时会执行下面修饰的方法 @property def age(self): return self.__age # 把age方法当做属性使用, 表示当设置属性时会执行下面修饰的方法 @age.setter def age(self, new_age): if new_age >= 150: print("成精了") else: self.__age = new_age# 创建personp = Person()print(p.age)p.age = 100print(p.age)p.age = 1000
运行结果:
0100成精了
代码说明:
class Person(object): def __init__(self): self.__age = 0 def get_age(self): """当获取age属性的时候会执行该方法""" return self.__age def set_age(self, new_age): """当设置age属性的时候会执行该方法""" if new_age >= 150: print("成精了") else: self.__age = new_age # 类属性方式的property属性 age = property(get_age, set_age)# 创建personp = Person()print(p.age)p.age = 100print(p.age)p.age = 1000
运行结果:
0100成精了
代码说明:
基础班向文件中写入数据的示例代码:
# 1、以写的方式打开文件 f = open("1.txt", "w") # 2、写入文件内容 f.write("hello world") # 3、关闭文件 f.close()
代码说明:
这种写法可能出现一定的安全隐患,错误代码如下:
# 1、以读的方式打开文件 f = open("1.txt", "r") # 2、读取文件内容 f.write("hello world") # 3、关闭文件 f.close()
运行结果:
Traceback (most recent call last): File "/home/python/Desktop/test/xxf.py", line 4, in <module> f.write("hello world")io.UnsupportedOperation: not writable
代码说明:
安全写法, 代码如下:
try: # 1、以读的方式打开文件 f = open("1.txt", "r") # 2、读取文件内容 f.write("xxxxx")except IOError as e: print("文件操作出错", e)finally: # 3、关闭文件 f.close()
运行结果:
文件操作出错 not writable
这种方法虽然代码运行良好,但是缺点就是代码过于冗长,并且需要添加try-except-finally语句,不是很方便,也容易忘记.
在这种情况下,Python提供了 with 语句的这种写法,既简单又安全,并且 with 语句执行完成以后自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作。
with 语句的示例代码:
# 1、以写的方式打开文件with open("1.txt", "w") as f: # 2、读取文件内容 f.write("hello world")
一个类只要实现了__enter__()和__exit__()
这个两个方法,通过该类创建的对象我们就称之为上下文管理器。
上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建的文件对象就是就是一个上下文管理器对象。
自定义上下文管理器类,模拟文件操作:
定义一个File类,实现 __enter__() 和 __exit__()
方法,然后使用 with 语句来完成操作文件, 示例代码:
class File(object): # 初始化方法 def __init__(self, file_name, file_model): # 定义变量保存文件名和打开模式 self.file_name = file_name self.file_model = file_model # 上文方法 def __enter__(self): print("进入上文方法") # 返回文件资源 self.file = open(self.file_name,self.file_model) return self.file # 下文方法 def __exit__(self, exc_type, exc_val, exc_tb): print("进入下文方法") self.file.close()if __name__ == '__main__': # 使用with管理文件 with File("1.txt", "r") as file: file_data = file.read() print(file_data)
运行结果:
进入上文方法hello world进入下文方法
代码说明:
__enter__
表示上文方法,需要返回一个操作文件对象__exit__
表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法。根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束。数据不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存。
生成器推导式:
# 创建生成器my_generator = (i * 2 for i in range(5))print(my_generator)# next获取生成器下一个值# value = next(my_generator)# print(value)# 遍历生成器for value in my_generator: print(value)
代码说明:
运行结果:
<generator object <genexpr> at 0x101367048>02468
yield 关键字:
def mygenerater(n): for i in range(n): print('开始生成...') yield i print('完成一次...')if __name__ == '__main__': g = mygenerater(2) # 获取生成器中下一个值 # result = next(g) # print(result) # while True: # try: # result = next(g) # print(result) # except StopIteration as e: # break # # for遍历生成器, for 循环内部自动处理了停止迭代异常,使用起来更加方便 for i in g: print(i)
代码说明:
运行结果:
开始生成...0完成一次...开始生成...1完成一次...
数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
现在我们使用生成器来实现这个斐波那契数列,每次取值都通过算法来生成下一个数据, 生成器每次调用只生成一个数据,可以节省大量的内存。
def fibonacci(num): a = 0 b = 1 # 记录生成fibonacci数字的下标 current_index = 0 while current_index < num: result = a a, b = b, a + b current_index += 1 # 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行 yield resultfib = fibonacci(5)# 遍历生成的数据for value in fib: print(value)
运行结果:
01123
copy函数是浅拷贝,只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象。
不可变类型的浅拷贝示例代码:
import copy # 使用浅拷贝需要导入copy模块# 不可变类型有: 数字、字符串、元组a1 = 123123b1 = copy.copy(a1) # 使用copy模块里的copy()函数就是浅拷贝了# 查看内存地址print(id(a1))print(id(b1))print("-" * 10)a2 = "abc"b2 = copy.copy(a2)# 查看内存地址print(id(a2))print(id(b2))print("-" * 10)a3 = (1, 2, ["hello", "world"])b3 = copy.copy(a3)# 查看内存地址print(id(a3))print(id(b3))
运行结果:
140459558944048140459558944048----------140459558648776140459558648776----------140459558073328140459558073328
不可变类型的浅拷贝说明:
可变类型的浅拷贝示例代码:
import copy # 使用浅拷贝需要导入copy模块# 可变类型有: 列表、字典、集合a1 = [1, 2]b1 = copy.copy(a1) # 使用copy模块里的copy()函数就是浅拷贝了# 查看内存地址print(id(a1))print(id(b1))print("-" * 10)a2 = {"name": "张三", "age": 20}b2 = copy.copy(a2)# 查看内存地址print(id(a2))print(id(b2))print("-" * 10)a3 = {1, 2, "王五"}b3 = copy.copy(a3)# 查看内存地址print(id(a3))print(id(b3))print("-" * 10)a4 = [1, 2, [4, 5]]# 注意:浅拷贝只会拷贝父对象,不会对子对象进行拷贝b4 = copy.copy(a4) # 使用copy模块里的copy()函数就是浅拷贝了# 查看内存地址print(id(a4))print(id(b4))print("-" * 10)# 查看内存地址print(id(a4[2]))print(id(b4[2]))# 修改数据a4[2][0] = 6# 子对象的数据会受影响print(a4)print(b4)
运行结果:
139882899585608139882899585800----------139882919626432139882919626504----------139882919321672139882899616264----------139882899587016139882899586952----------139882899693640139882899693640[1, 2, [6, 5]][1, 2, [6, 5]]
可变类型的浅拷贝说明:
deepcopy函数是深拷贝, 只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。
不可变类型的深拷贝示例代码:
import copy # 使用深拷贝需要导入copy模块# 不可变类型有: 数字、字符串、元组a1 = 1b1 = copy.deepcopy(a1) # 使用copy模块里的deepcopy()函数就是深拷贝了# 查看内存地址print(id(a1))print(id(b1))print("-" * 10)a2 = "张三"b2 = copy.deepcopy(a2)# 查看内存地址print(id(a2))print(id(b2))print("-" * 10)a3 = (1, 2)b3 = copy.deepcopy(a3)# 查看内存地址print(id(a3))print(id(b3))print("-" * 10)# 注意: 元组里面要是有可变类型对象,发现对象有可变类型就会该对象到最后一个可变类型的每一层对象进行拷贝a4 = (1, ["李四"])b4 = copy.deepcopy(a4)# 查看内存地址print(id(a4))print(id(b4))# 元组里面的可变类型子对象也会进行拷贝print(id(a4[1]))print(id(b4[1]))
运行结果:
92891209289120----------140115621848320140115621848320----------140115621859592140115621859592----------140115602480584140115621834568140115602328136140115602436168
不可变类型的深拷贝说明:
可变类型的深拷贝示例代码:
import copy # 使用深拷贝需要导入copy模块# 可变类型有: 列表、字典、集合a1 = [1, 2]b1 = copy.deepcopy(a1) # 使用copy模块里的deepcopy()函数就是深拷贝了# 查看内存地址print(id(a1))print(id(b1))print("-" * 10)a2 = {"name": "张三"}b2 = copy.deepcopy(a2)# 查看内存地址print(id(a2))print(id(b2))print("-" * 10)a3 = {1, 2}b3 = copy.deepcopy(a3)# 查看内存地址print(id(a3))print(id(b3))print("-" * 10)a4 = [1, 2, ["李四", "王五"]]b4 = copy.deepcopy(a4) # 使用copy模块里的deepcopy()函数就是深拷贝了# 查看内存地址print(id(a4))print(id(b4))# 查看内存地址print(id(a4[2]))print(id(b4[2]))a4[2][0] = "王五"# 因为列表的内存地址不同,所以数据不会收到影响print(a4)print(b4)
运行结果:
140348291721736140348291721928----------140348311762624140348311221592----------140348311457864140348291752456----------140348291723080140348291723144140348291723208140348291723016[1, 2, ['王五', '王五']][1, 2, ['李四', '王五']]
可变类型的深拷贝说明:
在实际开发过程中经常会有查找符合某些复杂规则的字符串的需要,比如:邮箱、图片地址、手机号码等,这时候想匹配或者查找符合某些规则的字符串就可以使用正则表达式了。
正则表达式就是记录文本规则的代码
0\d{2}-\d{8} 这个就是一个正则表达式,表达的意思是匹配的是座机号码
学习目标
在Python中需要通过正则表达式对字符串进行匹配的时候,可以使用一个 re 模块
# 导入re模块import re# 使用match方法进行匹配操作result = re.match(正则表达式,要匹配的字符串)# 如果上一步匹配到数据的话,可以使用group方法来提取数据result.group()
import re# 使用match方法进行匹配操作result = re.match("itcast","itcast.cn")# 获取匹配结果info = result.group()print(info)
运行结果:
itcast
学习目标
在上一小节中,了解到通过re模块能够完成使用正则表达式来匹配字符串
本小节,将要讲解正则表达式的单字符匹配
代码 | 功能 |
---|---|
. | 匹配任意1个字符(除了\n) |
[ ] | 匹配[ ]中列举的字符 |
\d | 匹配数字,即0-9 |
\D | 匹配非数字,即不是数字 |
\s | 匹配空白,即 空格,tab键 |
\S | 匹配非空白 |
\w | 匹配非特殊字符,即a-z、A-Z、0-9、_、汉字 |
\W | 匹配特殊字符,即非字母、非数字、非汉字 |
import reret = re.match(".","M")print(ret.group())ret = re.match("t.o","too")print(ret.group())ret = re.match("t.o","two")print(ret.group())
运行结果:
Mtootwo
import re# 如果hello的首字符小写,那么正则表达式需要小写的hret = re.match("h","hello Python") print(ret.group())# 如果hello的首字符大写,那么正则表达式需要大写的Hret = re.match("H","Hello Python") print(ret.group())# 大小写h都可以的情况ret = re.match("[hH]","hello Python")print(ret.group())ret = re.match("[hH]","Hello Python")print(ret.group())ret = re.match("[hH]ello Python","Hello Python")print(ret.group())# 匹配0到9第一种写法ret = re.match("[0123456789]Hello Python","7Hello Python")print(ret.group())# 匹配0到9第二种写法ret = re.match("[0-9]Hello Python","7Hello Python")print(ret.group())ret = re.match("[0-35-9]Hello Python","7Hello Python")print(ret.group())# 下面这个正则不能够匹配到数字4,因此ret为Noneret = re.match("[0-35-9]Hello Python","4Hello Python")# print(ret.group())
运行结果:
hHhHHello Python7Hello Python7Hello Python7Hello Python
import re# 普通的匹配方式ret = re.match("嫦娥1号","嫦娥1号发射成功") print(ret.group())ret = re.match("嫦娥2号","嫦娥2号发射成功") print(ret.group())ret = re.match("嫦娥3号","嫦娥3号发射成功") print(ret.group())# 使用\d进行匹配ret = re.match("嫦娥\d号","嫦娥1号发射成功") print(ret.group())ret = re.match("嫦娥\d号","嫦娥2号发射成功") print(ret.group())ret = re.match("嫦娥\d号","嫦娥3号发射成功") print(ret.group())
运行结果:
嫦娥1号嫦娥2号嫦娥3号嫦娥1号嫦娥2号嫦娥3号
import rematch_obj = re.match("\D", "f")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
运行结果:
f
import re# 空格属于空白字符match_obj = re.match("hello\sworld", "hello world")if match_obj: result = match_obj.group() print(result)else: print("匹配失败")# \t 属于空白字符match_obj = re.match("hello\sworld", "hello\tworld")if match_obj: result = match_obj.group() print(result)else: print("匹配失败")
运行结果:
hello worldhello world
import rematch_obj = re.match("hello\Sworld", "hello&world")if match_obj:result = match_obj.group()print(result)else:print("匹配失败")match_obj = re.match("hello\Sworld", "hello$world")if match_obj:result = match_obj.group()print(result)else:print("匹配失败")
运行结果:
hello&world hello$world
import re# 匹配非特殊字符中的一位match_obj = re.match("\w", "A")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
执行结果:
A
# 匹配特殊字符中的一位match_obj = re.match("\W", "&")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
执行结果:
&
代码 | 功能 |
---|---|
* | 匹配前一个字符出现0次或者无限次,即可有可无 |
+ | 匹配前一个字符出现1次或者无限次,即至少有1次 |
? | 匹配前一个字符出现1次或者0次,即要么有1次,要么没有 |
{m} | 匹配前一个字符出现m次 |
{m,n} | 匹配前一个字符出现从m到n次 |
需求:匹配出一个字符串第一个字母为大小字符,后面都是小写字母并且这些小写字母可 有可无
import reret = re.match("[A-Z][a-z]*","M")print(ret.group())ret = re.match("[A-Z][a-z]*","MnnM")print(ret.group())ret = re.match("[A-Z][a-z]*","Aabcdef")print(ret.group())
运行结果:
MMnnAabcdef
需求:匹配一个字符串,第一个字符是t,最后一个字符串是o,中间至少有一个字符
import rematch_obj = re.match("t.+o", "two")if match_obj: print(match_obj.group())else: print("匹配失败")
运行结果:
two
需求:匹配出这样的数据,但是https 这个s可能有,也可能是http 这个s没有
import rematch_obj = re.match("https?", "http")if match_obj: print(match_obj.group())else: print("匹配失败")
运行结果:
https
需求:匹配出,8到20位的密码,可以是大小写英文字母、数字、下划线
import reret = re.match("[a-zA-Z0-9_]{6}","12a3g45678")print(ret.group())ret = re.match("[a-zA-Z0-9_]{8,20}","1ad12f23s34455ff66")print(ret.group())
运行结果:
12a3g41ad12f23s34455ff66
代码 | 功能 |
---|---|
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
需求:匹配以数字开头的数据
import re# 匹配以数字开头的数据match_obj = re.match("^\d.*", "3hello")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
运行结果:
3hello
需求: 匹配以数字结尾的数据
import re# 匹配以数字结尾的数据match_obj = re.match(".*\d$", "hello5")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
运行结果:
hello5
需求: 匹配以数字开头中间内容不管以数字结尾
match_obj = re.match("^\d.*\d$", "4hello4")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
运行结果:
4hello4
需求: 第一个字符除了aeiou的字符都匹配
import rematch_obj = re.match("[^aeiou]", "h")if match_obj: # 获取匹配结果 print(match_obj.group())else: print("匹配失败")
执行结果
h
代码 | 功能 |
---|---|
| | 匹配左右任意一个表达式 |
(ab) | 将括号中字符作为一个分组 |
\num | 引用分组num匹配到的字符串 |
(?P<name>) | 分组起别名 |
(?P=name) | 引用别名为name分组匹配到的字符串 |
需求:在列表中[“apple”, “banana”, “orange”, “pear”],匹配apple和pear
import re# 水果列表fruit_list = ["apple", "banana", "orange", "pear"]# 遍历数据for value in fruit_list: # | 匹配左右任意一个表达式 match_obj = re.match("apple|pear", value) if match_obj: print("%s是我想要的" % match_obj.group()) else: print("%s不是我要的" % value)
执行结果:
apple是我想要的banana不是我要的orange不是我要的pear是我想要的
需求:匹配出163、126、qq等邮箱
import rematch_obj = re.match("[a-zA-Z0-9_]{4,20}@(163|126|qq|sina|yahoo)\.com", "hello@163.com")if match_obj: print(match_obj.group()) # 获取分组数据 print(match_obj.group(1))else: print("匹配失败")
执行结果:
hello@163.com163
需求: 匹配qq:10567这样的数据,提取出来qq文字和qq号码
import rematch_obj = re.match("(qq):([1-9]\d{4,10})", "qq:10567")if match_obj: print(match_obj.group()) # 分组:默认是1一个分组,多个分组从左到右依次加1 print(match_obj.group(1)) # 提取第二个分组数据 print(match_obj.group(2))else: print("匹配失败")
执行结果:
qq10567
需求:匹配出<html>hh</html>
match_obj = re.match("<[a-zA-Z1-6]+>.*</[a-zA-Z1-6]+>", "<html>hh</div>")if match_obj: print(match_obj.group())else: print("匹配失败")match_obj = re.match("<([a-zA-Z1-6]+)>.*</\\1>", "<html>hh</html>")if match_obj: print(match_obj.group())else: print("匹配失败")
运行结果:
<html>hh</div><html>hh</html>
需求:匹配出<html><h1>www.itcast.cn</h1></html>
match_obj = re.match("<([a-zA-Z1-6]+)><([a-zA-Z1-6]+)>.*</\\2></\\1>", "<html><h1>www.itcast.cn</h1></html>")if match_obj: print(match_obj.group())else: print("匹配失败")
运行结果:
<html><h1>www.itcast.cn</h1></html>
(?P<name>)
(?P=name)
需求:匹配出<html><h1>www.itcast.cn</h1></html>
match_obj = re.match("<(?P<name1>[a-zA-Z1-6]+)><(?P<name2>[a-zA-Z1-6]+)>.*</(?P=name2)></(?P=name1)>", "<html><h1>www.itcast.cn</h1></html>")if match_obj: print(match_obj.group())else: print("匹配失败")
运行结果:
<html><h1>www.itcast.cn</h1></html>
\num
表示引用分组num匹配到的字符串(?P<name>)
表示分组起别名使用web框架专门负责处理用户的动态资源请求,这个web框架其实就是一个为web服务器提供服务的应用程序,简称web框架。
关系说明:
不需要经常变化的资源,这种资源web服务器可以提前准备好,比如: png/jpg/css/js等文件。
和静态资源相反, 这种资源会经常变化,比如: 我们在京东浏览商品时经常会根据条件进行筛选,选择不同条件, 浏览的商品就不同,这种资源web服务器无法提前准备好,需要web框架来帮web服务器进行准备,在这里web服务器可以把.html的资源请求认为是动态资源请求交由web框架进行处理。
它是web服务器和web框架之间进行协同工作的一个规则,WSGI协议规定web服务器把动态资源的请求信息传给web框架处理,web框架把处理好的结果返回给web服务器。
web服务器程序(web.py)代码:
import socketimport threadingimport sysimport framework# 定义web服务器类class HttpWebServer(object): def __init__(self, port): # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", port)) # 设置监听 tcp_server_socket.listen(128) self.tcp_server_socket = tcp_server_socket # 处理客户的请求 @staticmethod def handle_client_quest(new_socket): # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) if len(recv_client_data) == 0: print("关闭浏览器了") # 关闭服务与客户端的套接字 new_socket.close() return # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) # 根据指定字符串进行分割, 最大分割次数指定2 request_list = recv_client_content.split(" ", maxsplit=2) # 获取请求资源路径 request_path = request_list[1] print(request_path) # 判断请求的是否是根目录,如果条件成立,指定首页数据返回 if request_path == "/": request_path = "/index.html" # 判断是否是动态资源请求 if request_path.endswith(".html"): """这里是动态资源请求,把请求信息交给框架处理""" # 字典存储用户的请求信息 env = { "request_path": request_path } # 获取处理结果 status, headers, response_body = framework.handle_request(env) # 使用框架处理的数据拼接响应报文 # 响应行 response_line = "HTTP/1.1 %s\r\n" % status # 响应头 response_header = "" # 遍历头部信息 for header in headers: # 拼接多个响应头 response_header += "%s: %s\r\n" % header response_data = (response_line + response_header + "\r\n" + response_body).encode("utf-8") # 发送数据 new_socket.send(response_data) # 关闭socket new_socket.close() else: """这里是静态资源请求""" try: # 动态打开指定文件 with open("static" + request_path, "rb") as file: # 读取文件数据 file_data = file.read() except Exception as e: # 请求资源不存在,返回404数据 # 响应行 response_line = "HTTP/1.1 404 Not Found\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" with open("static/error.html", "rb") as file: file_data = file.read() # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) else: # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) finally: # 关闭服务与客户端的套接字 new_socket.close() def start(self): while True: # 等待接受客户端的连接请求 new_socket, ip_port = self.tcp_server_socket.accept() sub_thread = threading.Thread(target=self.handle_client_quest, args=(new_socket,)) # 设置守护线程 sub_thread.setDaemon(True) sub_thread.start()# 程序入口函数def main(): # 获取命令行参数判断长度 if len(sys.argv) != 2: print("执行命令如下: python3 xxx.py 9000") return # 判断端口号是否是数字 if not sys.argv[1].isdigit(): print("执行命令如下: python3 xxx.py 9000") return # 需要转成int类型 port = int(sys.argv[1]) # 创建web服务器 web_server = HttpWebServer(port) # 启动web服务器 web_server.start()if __name__ == '__main__': main()
web框架程序(framework.py)代码:
"""miniweb框架,负责处理动态资源请求"""import time# 获取首页数据def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 处理后的数据 data = time.ctime() return status, response_header, data# 没有找到动态资源def not_found(): # 响应状态 status = "404 Not Found"; # 响应头 response_header = [("Server", "PWS2.0")] # 处理后的数据 data = "not found" return status, response_header, data# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) if request_path == "/index.html": # 获取首页数据 result = index() return result else: # 没有找到动态资源 result = not_found() return result
framework.py示例代码:
# 获取首页数据def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read()
framework.py示例代码:
# 获取首页数据def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 1. 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 2. 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result
接着上面程序的判断场景,假如咱们再处理一个个人中心的动态资源请求非常简单,再添加一个函数和更加一个分支判断就可以实现了。
framework.py 示例代码:
# 获取个人中心数据def center(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/center.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) if request_path == "/index.html": # 获取首页数据 result = index() return result elif request_path == "/center.html": # 获取个人中心数据 result = center() return result else: # 没有找到动态资源 result = not_found() return result
那如果咱们的框架处理的页面请求路径再多一些,比如:5个路径判断,大家可能感觉条件分支完全可以胜任,如果是40个甚至更多呢? 如果这是还是用普通的条件分支简直无法忍受。
解决办法: 可以使用路由
什么是路由?
路由就是请求的URL到处理函数的映射,也就是说提前把请求的URL和处理函数关联好。
路由列表
这么多的路由如何管理呢, 可以使用一个路由列表进行管理,通过路由列表保存每一个路由。
请求路径 | 处理函数 |
---|---|
/login.html | login函数 |
/index.html | index函数 |
/center.html | center函数 |
framework.py 示例代码:
# 定义路由列表route_list = [ ("/index.html", index), ("/center.html", center)]
framework.py 示例代码:
# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: # 没有找到动态资源 result = not_found() return result # if request_path == "/index.html": # # 获取首页数据 # result = index() # return result # elif request_path == "/center.html": # # 获取个人中心数据 # result = center() # return result # else: # # 没有找到动态资源 # result = not_found() # return result
前面我们已经实现了路由列表,但是每次添加路由都需要手动添加来完成,接下来我们想要完成路由的自动添加,可以通过装饰器来实现,在使用装饰器对处理函数进行装饰的时候我们需要知道装饰的函数和那个请求路径进行关联,也就是说装饰器需要接收一个url参数,这样我们定义的装饰器是一个带有参数的装饰器。
示例代码:
"""miniweb框架,负责处理动态资源请求"""import time# 定义路由列表route_list = []# 定义带有参数的装饰器def route(path): # 装饰器 def decorator(func): # 当执行装饰器装饰指定函数的时候,把路径和函数添加到路由列表 route_list.append((path, func)) def inner(): # 执行指定函数 return func() return inner # 返回装饰器 return decorator# 获取首页数据@route("/index.html")def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result# 获取个人中心数据@route("/center.html")def center(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/center.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 data = time.ctime() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result# 没有找到动态资源def not_found(): # 响应状态 status = "404 Not Found"; # 响应头 response_header = [("Server", "PWS2.0")] # 处理后的数据 data = "not found" return status, response_header, data# 处理动态资源请求def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: # 没有找到动态资源 result = not_found() return result
-- 创建数据库create database stock_db charset=utf8;-- 切换数据库use stock_db;-- 执行sql文件source stock_db.sql;
示例代码:
# 获取首页数据@route("/index.html")def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = "select * from info;" # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() print(result)
示例代码:
# 获取首页数据@route("/index.html")def index(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/index.html", "r") as file: file_data = file.read() # 处理后的数据, 从数据库查询 conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = "select * from info;" # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() print(result) data = "" for row in result: data += '''<tr> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%s</td> <td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td> </tr>''' % row # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", data) return status, response_header, result
# 个人中心数据接口开发@route("/center_data.html")def center_data(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0"), ("Content-Type", "text/html;charset=utf-8")] conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = '''select i.code, i.short, i.chg, i.turnover, i.price, i.highs, f.note_info from info as i inner join focus as f on i.id = f.info_id;''' # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() # 关闭游标 cursor.close() # 关闭数据库连接 conn.close() print(result)
# 个人中心数据接口开发@route("/center_data.html")def center_data(): # 响应状态 status = "200 OK"; # 响应头 response_header = [("Server", "PWS2.0"), ("Content-Type", "text/html;charset=utf-8")] conn = pymysql.connect(host="localhost", port=3306, user="root", password="mysql", database="stock_db", charset="utf8") # 获取游标 cursor = conn.cursor() # 查询sql语句 sql = '''select i.code, i.short, i.chg, i.turnover, i.price, i.highs, f.note_info from info as i inner join focus as f on i.id = f.info_id;''' # 执行sql cursor.execute(sql) # 获取结果集 result = cursor.fetchall() # 关闭游标 cursor.close() # 关闭数据库连接 conn.close() # 个人中心数据列表 center_data_list = list() # 遍历每一行数据转成字典 for row in result: # 创建空的字典 center_dict = dict() center_dict["code"] = row[0] center_dict["short"] = row[1] center_dict["chg"] = row[2] center_dict["turnover"] = row[3] center_dict["price"] = str(row[4]) center_dict["highs"] = str(row[5]) center_dict["note_info"] = row[6] # 添加每个字典信息 center_data_list.append(center_dict) # 把列表字典转成json字符串, 并在控制台显示 json_str = json.dumps(center_data_list,ensure_ascii=False) print(json_str) return status, response_header, json_str
代码说明:
# 获取个人中心数据@route("/center.html")def center(): # 响应状态 status = "200 OK" # 响应头 response_header = [("Server", "PWS2.0")] # 打开模板文件,读取数据 with open("template/center.html", "r") as file: file_data = file.read() # 替换模板文件中的模板遍历 result = file_data.replace("{%content%}", "") return status, response_header, result
// 发送ajax请求获取个人中心页面数据// 路径写成 center_data.html,发送ajax的时候路径其实是http://ip地址:端口号/center.data.html$.get("center_data.html", function (data) { alert(data); }}, "json");
// 发送ajax请求获取个人中心页面数据$.get("center_data.html", function (data) { var data_array = data; // 获取table标签对象 var $table = $(".table") for(var i = 0; i < data_array.length; i++){ // 获取每一条对象 var center_obj = data_array[i]; var row_html = '<tr>' + '<td>'+ center_obj.code +'</td>' + '<td>'+ center_obj.short +'</td>' + '<td>'+ center_obj.chg +'</td>' + '<td>'+ center_obj.turnover +'</td>' + '<td>'+ center_obj.price +'</td>' + '<td>'+ center_obj.highs +'</td>' + '<td>'+ center_obj.note_info +'</td>' + '<td><a type="button" class="btn btn-default btn-xs" href="/update/000007.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a></td><td><input type="button" value="删除" id="toDel" name="toDel" systemidvaule="000007"></td></tr>'; // 为table标签添加每一行组装的html数据 $table.append(row_html); }}, "json");
在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在咱们python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?
可以使用 logging 这个包来完成
记录程序日志信息的目的是:
日志等级可以分为5个,从低到高分别是:
日志等级说明:
在 logging 包中记录日志的方式有两种:
日志信息输出到控制台的示例代码:
import logginglogging.debug('这是一个debug级别的日志信息')logging.info('这是一个info级别的日志信息')logging.warning('这是一个warning级别的日志信息')logging.error('这是一个error级别的日志信息')logging.critical('这是一个critical级别的日志信息')
运行结果:
WARNING:root:这是一个warning级别的日志信息ERROR:root:这是一个error级别的日志信息CRITICAL:root:这是一个critical级别的日志信息
说明:
logging日志等级和输出格式的设置:
import logging# 设置日志等级和输出日志格式logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')logging.debug('这是一个debug级别的日志信息')logging.info('这是一个info级别的日志信息')logging.warning('这是一个warning级别的日志信息')logging.error('这是一个error级别的日志信息')logging.critical('这是一个critical级别的日志信息')
运行结果:
2019-02-13 20:41:33,080 - hello.py[line:6] - DEBUG: 这是一个debug级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:7] - INFO: 这是一个info级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:8] - WARNING: 这是一个warning级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:9] - ERROR: 这是一个error级别的日志信息2019-02-13 20:41:33,080 - hello.py[line:10] - CRITICAL: 这是一个critical级别的日志信息
代码说明:
日志信息保存到日志文件的示例代码:
import logginglogging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s', filename="log.txt", filemode="w")logging.debug('这是一个debug级别的日志信息')logging.info('这是一个info级别的日志信息')logging.warning('这是一个warning级别的日志信息')logging.error('这是一个error级别的日志信息')logging.critical('这是一个critical级别的日志信息')
web.py 程序使用logging日志示例:
程序入口模块设置logging日志的设置
import socket import threading import sys import framework import logging # logging日志的配置 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s', filename="log.txt", filemode="w")
INFO级别的日志输出,示例代码:
# 判断是否是动态资源请求 if request_path.endswith(".html"): """这里是动态资源请求,把请求信息交给框架处理""" logging.info("动态资源请求:" + request_path) ... else: """这里是静态资源请求""" logging.info("静态资源请求:" + request_path) ...
WARNING级别的日志输出,示例代码:
# 获取命令行参数判断长度 if len(sys.argv) != 2: print("执行命令如下: python3 xxx.py 9000") logging.warning("用户在命令行启动程序参数个数不正确!") return # 判断端口号是否是数字 if not sys.argv[1].isdigit(): print("执行命令如下: python3 xxx.py 9000") logging.warning("用户在命令行启动程序参数不是数字字符串!") return
framework.py 程序使用logging日志示例:
ERROR级别的日志输出,示例代码:
# 处理动态资源请求 def handle_request(env): # 获取动态请求资源路径 request_path = env["request_path"] print("接收到的动态资源请求:", request_path) # 遍历路由列表,选择执行的函数 for path, func in route_list: if request_path == path: result = func() return result else: logging.error("没有设置相应的路由:" + request_path) # 没有找到动态资源 result = not_found() return result
说明:
数据库就是存储和管理数据的仓库,数据按照一定的格式进行存储,用户可以对数据库中的数据进行增加、修改、删除、查询等操作。
关系型数据库:
是指采用了关系模型来组织数据的数据库,简单来说,关系模型指的就是二维表格模型,好比Excel文件中的表格,强调使用表格的方式存储数据。
关系型数据库中核心元素
常用的关系型数据库:
非关系型数据库:
非关系型数据库,又被称为NoSQL(Not Only SQL ),意为不仅仅是SQL,对NoSQL 最普遍的定义是“非关联型的”,强调 Key-Value 的方式存储数据。
常用的非关系型数据库:
数据库的作用就是存储和管理数据的,比如: 我们在京东网站上的浏览的商品列表数据,这些数据都会存储在数据库。
数据库管理系统(英语全拼:Relational Database Management System,简称RDBMS)是为管理关系型数据库而设计的软件系统,如果大家想要使用关系型数据库就需要安装数据库管理系统,其实就是一个应用软件。
关系型数据库管理系统可以分为:
关系型数据库服务端软件:
主要负责管理不同的数据库,而每个数据库里面会有一系列数据文件,数据文件是用来存储数据的, 其实数据库就是一系列数据文件的集合。
关系型数据库客户端软件:
主要负责和关系型数据库服务端软件进行通信, 向服务端传输数据或者从服务端获取数据.
说明:
SQL(Structured Query Language)是结构化查询语言,是一种用来操作RDBMS的数据库的语言。也就是说通过 SQL 可以操作 oracle,sql server,mysql,sqlite 等关系型的数据库。
SQL的作用是实现数据库客户端和数据库服务端之间的通信,SQL就是通信的桥梁。
SQL语言主要分为:
说明:
MySQL是一个关系型数据库管理系统,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件,它是由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品,MySQL 是最流行的关系型数据库管理系统中的一个。
MySQL的特点:
MySQL数据库服务端软件的安装:
在Ubuntu中打开终端,输入下面的命令:
sudo apt install mysql-server mysql-client
manjaro安装mysql:
sudo pacman -S mysql
centos 安装mysql
yum install mysql mysql-devel -y
查看MySQL服务状态:
sudo service mysql status
停止MySQL服务:
sudo service mysql stop
启动MySQL服务:
sudo service mysql start
重启MySQL服务:
sudo service mysql restart
MySQL配置文件的介绍:(ubutnu)
配置文件路径为: /etc/mysql/mysql.conf.d/mysqld.cnf
主要配置信息说明:
port表示端口号,默认为3306bind-address表示服务器绑定的ip,默认为127.0.0.1datadir表示数据库保存路径,默认为/var/lib/mysqllog_error表示错误日志,默认为/var/log/mysql/error.log
MySQL数据库客户端软件的安装:
客户端是程序员或者dba使用的软件,通过socket方式与服务端程序通信。
常用的MySQL数据库客户端软件有
mysql命令的使用帮助:
mysql --help
MySQL客户端的使用:
MySQL客户端连接MySQL服务端命令
mysql -uroot -p
说明:
数据类型是指在创建表的时候为表中字段指定数据类型,只有数据符合类型要求才能存储起来,使用数据类型的原则是:够用就行,尽量使用取值范围小的,而不用大的,这样可以更多的节省存储空间。
常用数据类型如下:
数据类型说明:
约束是指数据在数据类型限定的基础上额外增加的要求.
常见的约束如下:
类型 | 字节大小 | 有符号范围(Signed) | 无符号范围(Unsigned) |
---|---|---|---|
TINYINT | 1 | -128 ~ 127 | 0 ~ 255 |
SMALLINT | 2 | -32768 ~ 32767 | 0 ~ 65535 |
MEDIUMINT | 3 | -8388608 ~ 8388607 | 0 ~ 16777215 |
INT/INTEGER | 4 | -2147483648 ~2147483647 | 0 ~ 4294967295 |
BIGINT | 8 | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 |
类型 | 说明 | 使用场景 |
---|---|---|
CHAR | 固定长度,小型数据 | 身份证号、手机号、电话、密码 |
VARCHAR | 可变长度,小型数据 | 姓名、地址、品牌、型号 |
TEXT | 可变长度,字符个数大于 4000 | 存储小型文章或者新闻 |
LONGTEXT | 可变长度, 极大型文本数据 | 存储极大型文本数据 |
类型 | 字节大小 | 示例 |
---|---|---|
DATE | 4 | ‘2020-01-01’ |
TIME | 3 | ‘12:29:59’ |
DATETIME | 8 | ‘2020-01-01 12:29:59’ |
YEAR | 1 | ‘2017’ |
TIMESTAMP | 4 | ‘1970-01-01 00:00:01’ UTC ~ ‘2038-01-01 00:00:01’ UTC |
登录数据库:
输入下面命令:
mysql -uroot -p
说明:
登录成功后, 输入如下命令查看效果:
# 显示当前时间select now();
登出(退出)数据库:
quit 或 exit 或 ctrl + d
查看所有数据库
show databases;
创建数据库
create database 数据库名 charset=utf8;例:create database python charset=utf8;
使用数据库
use 数据库名;
查看当前使用的数据库
select database();
删除数据库-慎重
drop database 数据库名;例:drop database python;
查看当前数据库中所有表
show tables;
创建表
create table students( id int unsigned primary key auto_increment not null, name varchar(20) not null, age tinyint unsigned default 0, height decimal(5,2), gender enum('男','女','人妖','保密') default '保密');
说明:
create table 表名(字段名称 数据类型 可选的约束条件,column1 datatype contrai,...);
修改表-添加字段
alter table 表名 add 列名 类型 约束;例:alter table students add birthday datetime;
修改表-修改字段类型
alter table 表名 modify 列名 类型 约束;例:alter table students modify birthday date not null;
说明:
修改表-修改字段名和字段类型
alter table 表名 change 原名 新名 类型及约束;例:alter table students change birthday birth datetime not null;
说明:
修改表-删除字段
alter table 表名 drop 列名;例:alter table students drop birthday;
查看创表SQL语句
show create table 表名;例:show create table students;
查看创库SQL语句
show create database 数据库名;例:show create database mytest;
删除表
drop table 表名;例:drop table students;
查询数据
-- 1. 查询所有列select * from 表名;例:select * from students;-- 2. 查询指定列select 列1,列2,... from 表名;例:select id,name from students;
添加数据
-- 1. 全列插入:值的顺序与表结构字段的顺序完全一一对应insert into 表名 values (...)例:insert into students values(0, 'xx', default, default, '男');-- 2. 部分列插入:值的顺序与给出的列顺序对应insert into 表名 (列1,...) values(值1,...)例:insert into students(name, age) values('王二小', 15);-- 3. 全列多行插入insert into 表名 values(...),(...)...;例:insert into students values(0, '张飞', 55, 1.75, '男'),(0, '关羽', 58, 1.85, '男');-- 4. 部分列多行插入insert into 表名(列1,...) values(值1,...),(值1,...)...;例:insert into students(name, height) values('刘备', 1.75),('曹操', 1.6);
说明:
修改数据
update 表名 set 列1=值1,列2=值2... where 条件例:update students set age = 18, gender = '女' where id = 6;
删除数据
delete from 表名 where 条件例:delete from students where id=5;
问题:
上面的操作称之为物理删除,一旦删除就不容易恢复,我们可以使用逻辑删除的方式来解决这个问题。
-- 添加删除表示字段,0表示未删除 1表示删除alter table students add isdelete bit default 0;-- 逻辑删除数据update students set isdelete = 1 where id = 8;
说明:
在使用SQL语句显示结果的时候,往往在屏幕显示的字段名并不具备良好的可读性,此时可以使用 as 给字段起一个别名。
使用 as 给字段起别名
select id as 序号, name as 名字, gender as 性别 from students;
可以通过 as 给表起别名
-- 如果是单表查询 可以省略表名select id, name, gender from students;-- 表名.字段名select students.id,students.name,students.gender from students;-- 可以通过 as 给表起别名 select s.id,s.name,s.gender from students as s;
distinct可以去除重复数据行。
select distinct 列1,... from 表名;例: 查询班级中学生的性别select name, gender from students;-- 看到了很多重复数据 想要对其中重复数据行进行去重操作可以使用 distinctselect distinct name, gender from students;
使用where条件查询可以对表中的数据进行筛选,条件成立的记录会出现在结果集中。
where语句支持的运算符:
where条件查询语法格式如下:
select * from 表名 where 条件;例:select * from students where id = 1;
例1:查询编号大于3的学生:
select * from students where id > 3;
例2:查询编号不大于4的学生:
select * from students where id <= 4;
例3:查询姓名不是“黄蓉”的学生:
select * from students where name != '黄蓉';
例4:查询没被删除的学生:
select * from students where is_delete=0;
例1:查询编号大于3的女同学:
select * from students where id > 3 and gender=0;
例2:查询编号小于4或没被删除的学生:
select * from students where id < 4 or is_delete=0;
例3:查询年龄不在10岁到15岁之间的学生:
select * from students where not (age >= 10 and age <= 15);
说明:
例1:查询姓黄的学生:
select * from students where name like '黄%';
例2:查询姓黄并且“名”是一个字的学生:
select * from students where name like '黄_';
例3:查询姓黄或叫靖的学生:
select * from students where name like '黄%' or name like '%靖';
例1:查询编号为3至8的学生:
select * from students where id between 3 and 8;
例2:查询编号不是3至8的男生:
select * from students where (not id between 3 and 8) and gender='男';
例1:查询没有填写身高的学生:
select * from students where height is null;
注意:
排序查询语法:
select * from 表名 order by 列1 asc|desc [,列2 asc|desc,...]
语法说明:
例1:查询未删除男生信息,按学号降序:
select * from students where gender=1 and is_delete=0 order by id desc;
例2:显示所有的学生信息,先按照年龄从大–>小排序,当年龄相同时 按照身高从高–>矮排序:
select * from students order by age desc,height desc;
小结:
当我们在京东购物,浏览商品列表的时候,由于数据特别多,一页显示不完,一页一页的进行显示,这就是分页查询
select * from 表名 limit start,count
说明:
例1:查询前3行男生信息:
select * from students where gender=1 limit 0,3;简写select * from students where gender=1 limit 3;
已知每页显示m条数据,求第n页显示的数据
提示: 关键是求每页的开始行索引
查询学生表,获取第n页数据的SQL语句:
select * from students limit (n-1)*m,m
聚合函数又叫组函数,通常是对表中的数据进行统计和计算,一般结合分组(group by)来使用,用于统计和计算分组数据。
常用的聚合函数:
-- 返回非NULL数据的总行数.select count(height) from students; -- 返回总行数,包含null值记录;select count(*) from students;
-- 查询女生的编号最大值select max(id) from students where gender = 2;
-- 查询未删除的学生最小编号select min(id) from students where is_delete = 0;
-- 查询男生的总身高select sum(height) from students where gender = 1;-- 平均身高select sum(height) / count(*) from students where gender = 1;
-- 求男生的平均身高, 聚合函数不统计null值,平均身高有误select avg(height) from students where gender = 1;-- 求男生的平均身高, 包含身高是null的select avg(ifnull(height,0)) from students where gender = 1;
说明
分组查询就是将查询结果按照指定字段进行分组,字段中数据相等的分为一组。
分组查询基本的语法格式如下:
GROUP BY 列名 [HAVING 条件表达式] [WITH ROLLUP]
说明:
group by可用于单个字段分组,也可用于多个字段分组
-- 根据gender字段来分组select gender from students group by gender;-- 根据name和gender字段进行分组select name, gender from students group by name, gender;
group_concat(字段名): 统计每个分组指定字段的信息集合,每个信息之间使用逗号进行分割
-- 根据gender字段进行分组, 查询gender字段和分组的name字段信息select gender,group_concat(name) from students group by gender;
-- 统计不同性别的人的平均年龄select gender,avg(age) from students group by gender;-- 统计不同性别的人的个数select gender,count(*) from students group by gender;
having作用和where类似都是过滤数据的,但having是过滤分组数据的,只能用于group by
-- 根据gender字段进行分组,统计分组条数大于2的select gender,count(*) from students group by gender having count(*)>2;
with rollup的作用是:在最后记录后面新增一行,显示select查询时聚合函数的统计和计算结果
-- 根据gender字段进行分组,汇总总人数select gender,count(*) from students group by gender with rollup;-- 根据gender字段进行分组,汇总所有人的年龄select gender,group_concat(age) from students group by gender with rollup;
连接查询可以实现多个表的查询,当查询的字段数据来自不同的表就可以使用连接查询来完成。
连接查询可以分为:
查询两个表中符合条件的共有记录
内连接查询效果图:
内连接查询语法格式:
select 字段 from 表1 inner join 表2 on 表1.字段1 = 表2.字段2
说明:
例1:使用内连接查询学生表与班级表:
select * from students as s inner join classes as c on s.cls_id = c.id;
以左表为主根据条件查询右表数据,如果根据条件查询右表数据不存在使用null值填充
左连接查询效果图:
左连接查询语法格式:
select 字段 from 表1 left join 表2 on 表1.字段1 = 表2.字段2
说明:
例1:使用左连接查询学生表与班级表:
select * from students as s left join classes as c on s.cls_id = c.id;
以右表为主根据条件查询左表数据,如果根据条件查询左表数据不存在使用null值填充
右连接查询效果图:
右连接查询语法格式:
select 字段 from 表1 right join 表2 on 表1.字段1 = 表2.字段2
说明:
例1:使用右连接查询学生表与班级表:
select * from students as s right join classes as c on s.cls_id = c.id;
左表和右表是同一个表,根据连接查询条件查询两个表中的数据。
自连接查询的用法:
select c.title, c.pid, p.id, p.title from areas as c inner join areas as p on c.pid = p.id where p.title = '目标';
说明:
这个直连接没有配图说明,可能会难以理解
在一个 select 语句中,嵌入了另外一个 select 语句, 那么被嵌入的 select 语句称之为子查询语句,外部那个select语句则称为主查询.
主查询和子查询的关系:
例1. 查询大于平均年龄的学生:
select * from students where age > (select avg(age) from students);
例2. 查询学生在班的所有班级名字:
select name from classes where id in (select cls_id from students where cls_id is not null);
例3. 查找年龄最大,身高最高的学生:
select * from students where (age, height) = (select max(age), max(height) from students);
范式: 对设计数据库提出的一些规范,目前有迹可寻的共有8种范式,一般遵守3范式即可。
E-R模型即实体-关系模型,E-R模型就是描述数据库存储数据的结构模型。
E-R模型的使用场景:
E-R模型的效果图:
说明:
一对多的关系:
说明:
一对多的关系:
说明:
多对多的关系:
说明:
外键约束:对外键字段的值进行更新和插入时会和引用表中字段的数据进行验证,数据如果不合法则更新和插入会失败,保证数据的有效性
-- 为cls_id字段添加外键约束alter table students add foreign key(cls_id) references classes(id);
-- 创建学校表create table school( id int not null primary key auto_increment, name varchar(10));-- 创建老师表create table teacher( id int not null primary key auto_increment, name varchar(10), s_id int not null, foreign key(s_id) references school(id));
-- 需要先获取外键约束名称,该名称系统会自动生成,可以通过查看表创建语句来获取名称show create table teacher;-- 获取名称之后就可以根据名称来删除外键约束alter table teacher drop foreign key 外键名;
-- 创建 "京东" 数据库create database jing_dong charset=utf8;-- 使用 "京东" 数据库use jing_dong;-- 创建一个商品goods数据表create table goods( id int unsigned primary key auto_increment not null, name varchar(150) not null, cate_name varchar(40) not null, brand_name varchar(40) not null, price decimal(10,3) not null default 0, is_show bit not null default 1, is_saleoff bit not null default 0);-- 向goods表中插入数据insert into goods values(0,'r510vc 15.6英寸笔记本','笔记本','华硕','3399',default,default); insert into goods values(0,'y400n 14.0英寸笔记本电脑','笔记本','联想','4999',default,default);insert into goods values(0,'g150th 15.6英寸游戏本','游戏本','雷神','8499',default,default); insert into goods values(0,'x550cc 15.6英寸笔记本','笔记本','华硕','2799',default,default); insert into goods values(0,'x240 超极本','超级本','联想','4880',default,default); insert into goods values(0,'u330p 13.3英寸超极本','超级本','联想','4299',default,default); insert into goods values(0,'svp13226scb 触控超极本','超级本','索尼','7999',default,default); insert into goods values(0,'ipad mini 7.9英寸平板电脑','平板电脑','苹果','1998',default,default);insert into goods values(0,'ipad air 9.7英寸平板电脑','平板电脑','苹果','3388',default,default); insert into goods values(0,'ipad mini 配备 retina 显示屏','平板电脑','苹果','2788',default,default); insert into goods values(0,'ideacentre c340 20英寸一体电脑 ','台式机','联想','3499',default,default); insert into goods values(0,'vostro 3800-r1206 台式电脑','台式机','戴尔','2899',default,default); insert into goods values(0,'imac me086ch/a 21.5英寸一体电脑','台式机','苹果','9188',default,default); insert into goods values(0,'at7-7414lp 台式电脑 linux )','台式机','宏碁','3699',default,default); insert into goods values(0,'z220sff f4f06pa工作站','服务器/工作站','惠普','4288',default,default); insert into goods values(0,'poweredge ii服务器','服务器/工作站','戴尔','5388',default,default); insert into goods values(0,'mac pro专业级台式电脑','服务器/工作站','苹果','28888',default,default); insert into goods values(0,'hmz-t3w 头戴显示设备','笔记本配件','索尼','6999',default,default); insert into goods values(0,'商务双肩背包','笔记本配件','索尼','99',default,default); insert into goods values(0,'x3250 m4机架式服务器','服务器/工作站','ibm','6888',default,default); insert into goods values(0,'商务双肩背包','笔记本配件','索尼','99',default,default);
表结构说明:
查询类型cate_name为 ‘超级本’ 的商品名称、价格
select name,price from goods where cate_name = '超级本';
显示商品的分类
select cate_name from goods group by cate_name;
求所有电脑产品的平均价格,并且保留两位小数
select round(avg(price),2) as avg_price from goods;
显示每种商品的平均价格
select cate_name,avg(price) from goods group by cate_name;
查询每种类型的商品中 最贵、最便宜、平均价、数量
select cate_name,max(price),min(price),avg(price),count(*) from goods group by cate_name;
查询所有价格大于平均价格的商品,并且按价格降序排序
select id,name,price from goods where price > (select round(avg(price),2) as avg_price from goods) order by price desc;
目前只有一个goods表,我们想要增加一个商品分类信息,比如:移动设备这个分类信息,只通过goods表无法完成商品分类的添加,那么如何实现添加商品分类信息的操作?
答案:
-- 创建商品分类表create table good_cates( id int not null primary key auto_increment, name varchar(50) not null);
-- 查询goods表中商品的分类信息select cate_name from goods group by cate_name;-- 将查询结果插入到good_cates表中insert into good_cates(name) select cate_name from goods group by cate_name;-- 添加移动设备分类信息insert into good_cates(name) values('移动设备');
说明:
上一节课我们已经创建了一个商品分类表(good_cates),并完成了商品分类信息的插入,现在需要更新goods表中的商品分类信息,把商品分类名称改成商量分类id。
接下来我们实现第二步操作:
-- 查看goods表中的商品分类名称对应的商品分类idselect * from goods inner join good_cates on goods.cate_name = good_cates.name;-- 把该语句中from 后的语句理解为一张虚表 update goods g inner join good_cates gc on g.cate_name=gc.name set g.cate_name=gc.id;
前面我们完成了商品分类表(good_cates)的创建和商品分类信息的添加以及把商品表(goods)中的商品分类名称改成了对应的商品分类id,假如我们想要添加一个品牌,比如:双飞燕这个品牌信息,只通过goods表无法完成品牌信息的添加,那么如何实现添加品牌信息的操作?
答案:
-- 查询品牌信息 select brand_name from goods group by brand_name;-- 通过create table ...select来创建数据表并且同时插入数据-- 创建商品分类表,注意: 需要对brand_name 用as起别名,否则name字段就没有值create table good_brands ( id int unsigned primary key auto_increment, name varchar(40) not null) select brand_name as name from goods group by brand_name;
说明:
-- 将goods表中的品牌名称更改成品牌表中对应的品牌idupdate goods as g inner join good_brands gb on g.brand_name = gb.name set g.brand_name = gb.id;
目前我们已经把good表中的商品分类和品牌信息已经更改成了商品分类id和品牌id,接下来需要把 cate_name 和 brand_name 字段分别改成 cate_id和 brand_id 字段,类型都改成int类型
-- 查看表结构desc goods;-- 通过alter table语句修改表结构alter table goods change cate_name cate_id int not null, change brand_name brand_id int not null;
说明:
事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
事务的使用场景:
在日常生活中,有时我们需要进行银行转账,这个银行转账操作背后就是需要执行多个SQL语句,假如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现,要想解决这个问题就需要通过事务来完成。
原子性:
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
一致性:
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)
隔离性:
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)
持久性:
一旦事务提交,则其所做的修改会永久保存到数据库。
说明:
事务能够保证数据的完整性和一致性,让用户的操作更加安全。
在使用事务之前,先要确保表的存储引擎是 InnoDB 类型, 只有这个类型才可以使用事务,MySQL数据库中表的存储引擎默认是 InnoDB 类型。
表的存储引擎说明:
表的存储引擎就是提供存储数据一种机制,不同表的存储引擎提供不同的存储机制。
-- 查看MySQL数据库支持的表的存储引擎show engines;
说明:
查看goods表的创表语句:
-- 选择数据库use jing_dong;-- 查看goods表show create table goods;mysql root@(none):jing_dong> show create table goods;+-------+--------------------------------------------------------+| Table | Create Table |+-------+--------------------------------------------------------+| goods | CREATE TABLE `goods` ( || | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, || | `name` varchar(150) NOT NULL, || | `cate_id` int(10) unsigned NOT NULL, || | `brand_id` int(10) unsigned NOT NULL, || | `price` decimal(10,3) NOT NULL DEFAULT '0.000', || | `is_show` bit(1) NOT NULL DEFAULT b'1', || | `is_saleoff` bit(1) NOT NULL DEFAULT b'0', || | PRIMARY KEY (`id`) || | ) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8 |+-------+--------------------------------------------------------+
说明:
开启事务:
begin;或者start transaction;
说明:
开启事务后执行修改命令,变更数据会保存到MySQL服务端的缓存文件中,而不维护到物理表中
MySQL数据库默认采用自动提交(autocommit)模式,如果没有显示的开启一个事务,那么每条sql语句都会被当作一个事务执行提交的操作
当设置autocommit=0就是取消了自动提交事务模式,直到显示的执行commit和rollback表示该事务结束。
set autocommit = 0;insert into students(name) values('刘三峰');-- 需要执行手动提交,数据才会真正添加到表中, 验证的话需要重新打开一个连接窗口查看表的数据信息,因为是临时关闭自动提交模式commit-- 重新打开一个终端窗口,连接MySQL数据库服务端mysql -uroot -p-- 然后查询数据,如果上个窗口执行了commit,这个窗口才能看到数据select * from students;
提交事务:
将本地缓存文件中的数据提交到物理表中,完成数据的更新。
commit;
回滚事务:
放弃本地缓存文件中的缓存数据, 表示回到开始事务前的状态
rollback;
事务演练的SQL语句:
begin;insert into students(name) values('李白');-- 查询数据,此时有新增的数据, 注意: 如果这里后续没有执行提交事务操作,那么数据是没有真正的更新到物理表中select * from students;-- 只有这里提交事务,才把数据真正插入到物理表中commit;-- 新打开一个终端,重新连接MySQL数据库,查询students表,这时没有显示新增的数据,说明之前的事务没有提交,这就是事务的隔离性-- 一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的select * from students;
索引在MySQL中也叫做“键”,它是一个特殊的文件,它保存着数据表里所有记录的位置信息,更通俗的来说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。
应用场景:
当数据库中数据量很大时,查找数据会变得很慢,我们就可以通过索引来提高数据库的查询效率。
查看表中已有索引:
show index from 表名;
说明:
索引的创建:
-- 创建索引的语法格式-- alter table 表名 add index 索引名[可选](列名, ..)-- 给name字段添加索引alter table classes add index my_name (name);
说明:
索引的删除:
-- 删除索引的语法格式-- alter table 表名 drop index 索引名-- 如果不知道索引名,可以查看创表sql语句show create table classes;alter table classes drop index my_name;
创建测试表testindex:
create table test_index(title varchar(10));
向表中插入十万条数据:
from pymysql import connectdef main(): # 创建Connection连接 conn = connect(host='localhost',port=3306,database='python',user='root',password='mysql',charset='utf8') # 获得Cursor对象 cursor = conn.cursor() # 插入10万次数据 for i in range(100000): cursor.execute("insert into test_index values('ha-%d')" % i) # 提交数据 conn.commit()if __name__ == "__main__": main()
验证索引性能操作:
-- 开启运行时间监测:set profiling=1;-- 查找第1万条数据ha-99999select * from test_index where title='ha-99999';-- 查看执行的时间:show profiles;-- 给title字段创建索引:alter table test_index add index (title);-- 再次执行查询语句select * from test_index where title='ha-99999';-- 再次查看执行的时间show profiles;
联合索引又叫复合索引,即一个索引覆盖表中两个或者多个字段,一般用在多个字段一起查询的时候。
-- 创建teacher表create table teacher( id int not null primary key auto_increment, name varchar(10), age int);-- 创建联合索引alter table teacher add index (name,age);
联合索引的好处:
在使用联合索引的时候,我们要遵守一个最左原则,即index(name,age)支持 name 、name 和 age 组合查询,而不支持单独 age 查询,因为没有用到创建的联合索引。
最左原则示例:
-- 下面的查询使用到了联合索引select * from stu where name='张三' -- 这里使用了联合索引的name部分select * from stu where name='李四' and age=10 -- 这里完整的使用联合索引,包括 name 和 age 部分 -- 下面的查询没有使用到联合索引select * from stu where age=10 -- 因为联合索引里面没有这个组合,只有 name | name age 这两种组合
说明:
如何实现将100000条数据插入到MySQL数据库?
答案:
如果使用之前学习的MySQL客户端来完成这个操作,那么这个工作量无疑是巨大的,我们可以通过使用程序代码的方式去连接MySQL数据库,然后对MySQL数据库进行增删改查的方式,实现10000条数据的插入,像这样使用代码的方式操作数据库就称为数据库编程。
安装pymysql第三方包:
sudo pip3 install pymysql
说明:
pymysql的使用:
导入 pymysql 包
import pymysql
创建连接对象
调用pymysql模块中的connect()函数来创建连接对象,代码如下:
conn=connect(参数列表) * 参数host:连接的mysql主机,如果本机是'localhost' * 参数port:连接的mysql主机的端口,默认是3306 * 参数user:连接的用户名 * 参数password:连接的密码 * 参数database:数据库的名称 * 参数charset:通信采用的编码方式,推荐使用utf8
连接对象操作说明:
获取游标对象
获取游标对象的目标就是要执行sql语句,完成对数据库的增、删、改、查操作。代码如下:
# 调用连接对象的cursor()方法获取游标对象 cur =conn.cursor()
游标操作说明:
pymysql完成数据的查询操作
import pymysql# 创建连接对象conn = pymysql.connect(host='localhost', port=3306, user='root', password='mysql',database='python', charset='utf8')# 获取游标对象cursor = conn.cursor()# 查询 SQL 语句sql = "select * from students;"# 执行 SQL 语句 返回值就是 SQL 语句在执行过程中影响的行数row_count = cursor.execute(sql)print("SQL 语句执行影响的行数%d" % row_count)# 取出结果集中一行数据, 例如:(1, '张三')# print(cursor.fetchone())# 取出结果集中的所有数据, 例如:((1, '张三'), (2, '李四'), (3, '王五'))for line in cursor.fetchall(): print(line)# 关闭游标cursor.close()# 关闭连接conn.close()
pymysql完成对数据的增删改
import pymysql# 创建连接对象conn = pymysql.connect(host='localhost', port=3306, user='root', password='mysql',database='python', charset='utf8')# 获取游标对象cursor = conn.cursor()try: # 添加 SQL 语句 # sql = "insert into students(name) values('刘璐'), ('王美丽');" # 删除 SQ L语句 # sql = "delete from students where id = 5;" # 修改 SQL 语句 sql = "update students set name = '王铁蛋' where id = 6;" # 执行 SQL 语句 row_count = cursor.execute(sql) print("SQL 语句执行影响的行数%d" % row_count) # 提交数据到数据库 conn.commit()except Exception as e: # 回滚数据, 即撤销刚刚的SQL语句操作 conn.rollback()# 关闭游标cursor.close()# 关闭连接conn.close()
说明:
防止SQL注入
什么是SQL注入?
用户提交带有恶意的数据与SQL语句进行字符串方式的拼接,从而影响了SQL语句的语义,最终产生数据泄露的现象。
如何防止SQL注入?
SQL语句参数化
防止SQL注入的示例代码:
from pymysql import connectdef main(): find_name = input("请输入物品名称:") # 创建Connection连接 conn = connect(host='localhost',port=3306,user='root',password='mysql',database='jing_dong',charset='utf8') # 获得Cursor对象 cs1 = conn.cursor() # 非安全的方式 # 输入 ' or 1 = 1 or ' (单引号也要输入) # sql = "select * from goods where name='%s'" % find_name # print("""sql===>%s<====""" % sql) # # 执行select语句,并返回受影响的行数:查询所有数据 # count = cs1.execute(sql) # 安全的方式 # 构造参数列表 params = [find_name] # 执行select语句,并返回受影响的行数:查询所有数据 count = cs1.execute("select * from goods where name=%s", params) # 注意: # 如果要是有多个参数,需要进行参数化 # 那么params = [数值1, 数值2....],此时sql语句中有多个%s即可 # %s 不需要带引号 # 打印受影响的行数 print(count) # 获取查询的结果 # result = cs1.fetchone() result = cs1.fetchall() # 打印查询的结果 print(result) # 关闭Cursor对象 cs1.close() # 关闭Connection对象 conn.close()if __name__ == '__main__': main()
说明:
导包
import pymysql
创建连接对象
pymysql.connect(参数列表)
获取游标对象
cursor =conn.cursor()
执行SQL语句
row_count = cursor.execute(sql)
获取查询结果集
result = cursor.fetchall()
将修改操作提交到数据库
conn.commit()
回滚数据
conn.rollback()
关闭游标
cursor.close()
关闭连接
conn.close()
一. 准备数据
该案例使用前面章节中完成的京东商品数据库.
二. 程序菜单
print('1. 查询所有商品信息') print("2. 查询所有包含商品的分类") print("3. 添加新商品分类") print("4. 将所有商品价格加1000") print("5. 将所有笔记本的分类改为超级本") print("6. 根据id查询商品信息") print("7. 根据id查询商品信息安全方式") print("8. 退出系统")
三. 方法命名
# 显示菜单方法 def __print_menu(self): pass # 打印结果方法 def __show_query_result(self, result): pass # 服务器运行方法,实现主体逻辑 def run(self): pass # 1. 查询所有商品信息 def __fetch_all_info(self): pass # 2. 查询所有包含商品的分类 def __fetch_cate_of_goods(self): pass # 3. 添加商品分类 def __add_new_cate(self): pass # 4. 将所有商品价格加1000 def __update_price(self): pass # 5. 将所有笔记本的分类改为超级本 def __update_cate(self): pass # 6. 根据id查询商品信息 def __fetch_info_with_id(self): pass # 7. 根据id查询商品信息安全方式 def __fetch_info_with_id_safe(self): pass
四. 代码实现
判断当前是否是主程序入口
if __name__ == '__main__': main()
实现主函数
def main(): # 创建服务器对象,并传入相应参数 jd = JDServer('jd', 'root', '123123') # 启动服务器 jd.run()
实现 JD 类
import pymysql class JDServer(object): """JD 类,提供商品查询服务""" # 将数据库连接操作放到初化方法中,对象创建时,自动连接数据库 def __init__(self, target_db, username, password): # 连接数据库 self.db_connect = pymysql.Connect(host='localhost',port=3306,database=target_db, user=username, passwd=password, charset='utf8') # 将数据库关闭操作放到 __del__方法中,当对象销毁时,自动关闭数据库 def __del__(self): # 关闭数据库 self.db_connect.close()
实现 run 方法 因为需要重复选择,所以要用死循环
# 服务器运行方法,实现主体逻辑,通过判断输入选择相应的功能函数 def run(self): while True: self.__print_menu() select_id = input('请输入功能ID:') if select_id == '1': self.__fetch_all_info() elif select_id == '2': self.__fetch_cate_of_goods() elif select_id == '3': self.__add_new_cate() elif select_id == '4': self.__update_price() elif select_id == '5': self.__update_cate() elif select_id == '6': self.__fetch_info_with_id() elif select_id == '7': self.__fetch_info_with_id_safe() elif select_id == '8': break else: print('输入功能不正确,请重新输入')
实现一个显示方法,用来输出结果
# 用来显示结果的方法,私有,对外不可见 def __show_query_result(self, result): for item in result: print(item)
实现查询所有商品信息
# 1. 查询所有商品信息 def __fetch_all_info(self): cur = self.db_connect.cursor() sql_str = '''select * from goods''' cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
查询所有包含商品的分类
# 2. 查询所有包含商品的分类 def __fetch_cate_of_goods(self): cur = self.db_connect.cursor() sql_str = '''select distinct good_cates.name from goods inner join good_cates on goods.cate_id = good_cates.id;''' # sql_str = ''' select name from good_cates where id in (select distinct cate_id from goods); ''' cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
添加商品分类
# 3. 添加商品分类 def __add_new_cate(self): new_cate = input('请输入一个新商品分类:') sql_str = ''' insert into good_cates(name) values("%s") ''' % new_cate cur = self.db_connect.cursor() cur.execute(sql_str) self.db_connect.commit() cur.close()
将所有商品价格加1000
# 4. 将所有商品价格加1000 def __update_price(self): cur = self.db_connect.cursor() sql_str = ''' update goods set price = price + 1000''' cur.execute(sql_str) self.db_connect.commit() cur.close()
将所有笔记本的分类改为超级本
# 5. 将所有笔记本的分类改为超级本def __update_cate(self): cur = self.db_connect.cursor() sql_str = '''update goods set cate_id = (select id from good_cates where name = '超级本') where name like '%笔记本%';''' cur.execute(sql_str) self.db_connect.commit() cur.close()
根据id 查找商品,会产生SQL注入问题
# 6. 根据id查询商品信息def __fetch_info_with_id(self): s_id = input('请输入一个商品ID:') sql_str = ''' select * from goods where id = %s ''' % s_id print(sql_str) cur = self.db_connect.cursor() cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
根据id查询商品信息安全方式,案例防注入
# 7. 根据id查询商品信息安全方式def __fetch_info_with_id_safe(self): s_id = input('请输入一个商品ID:') sql_str = ''' select * from goods where id = %s ''' print(sql_str) cur = self.db_connect.cursor() cur.execute(sql_str, (s_id,)) result = cur.fetchall() self.__show_query_result(result) cur.close() self.__show_query_result(result) cur.close()
查询所有包含商品的分类
# 2. 查询所有包含商品的分类 def __fetch_cate_of_goods(self): cur = self.db_connect.cursor() sql_str = '''select distinct good_cates.name from goods inner join good_cates on goods.cate_id = good_cates.id;''' # sql_str = ''' select name from good_cates where id in (select distinct cate_id from goods); ''' cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
添加商品分类
# 3. 添加商品分类 def __add_new_cate(self): new_cate = input('请输入一个新商品分类:') sql_str = ''' insert into good_cates(name) values("%s") ''' % new_cate cur = self.db_connect.cursor() cur.execute(sql_str) self.db_connect.commit() cur.close()
将所有商品价格加1000
# 4. 将所有商品价格加1000 def __update_price(self): cur = self.db_connect.cursor() sql_str = ''' update goods set price = price + 1000''' cur.execute(sql_str) self.db_connect.commit() cur.close()
将所有笔记本的分类改为超级本
# 5. 将所有笔记本的分类改为超级本def __update_cate(self): cur = self.db_connect.cursor() sql_str = '''update goods set cate_id = (select id from good_cates where name = '超级本') where name like '%笔记本%';''' cur.execute(sql_str) self.db_connect.commit() cur.close()
根据id 查找商品,会产生SQL注入问题
# 6. 根据id查询商品信息def __fetch_info_with_id(self): s_id = input('请输入一个商品ID:') sql_str = ''' select * from goods where id = %s ''' % s_id print(sql_str) cur = self.db_connect.cursor() cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
根据id查询商品信息安全方式,案例防注入
# 7. 根据id查询商品信息安全方式def __fetch_info_with_id_safe(self): s_id = input('请输入一个商品ID:') sql_str = ''' select * from goods where id = %s ''' print(sql_str) cur = self.db_connect.cursor() cur.execute(sql_str, (s_id,)) result = cur.fetchall() self.__show_query_result(result) cur.close()
JavaScript是运行在浏览器端的脚本语言, 是由浏览器解释执行的, 简称js.
它能够让网页和用户有交互功能, 增加良好的用户体验效果。
1、HTML:负责网页结构
2、CSS:负责网页样式
3、JavaScript:负责网页行为, 比如:网页与用户的交互效果
<input type="button" name="" onclick="alert('ok!');">
<script type="text/javascript"> alert('ok!');</script>
<script type="text/javascript" src="js/index.js"></script>
JavaScript 是一种弱类型语言,也就是说不需要指定变量的类型,JavaScript的变量类型由它的值来决定, 定义变量需要用关键字 ‘var’, 一条JavaScript语句应该以“;”结尾
定义变量的语法格式:
var 变量名 = 值;
var iNum = 123; var sTr = 'asd'; //同时定义多个变量可以用","隔开,公用一个‘var’关键字 var iNum = 45,sTr='qwe',sCount='68';
JavaScript的注释分为单行注释(//注释内容)和多行注释(/多行注释/)
<script type="text/javascript"> // 单行注释var iNum = 123;/* 多行注释 1、... 2、...*/var sTr = 'abc123';</script>
js中有六种数据类型,包括五种基本数据类型和一种复杂数据类型(object)。
5种基本数据类型:
1、number 数字类型
2、string 字符串类型
3、boolean 布尔类型 true 或 false
4、undefined undefined类型,变量声明未初始化,它的值就是undefined
5、null null类型,表示空对象,如果定义的变量将来准备保存对象,可以将变量初始化为null,在页面上获取不到对象,返回的值就是null
1种复合类型:
1、object 后面学习的JavaScript对象属于复合类型
js中有六种数据类型,分别是:
//1.1 数字 numbervar iOne = 10.1;//1.2 字符串 stringvar sStr = '1234';//1.3 布尔 boolean; var bIsTrue = false;//1.4 未定义 undefinedvar unData;//1.5 null 表示空对象var nullData = null;//1.6 object 表示对象类型var oObj = { name:"隔壁老王", age:88}// 获取变量的类型var type = typeof(oObj);alert(type);// 获取对象的name属性alert(oObj.name);
1、区分大小写
2、第一个字符必须是字母、下划线(_)或者美元符号($)
3、其他字符可以是字母、下划线、美元符或数字
对象o Object 比如:oDiv
数组a Array 比如:aItems
字符串s String 比如:sUserName
整数i Integer 比如:iItemCount
布尔值b Boolean 比如:bIsComplete
浮点数f Float 比如:fPrice
函数fn Function 比如:fnHandler
函数就是可以重复使用的代码块, 使用关键字 function 定义函数。
<script type="text/javascript"> // 函数定义 function fnAlert(){ alert('hello!'); }</script>
函数调用就是函数名加小括号,比如:函数名(参数[参数可选])
<script type="text/javascript"> // 函数定义 function fnAlert(){ alert('hello!'); } // 函数调用 fnAlert();</script>
定义函数时,函数如果有参数,参数放到小括号里面,函数如果有返回值,返回值通过 return 关键字来返回
<script type="text/javascript">function fnAdd(iNum01,iNum02){ var iRs = iNum01 + iNum02; return iRs; alert('here!');}var iCount = fnAdd(3,4);alert(iCount); //弹出7</script>
函数中’return’关键字的作用:
1、返回函数中的值
2、执行完return函数执行结束
变量作用域就是变量的使用范围,变量分为:
局部变量就是在函数内使用的变量,只能在函数内部使用。
<script type="text/javascript"> function myalert() { // 定义局部变量 var b = 23; alert(b); } myalert(); // 弹出23 alert(b); // 函数外使用出错</script>
全局变量就是在函数外定义的变量,可以在不同函数内使用。
<script type="text/javascript"> // 定义全局变量 var a = 12; function myalert() { // 修改全局变量 a++; } myalert(); alert(a); // 弹出13 </script>
条件语句就是通过条件来控制程序的走向
假如 x = 5, 查看比较后的结果:
比较运算符 | 描述 | 例子 |
---|---|---|
== | 等于 | x == 8 为 false |
=== | 全等(值和类型) | x === 5 为 true; x === “5” 为 false |
!= | 不等于 | x != 8 为 true |
> | 大于 | x > 8 为 false |
< | 小于 | x < 8 为 true |
>= | 大于或等于 | x >= 8 为 false |
<= | 小于或等于 | x <= 8 为 true |
比较运算符示例代码:
var iNum01 = 12;var sNum01 = '12';if(iNum01==12){ alert('相等!');}else{ alert('不相等!')}// "==" 符号默认会将符号两边的变量转换成数字再进行对比,这个叫做隐式转换if(sNum01==12){ alert('相等!');}else{ alert('不相等!')}// "===" 符号不会转换符号两边的数据类型if(sNum01===12){ alert('相等!');}else{ alert('不相等!')}// 多条件判断var sFruit = "苹果";if (sFruit == "苹果") { alert("您选择的水果是苹果");} else if (sFruit == "鸭梨") { alert("您选择的水果是鸭梨");} else { alert("对不起,您选择的水果不存在!")}
假如 x=6, y=3, 查看比较后的结果:
比较运算符 | 描述 | 例子 |
---|---|---|
&& | and | (x < 10 && y > 1) 为 true |
|| | or | (x==5 || y==5) 为 false |
! | not | !(x==y) 为 true |
逻辑运算符示例代码:
var x = 6;var y = 3;if(x < 10 && y > 1){ alert('都大于');}else{ alert('至少有一个不大于');}if(x > 5 || y > 7 ){ alert('至少有一个大于');}else{ alert('都不大于');}if(!(x == y)){ alert('等于')}else{ alert('不等于')}
可以使用内置对象 document 上的 getElementById 方法来获取页面上设置了id属性的标签元素,获取到的是一个html对象,然后将它赋值给一个变量,比如:
<script type="text/javascript"> var oDiv = document.getElementById('div1'); alert(oDiv);</script><div id="div1">这是一个div元素</div>
说明:
上面的代码,如果把javascript写在元素的上面,就会出错,因为页面上从上往下加载执行的,javascript去页面上获取元素div1的时候,元素div1还没有加载。
解决方法有两种:
第一种方法:将javascript放到页面最下边
<div id="div1">这是一个div元素</div><script type="text/javascript"> var oDiv = document.getElementById('div1'); alert(oDiv);</script>
第二种方法:设置页面加载完成执行的函数,在执行函数里面获取标签元素。
<script type="text/javascript"> window.onload = function(){ var oDiv = document.getElementById('div1'); }</script>
说明:
onload是页面所有元素加载完成的事件,给onload设置函数时,当事件触发就会执行设置的函数。
首先获取的页面标签元素,然后就可以对页面标签元素的属性进行操作,属性的操作包括:
属性名在js中的写法
<style> .sty01{ font-size:20px; color:red; } .sty02{ font-size:30px; color:pink; text-decoration:none; }</style><script type="text/javascript"> window.onload = function(){ var oInput = document.getElementById('input1'); var oA = document.getElementById('link1'); // 读取属性值 var sValue = oInput.value; var sType = oInput.type; var sName = oInput.name; var sLinks = oA.href; // 操作class属性,需要写成“className” oA.className = 'sty02'; // 写(设置)属性 oA.style.color = 'red'; oA.style.fontSize = sValue; }</script><input type="text" name="setsize" id="input1" value="20px"><a href="#" id="link01" class="sty01">这是一个链接</a>
innerHTML可以读取或者设置标签包裹的内容
<script type="text/javascript"> window.onload = function(){ var oDiv = document.getElementById('div1'); //读取 var sTxt = oDiv.innerHTML; alert(sTxt); //写入 oDiv.innerHTML = '<a href="http://www.itcast.cn">传智播客<a/>'; }</script><div id="div1">这是一个div元素</div>
数组就是一组数据的集合,javascript 中,数组里面的数据可以是不同类型的数据,好比 python 里面的列表。
// 实例化对象方式创建var aList = new Array(1,2,3);// 字面量方式创建,推荐使用var aList2 = [1,2,3,'asd'];
多维数组指的是数组的成员也是数组,把这样的数组叫做多维数组。
var aList = [[1,2,3],['a','b','c']];
1、 获取数组的长度
var aList = [1,2,3,4];alert(aList.length); // 弹出4
2、 根据下标取值
var aList = [1,2,3,4];alert(aList[0]); // 弹出1
3、 从数组最后添加和删除数据
var aList = [1,2,3,4];aList.push(5);alert(aList); //弹出1,2,3,4,5aList.pop();alert(aList); // 弹出1,2,3,4
4、根据下标添加和删除元素
arr.splice(start,num,element1,…..,elementN)
参数解析:
此方法会删除从start索引开始的num个元素,并将elementN参数插入到start索引位置。
var colors = ["red", "green", "blue"];colors.splice(0,1); //删除第一项alert(colors); //green,bluecolors.splice(1, 0, "yellow", "orange"); //从第一个索引位置插入两项数据alert(colors); //green,yellow,organge,bluecolors.splice(1, 1, "red", "purple"); //删除一项,插入两项数据alert(colors); //green,red,purple,orange,blue
循环语句就是让一部分代码重复执行,javascript中常用的循环语句有:
var array = [1, 4, 5];for(var index = 0; index < array.length; index++){ var result = array[index]; alert(result);}
var array = [1, 4, 5]; var index = 0;while (index < array.length) { var result = array[index]; alert(result); index++;}
说明:
当条件成立的时候, while语句会循环执行
var array = [1, 4, 5];var index = 0;do { var result = array[index]; alert(result); index++;} while (index < array.length);
说明:
当条件不成立的时候do语句也会执行一次
字符串拼接使用: “+” 运算符
var iNum1 = 10;var fNum2 = 11.1;var sStr = 'abc';result = iNum1 + fNum2;alert(result); // 弹出21.1result = fNum2 + sStr;alert(result); // 弹出11.1abc
说明
数字和字符串拼接会自动进行类型转换(隐式类型转换),把数字类型转成字符串类型进行拼接
定时器就是在一段特定的时间后执行某段程序代码。
js 定时器有两种创建方式:
setTimeout函数的参数说明:
<script> function hello(){ alert('hello'); } // 执行一次函数的定时器 setTimeout(hello, 500);</script>
setInterval函数的参数说明:
<script> function hello(){ alert('hello'); } // 重复执行函数的定时器 setInterval(hello, 1000);</script>
js 清除定时器分别是:
clearTimeout函数的参数说明:
<script> function hello(){ alert('hello'); // 清除只执行一次的定时器 clearTimeout(t1) } // 执行一次函数的定时器 t1 = setTimeout(hello, 500);</script>
clearInterval函数的参数说明:
<script> function hello(){ alert('hello'); } // 重复执行函数的定时器 var t1 = setInterval(hello, 1000); function stop(){ // 清除反复执行的定时器 clearInterval(t1); } </script> <input type="button" value="停止" onclick="stop();">
]]>jQuery是对JavaScript的封装,它是免费、开源的JavaScript函数库,jQuery 极大地简化了 JavaScript 编程。
jQuery官网:https://jquery.com/
jQuery和JavaScript它们的作用一样,都是负责网页行为操作,增加网页和用户的交互效果的,只不过jQuery简化了JavaScript编程,jQuery实现交互效果更简单。
<script src="js/jquery-1.12.4.min.js"></script>
jQuery入口函数有两种写法:
// 完整写法 $(document).ready(function(){ ... }); // 简化写法 $(function(){ ... });
我们知道使用js获取标签元素,需要页面加载完成以后再获取,我们通过给onload事件属性设置了一个函数来获取标签元素,而jquery提供了ready函数来解决这个问题,保证获取标签元素没有问题,它的速度比原生的 window.onload 更快。
入口函数示例代码:
<script src="js/jquery-1.12.4.min.js"></script><script> window.onload = function(){ var oDiv = document.getElementById('div01'); alert('原生就是获取的div:' + oDiv); }; $(document).ready(function(){ var $div = $('#div01'); alert('jquery获取的div:' + $div); });</script><div id="div01">这是一个div</div>
入口函数的简写示例代码:
<script src="js/jquery-1.12.4.min.js"></script><script> window.onload = function(){ var oDiv = document.getElementById('div01'); alert('原生就是获取的div:' + oDiv); }; /* $(document).ready(function(){ var $div = $('#div01'); alert('jquery获取的div:' + $div); }); */ // 上面ready的写法可以简写成下面的形式: $(function(){ var $div = $('#div01'); alert('jquery获取的div:' + $div); }); </script><div id="div01">这是一个div</div>
jquery选择器就是快速选择标签元素,获取标签的,选择规则和css样式一样。
jquery给标签设置样式使用css方法
示例代码:
$('#myId') //选择id为myId的标签$('.myClass') // 选择class为myClass的标签$('li') //选择所有的li标签$('#ul1 li span') //选择id为ul1标签下的所有li标签下的span标签$('input[name=first]') // 选择name属性等于first的input标签
说明:
可以使用length属性来判断标签是否选择成功, 如果length大于0表示选择成功,否则选择失败。
$(function(){ result = $("div").length; alert(result);});
选择集过滤就是在选择标签的集合里面过滤自己需要的标签
has方法的示例代码:
<script> $(function(){ // has方法的使用 var $div = $("div").has("#mytext"); // 设置样式 $div.css({"background":"red"}); });</script><div> 这是第一个div <input type="text" id="mytext"></div><div> 这是第二个div <input type="text"> <input type="button"></div>
eq方法的示例代码:
<script> $(function(){ // has方法的使用 var $div = $("div").has("#mytext"); // 设置样式 $div.css({"background":"red"}); // eq方法的使用 var $div = $("div").eq(1); // 设置样式 $div.css({"background":"yellow"}); });</script><div> 这是第一个div <input type="text" id="mytext"></div><div> 这是第二个div <input type="text"> <input type="button"></div>
选择集转移就是以选择的标签为参照,然后获取转移后的标签
选择集转移的示例代码:
<script> $(function(){ var $div = $('#div01'); $div.prev().css({'color':'red'}); $div.prevAll().css({'text-indent':50}); $div.next().css({'color':'blue'}); $div.nextAll().css({'text-indent':80}); $div.siblings().css({'text-decoration':'underline'}) $div.parent().css({'background':'gray'}); $div.children().css({'color':'red'}); $div.find('.sp02').css({'font-size':30}); }); </script><div> <h2>这是第一个h2标签</h2> <p>这是第一个段落</p> <div id="div01">这是一个<span>div</span><span class="sp02">第二个span</span></div> <h2>这是第二个h2标签</h2> <p>这是第二个段落</p>
获取和设置元素的内容使用: html方法
给指定元素追加html内容使用: append方法
jquery中的html方法可以获取和设置标签的html内容
示例代码:
<script> $(function(){ var $div = $("#div1"); // 获取标签的html内容 var result = $div.html(); alert(result); // 设置标签的html内容,之前的内容会清除 $div.html("<span style='color:red'>你好</span>"); // 追加html内容 $div.append("<span style='color:red'>你好</span>"); });</script><div id="div1"> <p>hello</p></div>
说明:
给指定标签追加html内容使用append方法
之前使用css方法可以给标签设置样式属性,那么设置标签的其它属性可以使用prop方法了。
示例代码:
<style> .a01{ color:red; }</style><script> $(function(){ var $a = $("#link01"); var $input = $('#input01') // 获取元素属性 var sId = $a.prop("id"); alert(sId); // 设置元素属性 $a.prop({"href":"http://www.baidu.com","title":'这是去到百度的链接',"class":"a01"}); // 获取value属性 // var sValue = $input.prop("value"); // alert(sValue); // 获取value属性使用val()方法的简写方式 var sValue = $input.val(); alert(sValue); // 设置value值 $input.val("222222"); })</script><a id="link01">这是一个链接</a><input type="text" id="input01" value="111111">
说明: 获取value属性和设置value属性还可以通过val方法来完成。
示例代码:
<script> $(function(){ var $li = $('.list li'); var $button = $('#button1') var $text = $("#text1"); var $div = $("#div1") // 鼠标点击 $li.click(function(){ // this指的是当前发生事件的对象,但是它是一个原生js对象 // this.style.background = 'red'; // $(this) 指的是当前发生事件的jquery对象 $(this).css({'background':'gold'}); // 获取jquery对象的索引值,通过index() 方法 alert($(this).index()); }); // 一般和按钮配合使用 $button.click(function(){ alert($text.val()); }); // 获取焦点 $text.focus(function(){ $(this).css({'background':'red'}); }); // 失去焦点 $text.blur(function(){ $(this).css({'background':'white'}); }); // 鼠标进入 $div.mouseover(function(){ $(this).css({'background':'gold'}); }); // 鼠标离开 $div.mouseout(function() { $(this).css({'background':'white'}); }); });</script><div id="div1"> <ul class="list"> <li>列表文字</li> <li>列表文字</li> <li>列表文字</li> </ul> <input type="text" id="text1"> <input type="button" id="button1" value="点击"></div>
说明:
事件代理就是利用事件冒泡的原理(事件冒泡就是事件会向它的父级一级一级传递),把事件加到父级上,通过判断事件来源,执行相应的子元素的操作,事件代理首先可以极大减少事件绑定次数,提高性能;其次可以让新加入的子元素也可以拥有相同的操作。
事件冒泡代码:
<script> $(function(){ var $div1 = $('#div1'); var $div2 = $('#div2'); $div1.click(function(){ alert($(this).html()); }); $div2.click(function(){ alert($(this).html()); }); });</script> <div id="div1" style="width:200px; height:200px; background: red;"> <div id="div2" style="width:100px; height:100px;background: yellow;"> 哈哈 </div></div>
说明:
当点击子元素div,它的点击事件会向它父元素传递,也会触发了父元素的点击事件,这就是事件冒泡。
一般绑定事件的写法:
$(function(){ $ali = $('#list li'); $ali.click(function() { $(this).css({background:'red'}); });})<ul id="list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li></ul>
事件代理的写法
$(function(){ $list = $('#list'); // 父元素ul 来代理 子元素li的点击事件 $list.delegate('li', 'click', function() { // $(this)表示当前点击的子元素对象 $(this).css({background:'red'}); });})<ul id="list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li></ul>
delegate方法参数说明:
delegate(childSelector,event,function)
JavaScript 中的所有事物都是对象:字符串、数值、数组、函数等都可以认为是对象,此外,JavaScript 允许自定义对象,对象可以拥有属性和方法。
创建自定义javascript对象有两种方式:
创建自定义javascript对象有两种方式:
Object类创建对象的示例代码:
<script> var person = new Object(); // 添加属性: person.name = 'tom'; person.age = '25'; // 添加方法: person.sayName = function(){ alert(this.name); } // 调用属性和方法: alert(person.age); person.sayName();</script>
对象字面量创建对象的示例代码:
<script> var person2 = { name:'Rose', age: 18, sayName:function(){ alert('My name is' + this.name); } } // 调用属性和方法: alert(person2.age); person2.sayName();</script>
说明:
调用属性和方法的操作都是通过点语法的方式来完成,对象的创建推荐使用字面量方式,因为更加简单。
json是 JavaScript Object Notation 的首字母缩写,翻译过来就是javascript对象表示法,这里说的json就是类似于javascript对象的字符串,它同时是一种数据格式,目前这种数据格式比较流行,逐渐替换掉了传统的xml数据格式。
json有两种格式:
对象格式:
对象格式的json数据,使用一对大括号({}),大括号里面放入key:value形式的键值对,多个键值对使用逗号分隔。
对象格式的json数据:
{ "name":"tom", "age":18}
格式说明:
json中的(key)属性名称和字符串值需要用双引号引起来,用单引号或者不用引号会导致读取数据错误。
数组格式:
数组格式的json数据,使用一对中括号([]),中括号里面的数据使用逗号分隔。
数组格式的json数据:
["tom",18,"programmer"]
实际开发的json格式比较复杂,例如:
{ "name":"jack", "age":29, "hobby":["reading","travel","photography"] "school":{ "name":"Merrimack College", "location":"North Andover, MA" }}
json本质上是字符串,如果在js中操作json数据,可以将json字符串转化为JavaScript对象。
示例代码:
var sJson = '{"name":"tom","age":18}';var oPerson = JSON.parse(sJson);// 操作属性alert(oPerson.name);alert(oPerson.age);
ajax 是 Asynchronous JavaScript and XML的简写,ajax一个前后台配合的技术,它可以让 javascript 发送异步的 http 请求,与后台通信进行数据的获取,ajax 最大的优点是实现局部刷新,ajax可以发送http请求,当获取到后台数据的时候更新页面显示数据实现局部刷新,在这里大家只需要记住,当前端页面想和后台服务器进行数据交互就可以使用ajax了。
这里提示一下大家, 在html页面使用ajax需要在web服务器环境下运行, 一般向自己的web服务器发送ajax请求。
jquery将它封装成了一个方法$.ajax(),我们可以直接用这个方法来执行ajax请求。
示例代码:
<script> $.ajax({ // 1.url 请求地址 url:'http://t.weather.sojson.com/api/weather/city/101010100', // 2.type 请求方式,默认是'GET',常用的还有'POST' type:'GET', // 3.dataType 设置返回的数据格式,常用的是'json'格式 dataType:'JSON', // 4.data 设置发送给服务器的数据, 没有参数不需要设置 // 5.success 设置请求成功后的回调函数 success:function (response) { console.log(response); }, // 6.error 设置请求失败后的回调函数 error:function () { alert("请求失败,请稍后再试!"); }, // 7.async 设置是否异步,默认值是'true',表示异步,一般不用写 async:true});</script>
ajax方法的参数说明:
ajax的简写方式:
$.ajax按照请求方式可以简写成$.get或者$.post方式
ajax简写方式的示例代码:
<script> $(function(){ /* 1. url 请求地址 2. data 设置发送给服务器的数据, 没有参数不需要设置 3. success 设置请求成功后的回调函数 4. dataType 设置返回的数据格式,常用的是'json'格式, 默认智能判断数据格式 */ $.get("http://t.weather.sojson.com/api/weather/city/101010100", function(dat,status){ console.log(dat); console.log(status); alert(dat); }).error(function(){ alert("网络异常"); }); /* 1. url 请求地址 2. data 设置发送给服务器的数据, 没有参数不需要设置 3. success 设置请求成功后的回调函数 4. dataType 设置返回的数据格式,常用的是'json'格式, 默认智能判断数据格式 */ $.post("test.php", {"func": "getNameAndTime"}, function(data){ alert(data.name); console.log(data.time); }, "json").error(function(){ alert("网络异常"); }); });</script>
$.get和$.post方法的参数说明:
$.get(url,data,success(data, status, xhr),dataType).error(func)
$.post(url,data,success(data, status, xhr),dataType).error(func)
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="js/jquery-3.5.1.min.js"></script> <script> function search(){ var search_str = $("input01").val(); $.get("http://ttapi.research.itcast.cn/app/v1_0/search?q=" + search_str,function(response){ console.log(typeof(response),response); var res_array = response.data.results; var $ul = $("#content"); $ul.html(""); for(var i=0;i < res_array.length;i++){ console.log(res_array[i].title); $ul.append("<li>" + res_array[i].title + "</li>"); } },"JSON").error(function(){ alert("请求失败,请稍后再试") }); } </script></head><body> <input type="text" id="input01"> <input type="button" id="btn01" value="搜索" onclick="search();"> <ul id="content"> </ul></body></html>
]]>HTTP 协议的全称是(HyperText Transfer Protocol),翻译过来就是超文本传输协议。
超文本是超级文本的缩写,是指超越文本限制或者超链接,比如:图片、音乐、视频、超链接等等都属于超文本。
HTTP 协议的制作者是蒂姆·伯纳斯-李,1991年设计出来的,HTTP 协议设计之前目的是传输网页数据的,现在允许传输任意类型的数据。
传输 HTTP 协议格式的数据是基于 TCP 传输协议的,发送数据之前需要先建立连接。
它规定了浏览器和 Web 服务器通信数据的格式,也就是说浏览器和web服务器通信需要使用http协议。
第1步:浏览器客户端通过DNS(域名解析服务器)将目标网站域名(例如www.baidu.com)解析成IP地址(39.156.69.79) >>>获取到目标ip
第2步:浏览器客户端与web服务器程序默认80端口号建立连接
第3步:浏览器向web服务器发送http请求数据
第4步:web服务器收到请求后根据请求向服务器主机获取资源
第5步:服务器主机将资源返回给web服务器
第6步:web服务器向浏览器返回http响应数据
URL的英文全拼是(Uniform Resoure Locator),表达的意思是统一资源定位符,通俗理解就是网络资源地址,也就是我们常说的网址。
URL的样子:
https://news.163.com/18/1122/10/E178J2O4000189FH.html
URL的组成部分:
域名:
域名就是IP地址的别名,它是用点进行分割使用英文字母和数字组成的名字,使用域名目的就是方便的记住某台主机IP地址。
URL的扩展:
https://news.163.com/hello.html?page=1&count=10
参数说明:
首先需要安装Google Chrome浏览器,然后Windows和Linux平台按F12调出开发者工具, mac OS选择 视图 -> 开发者 -> 开发者工具或者直接使用 alt+command+i 这个快捷键,还有一个多平台通用的操作就是在网页右击选择检查。
开发者工具的标签选项说明:
开发者工具的使用说明:
HTTP最常见的请求报文有两种:
说明:
GET 请求报文说明:
---- 请求行 ----GET / HTTP/1.1 # GET请求方式 请求资源路径 HTTP协议版本---- 请求头 -----Host: www.itcast.cn # 服务器的主机地址和端口号,默认是80Connection: keep-alive # 和服务端保持长连接Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 用户代理,也就是客户端的名称Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 # 可接受的数据类型Accept-Encoding: gzip, deflate # 可接受的压缩格式Accept-Language: zh-CN,zh;q=0.9 #可接受的语言Cookie: pgv_pvi=1246921728; # 登录用户的身份标识---- 空行 ----
GET 请求原始报文说明:
GET / HTTP/1.1\r\nHost: www.itcast.cn\r\n Connection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: pgv_pvi=1246921728; \r\n\r\n (请求头信息后面还有一个单独的’\r\n’不能省略)
说明:
POST 请求报文说明:
---- 请求行 ----POST /xmweb?host=mail.itcast.cn&_t=1542884567319 HTTP/1.1 # POST请求方式 请求资源路径 HTTP协议版本---- 请求头 ----Host: mail.itcast.cn # 服务器的主机地址和端口号,默认是80Connection: keep-alive # 和服务端保持长连接Content-Type: application/x-www-form-urlencoded # 告诉服务端请求的数据类型User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 客户端的名称---- 空行 -------- 请求体 ----username=hello&pass=hello # 请求参数
POST 请求原始报文说明:
POST /xmweb?host=mail.itcast.cn&_t=1542884567319 HTTP/1.1\r\nHost: mail.itcast.cn\r\nConnection: keep-alive\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n\r\n(请求头信息后面还有一个单独的’\r\n’不能省略)username=hello&pass=hello
说明:
一个HTTP响应报文是由响应行、响应头、空行和响应体4个部分组成。
响应行是由三部分组成:HTTP协议版本 状态码 状态描述,最常见的状态码是200
响应报文说明:
--- 响应行/状态行 ---HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述--- 响应头 ---Server: Tengine # 服务器名称Content-Type: text/html; charset=UTF-8 # 内容类型Transfer-Encoding: chunked # 发送给客户端内容不确定内容长度,发送结束的标记是0\r\n, Content-Length表示服务端确定发送给客户端的内容大小,但是二者只能用其一。Connection: keep-alive # 和客户端保持长连接Date: Fri, 23 Nov 2018 02:01:05 GMT # 服务端的响应时间--- 空行 ------ 响应体 ---<!DOCTYPE html><html lang=“en”> …</html> # 响应给客户端的数据
原始响应报文说明:
HTTP/1.1 200 OK\r\nServer: Tengine\r\nContent-Type: text/html; charset=UTF-8\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\nDate: Fri, 23 Nov 2018 02:01:05 GMT\r\n\r\n(响应头信息后面还有一个单独的’\r\n’不能省略)<!DOCTYPE html><html lang=“en”> …</html>
说明:
每项数据之间使用:\r\n
HTTP 状态码是用于表示web服务器响应状态的3位数字代码。
状态码 | 说明 |
---|---|
200 | 请求成功 |
307 | 重定向 |
400 | 错误的请求,请求地址或者参数有误 |
404 | 请求资源在服务器不存在 |
500 | 服务器内部源代码出现错误 |
可以为发出请求的浏览器提供静态文档的程序。
平时我们浏览百度新闻数据的时候,每天的新闻数据都会发生变化,那访问的这个页面就是动态的,而我们开发的是静态的,页面的数据不会发生变化。
搭建Python自带的静态Web服务器使用 python3 -m http.server 端口号
ubuntu@python:~$ python3 -m http.server 8080Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
-m选项说明:
-m表示运行包里面的模块,执行这个命令的时候,需要进入你自己指定静态文件的目录,然后通过浏览器就能访问对应的 html文件了,这样一个静态的web服务器就搭建好了。
在浏览器上输入127.0.0.1:8080
在浏览器上按F12键或者右键选择检查打开调试控制台
选择Network或者网络
刷新一下就可以看到通信过程了
实现步骤:
import socketif __name__ == '__main__': # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", 9000)) # 设置监听 tcp_server_socket.listen(128) while True: # 等待接受客户端的连接请求 new_socket, ip_port = tcp_server_socket.accept() # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) with open("static/index.html", "rb") as file: # 读取文件数据 file_data = file.read() # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) # 关闭服务与客户端的套接字 new_socket.close()
目前的Web服务器,不管用户访问什么页面,返回的都是固定页面的数据,接下来需要根据用户的请求返回指定页面的数据
返回指定页面数据的实现步骤:
import socketdef main(): # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", 9000)) # 设置监听 tcp_server_socket.listen(128) while True: # 等待接受客户端的连接请求 new_socket, ip_port = tcp_server_socket.accept() # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) if len(recv_client_data) == 0: print("关闭浏览器了") new_socket.close() return # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) # 根据指定字符串进行分割, 最大分割次数指定2 request_list = recv_client_content.split(" ", maxsplit=2) # 获取请求资源路径 request_path = request_list[1] print(request_path) # 判断请求的是否是根目录,如果条件成立,指定首页数据返回 if request_path == "/": request_path = "/index.html" try: # 动态打开指定文件 with open("static" + request_path, "rb") as file: # 读取文件数据 file_data = file.read() except Exception as e: # 请求资源不存在,返回404数据 # 响应行 response_line = "HTTP/1.1 404 Not Found\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" with open("static/error.html", "rb") as file: file_data = file.read() # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) else: # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) finally: # 关闭服务与客户端的套接字 new_socket.close()if __name__ == '__main__': main()
目前的Web服务器,不能支持多用户同时访问,只能一个一个的处理客户端的请求,那么如何开发多任务版的web服务器同时处理 多个客户端的请求?
可以使用多线程,比进程更加节省内存资源。
多任务版web服务器程序的实现步骤:
import socketimport threading# 处理客户端的请求def handle_client_request(new_socket): # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) if len(recv_client_data) == 0: print("关闭浏览器了") new_socket.close() return # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) # 根据指定字符串进行分割, 最大分割次数指定2 request_list = recv_client_content.split(" ", maxsplit=2) # 获取请求资源路径 request_path = request_list[1] print(request_path) # 判断请求的是否是根目录,如果条件成立,指定首页数据返回 if request_path == "/": request_path = "/index.html" try: # 动态打开指定文件 with open("static" + request_path, "rb") as file: # 读取文件数据 file_data = file.read() except Exception as e: # 请求资源不存在,返回404数据 # 响应行 response_line = "HTTP/1.1 404 Not Found\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" with open("static/error.html", "rb") as file: file_data = file.read() # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) else: # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) finally: # 关闭服务与客户端的套接字 new_socket.close()# 程序入口函数def main(): # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", 9000)) # 设置监听 tcp_server_socket.listen(128) while True: # 等待接受客户端的连接请求 new_socket, ip_port = tcp_server_socket.accept() print(ip_port) # 当客户端和服务器建立连接程,创建子线程 sub_thread = threading.Thread(target=handle_client_request, args=(new_socket,)) # 设置守护主线程 sub_thread.setDaemon(True) # 启动子线程执行对应的任务 sub_thread.start()if __name__ == '__main__': main()
实现步骤:
import socketimport threading# 定义web服务器类class HttpWebServer(object): def __init__(self): # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", 9000)) # 设置监听 tcp_server_socket.listen(128) # 保存创建成功的服务器套接字 self.tcp_server_socket = tcp_server_socket # 处理客户端的请求 @staticmethod def handle_client_request(new_socket): # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) if len(recv_client_data) == 0: print("关闭浏览器了") new_socket.close() return # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) # 根据指定字符串进行分割, 最大分割次数指定2 request_list = recv_client_content.split(" ", maxsplit=2) # 获取请求资源路径 request_path = request_list[1] print(request_path) # 判断请求的是否是根目录,如果条件成立,指定首页数据返回 if request_path == "/": request_path = "/index.html" try: # 动态打开指定文件 with open("static" + request_path, "rb") as file: # 读取文件数据 file_data = file.read() except Exception as e: # 请求资源不存在,返回404数据 # 响应行 response_line = "HTTP/1.1 404 Not Found\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" with open("static/error.html", "rb") as file: file_data = file.read() # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) else: # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) finally: # 关闭服务与客户端的套接字 new_socket.close() # 启动web服务器进行工作 def start(self): while True: # 等待接受客户端的连接请求 new_socket, ip_port = self.tcp_server_socket.accept() # 当客户端和服务器建立连接程,创建子线程 sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,)) # 设置守护主线程 sub_thread.setDaemon(True) # 启动子线程执行对应的任务 sub_thread.start()# 程序入口函数def main(): # 创建web服务器对象 web_server = HttpWebServer() # 启动web服务器进行工作 web_server.start()if __name__ == '__main__': main()
实现步骤:
import socketimport threadingimport sys# 定义web服务器类class HttpWebServer(object): def __init__(self, port): # 创建tcp服务端套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用, 程序退出端口立即释放 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 tcp_server_socket.bind(("", port)) # 设置监听 tcp_server_socket.listen(128) # 保存创建成功的服务器套接字 self.tcp_server_socket = tcp_server_socket # 处理客户端的请求 @staticmethod def handle_client_request(new_socket): # 代码执行到此,说明连接建立成功 recv_client_data = new_socket.recv(4096) if len(recv_client_data) == 0: print("关闭浏览器了") new_socket.close() return # 对二进制数据进行解码 recv_client_content = recv_client_data.decode("utf-8") print(recv_client_content) # 根据指定字符串进行分割, 最大分割次数指定2 request_list = recv_client_content.split(" ", maxsplit=2) # 获取请求资源路径 request_path = request_list[1] print(request_path) # 判断请求的是否是根目录,如果条件成立,指定首页数据返回 if request_path == "/": request_path = "/index.html" try: # 动态打开指定文件 with open("static" + request_path, "rb") as file: # 读取文件数据 file_data = file.read() except Exception as e: # 请求资源不存在,返回404数据 # 响应行 response_line = "HTTP/1.1 404 Not Found\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" with open("static/error.html", "rb") as file: file_data = file.read() # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) else: # 响应行 response_line = "HTTP/1.1 200 OK\r\n" # 响应头 response_header = "Server: PWS1.0\r\n" # 响应体 response_body = file_data # 拼接响应报文 response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body # 发送数据 new_socket.send(response_data) finally: # 关闭服务与客户端的套接字 new_socket.close() # 启动web服务器进行工作 def start(self): while True: # 等待接受客户端的连接请求 new_socket, ip_port = self.tcp_server_socket.accept() # 当客户端和服务器建立连接程,创建子线程 sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,)) # 设置守护主线程 sub_thread.setDaemon(True) # 启动子线程执行对应的任务 sub_thread.start()# 程序入口函数def main(): print(sys.argv) # 判断命令行参数是否等于2, if len(sys.argv) != 2: print("执行命令如下: python3 xxx.py 8000") return # 判断字符串是否都是数字组成 if not sys.argv[1].isdigit(): print("执行命令如下: python3 xxx.py 8000") return # 获取终端命令行参数 port = int(sys.argv[1]) # 创建web服务器对象 web_server = HttpWebServer(port) # 启动web服务器进行工作 web_server.start()if __name__ == '__main__': main()
]]>HTML 的全称为:HyperText Mark-up Language, 指的是超文本标记语言。 标记:就是标签, <标签名称> </标签名称>
, 比如: <html></html>、<h1></h1>
等,标签大多数都是成对出现的。
所谓超文本,有两层含义:
html是用来开发网页的,它是开发网页的语言。
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>网页标题</title> </head> <body> 网页显示内容 </body></html>
<!DOCTYPE html>
是文档声明, 用来指定页面所使用的html的版本, 这里声明的是一个html5的文档。<html>...</html>
标签是开发人员在告诉浏览器,整个网页是从<html>
这里开始的,到</html>
结束,也就是html文档的开始和结束标签。<head>...</head>
标签用于定义文档的头部,是负责对网页进行设置标题、编码格式以及引入css和js文件的。<body>...</body>
标签是编写网页上显示的内容。网页文件的后缀是.html或者.htm, 一个html文件就是一个网页,html文件用编辑器打开显示的是文本,可以用文本的方式编辑它,如果用浏览器打开,浏览器会按照标签描述内容将文件渲染成网页。
目前常用的编辑网页工具有vscode+chrome。
<!-- 1、成对出现的标签:--><h1>h1标题</h1><div>这是一个div标签</div><p>这个一个段落标签</p><!-- 2、单个出现的标签: --><br><img src="images/pic.jpg" alt="图片"><hr><!-- 3、带属性的标签,如src、alt 和 href等都是属性 --><img src="images/pic.jpg" alt="图片"><a href="http://www.baidu.com">百度网</a><!-- 4、标签的嵌套 --><div> <img src="images/pic.jpg" alt="图片"> <a href="http://www.baidu.com">百度网</a></div>
提示:
从当前操作 html 的文档所在目录算起的路径叫做相对路径
示例代码:
<!-- 相对路径方式1 --><img src="./images/logo.png"><!-- 相对路径方式2 --><img src="images/logo.png">
从根目录算起的路径叫做绝对路径,Windows 的根目录是指定的盘符,mac OS 和Linux 是/
示例代码:
<!-- 绝对路径 --><img src="/Users/apple/Desktop/demo/hello/images/logo.png"><img src="C:\demo\images\001.jpg">
提示:
一般都会使用相对路径,绝对路径的操作在其它电脑上打开会有可能出现资源文件找不到的问题
<!-- ul标签定义无序列表 --><ul> <!-- li标签定义列表项目 --> <li>列表标题一</li> <li>列表标题二</li> <li>列表标题三</li></ul>
<!-- ol标签定义有序列表 --><ol> <!-- li标签定义列表项目 --> <li><a href="#">列表标题一</a></li> <li><a href="#">列表标题二</a></li> <li><a href="#">列表标题三</a></li></ol>
表格是由行和列组成,好比一个excel文件
类型 | 字节大小 | 有符号范围(Signed) | 无符号范围(Unsigned) |
---|---|---|---|
TINYINT | 1 | -128 ~ 127 | 0 ~ 255 |
SMALLINT | 2 | -32768 ~ 32767 | 0 ~ 65535 |
MEDIUMINT | 3 | -8388608 ~ 8388607 | 0 ~ 16777215 |
INT/INTEGER | 4 | -2147483648 ~2147483647 | 0 ~ 4294967295 |
BIGINT | 8 | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 |
类型 | 说明 | 使用场景 |
---|---|---|
CHAR | 固定长度,小型数据 | 身份证号、手机号、电话、密码 |
VARCHAR | 可变长度,小型数据 | 姓名、地址、品牌、型号 |
TEXT | 可变长度,字符个数大于 4000 | 存储小型文章或者新闻 |
LONGTEXT | 可变长度, 极大型文本数据 | 存储极大型文本数据 |
类型 | 字节大小 | 示例 |
---|---|---|
DATE | 4 | ‘2020-01-01’ |
TIME | 3 | ‘12:29:59’ |
DATETIME | 8 | ‘2020-01-01 12:29:59’ |
YEAR | 1 | ‘2017’ |
TIMESTAMP | 4 | ‘1970-01-01 00:00:01’ UTC ~ ‘2038-01-01 00:00:01’ UTC |
登录数据库:
输入下面命令:
mysql -uroot -p
说明:
登录成功后, 输入如下命令查看效果:
# 显示当前时间select now();
登出(退出)数据库:
quit 或 exit 或 ctrl + d
查看所有数据库
show databases;
创建数据库
create database 数据库名 charset=utf8;例:create database python charset=utf8;
使用数据库
use 数据库名;
查看当前使用的数据库
select database();
删除数据库-慎重
drop database 数据库名;例:drop database python;
查看当前数据库中所有表
show tables;
创建表
create table students( id int unsigned primary key auto_increment not null, name varchar(20) not null, age tinyint unsigned default 0, height decimal(5,2), gender enum('男','女','人妖','保密') default '保密');
说明:
create table 表名(字段名称 数据类型 可选的约束条件,column1 datatype contrai,...);
修改表-添加字段
alter table 表名 add 列名 类型 约束;例:alter table students add birthday datetime;
修改表-修改字段类型
alter table 表名 modify 列名 类型 约束;例:alter table students modify birthday date not null;
说明:
修改表-修改字段名和字段类型
alter table 表名 change 原名 新名 类型及约束;例:alter table students change birthday birth datetime not null;
说明:
修改表-删除字段
alter table 表名 drop 列名;例:alter table students drop birthday;
查看创表SQL语句
show create table 表名;例:show create table students;
查看创库SQL语句
show create database 数据库名;例:show create database mytest;
删除表
drop table 表名;例:drop table students;
查询数据
-- 1. 查询所有列select * from 表名;例:select * from students;-- 2. 查询指定列select 列1,列2,... from 表名;例:select id,name from students;
添加数据
-- 1. 全列插入:值的顺序与表结构字段的顺序完全一一对应insert into 表名 values (...)例:insert into students values(0, 'xx', default, default, '男');-- 2. 部分列插入:值的顺序与给出的列顺序对应insert into 表名 (列1,...) values(值1,...)例:insert into students(name, age) values('王二小', 15);-- 3. 全列多行插入insert into 表名 values(...),(...)...;例:insert into students values(0, '张飞', 55, 1.75, '男'),(0, '关羽', 58, 1.85, '男');-- 4. 部分列多行插入insert into 表名(列1,...) values(值1,...),(值1,...)...;例:insert into students(name, height) values('刘备', 1.75),('曹操', 1.6);
说明:
修改数据
update 表名 set 列1=值1,列2=值2... where 条件例:update students set age = 18, gender = '女' where id = 6;
删除数据
delete from 表名 where 条件例:delete from students where id=5;
问题:
上面的操作称之为物理删除,一旦删除就不容易恢复,我们可以使用逻辑删除的方式来解决这个问题。
-- 添加删除表示字段,0表示未删除 1表示删除alter table students add isdelete bit default 0;-- 逻辑删除数据update students set isdelete = 1 where id = 8;
说明:
在使用SQL语句显示结果的时候,往往在屏幕显示的字段名并不具备良好的可读性,此时可以使用 as 给字段起一个别名。
使用 as 给字段起别名
select id as 序号, name as 名字, gender as 性别 from students;
可以通过 as 给表起别名
-- 如果是单表查询 可以省略表名select id, name, gender from students;-- 表名.字段名select students.id,students.name,students.gender from students;-- 可以通过 as 给表起别名 select s.id,s.name,s.gender from students as s;
distinct可以去除重复数据行。
select distinct 列1,... from 表名;例: 查询班级中学生的性别select name, gender from students;-- 看到了很多重复数据 想要对其中重复数据行进行去重操作可以使用 distinctselect distinct name, gender from students;
使用where条件查询可以对表中的数据进行筛选,条件成立的记录会出现在结果集中。
where语句支持的运算符:
where条件查询语法格式如下:
select * from 表名 where 条件;例:select * from students where id = 1;
例1:查询编号大于3的学生:
select * from students where id > 3;
例2:查询编号不大于4的学生:
select * from students where id <= 4;
例3:查询姓名不是“黄蓉”的学生:
select * from students where name != '黄蓉';
例4:查询没被删除的学生:
select * from students where is_delete=0;
例1:查询编号大于3的女同学:
select * from students where id > 3 and gender=0;
例2:查询编号小于4或没被删除的学生:
select * from students where id < 4 or is_delete=0;
例3:查询年龄不在10岁到15岁之间的学生:
select * from students where not (age >= 10 and age <= 15);
说明:
例1:查询姓黄的学生:
select * from students where name like '黄%';
例2:查询姓黄并且“名”是一个字的学生:
select * from students where name like '黄_';
例3:查询姓黄或叫靖的学生:
select * from students where name like '黄%' or name like '%靖';
例1:查询编号为3至8的学生:
select * from students where id between 3 and 8;
例2:查询编号不是3至8的男生:
select * from students where (not id between 3 and 8) and gender='男';
例1:查询没有填写身高的学生:
select * from students where height is null;
注意:
排序查询语法:
select * from 表名 order by 列1 asc|desc [,列2 asc|desc,...]
语法说明:
例1:查询未删除男生信息,按学号降序:
select * from students where gender=1 and is_delete=0 order by id desc;
例2:显示所有的学生信息,先按照年龄从大–>小排序,当年龄相同时 按照身高从高–>矮排序:
select * from students order by age desc,height desc;
小结:
当我们在京东购物,浏览商品列表的时候,由于数据特别多,一页显示不完,一页一页的进行显示,这就是分页查询
select * from 表名 limit start,count
说明:
例1:查询前3行男生信息:
select * from students where gender=1 limit 0,3;简写select * from students where gender=1 limit 3;
已知每页显示m条数据,求第n页显示的数据
提示: 关键是求每页的开始行索引
查询学生表,获取第n页数据的SQL语句:
select * from students limit (n-1)*m,m
聚合函数又叫组函数,通常是对表中的数据进行统计和计算,一般结合分组(group by)来使用,用于统计和计算分组数据。
常用的聚合函数:
-- 返回非NULL数据的总行数.select count(height) from students; -- 返回总行数,包含null值记录;select count(*) from students;
-- 查询女生的编号最大值select max(id) from students where gender = 2;
-- 查询未删除的学生最小编号select min(id) from students where is_delete = 0;
-- 查询男生的总身高select sum(height) from students where gender = 1;-- 平均身高select sum(height) / count(*) from students where gender = 1;
-- 求男生的平均身高, 聚合函数不统计null值,平均身高有误select avg(height) from students where gender = 1;-- 求男生的平均身高, 包含身高是null的select avg(ifnull(height,0)) from students where gender = 1;
说明
分组查询就是将查询结果按照指定字段进行分组,字段中数据相等的分为一组。
分组查询基本的语法格式如下:
GROUP BY 列名 [HAVING 条件表达式] [WITH ROLLUP]
说明:
group by可用于单个字段分组,也可用于多个字段分组
-- 根据gender字段来分组select gender from students group by gender;-- 根据name和gender字段进行分组select name, gender from students group by name, gender;
group_concat(字段名): 统计每个分组指定字段的信息集合,每个信息之间使用逗号进行分割
-- 根据gender字段进行分组, 查询gender字段和分组的name字段信息select gender,group_concat(name) from students group by gender;
-- 统计不同性别的人的平均年龄select gender,avg(age) from students group by gender;-- 统计不同性别的人的个数select gender,count(*) from students group by gender;
having作用和where类似都是过滤数据的,但having是过滤分组数据的,只能用于group by
-- 根据gender字段进行分组,统计分组条数大于2的select gender,count(*) from students group by gender having count(*)>2;
with rollup的作用是:在最后记录后面新增一行,显示select查询时聚合函数的统计和计算结果
-- 根据gender字段进行分组,汇总总人数select gender,count(*) from students group by gender with rollup;-- 根据gender字段进行分组,汇总所有人的年龄select gender,group_concat(age) from students group by gender with rollup;
连接查询可以实现多个表的查询,当查询的字段数据来自不同的表就可以使用连接查询来完成。
连接查询可以分为:
查询两个表中符合条件的共有记录
内连接查询效果图:
内连接查询语法格式:
select 字段 from 表1 inner join 表2 on 表1.字段1 = 表2.字段2
说明:
例1:使用内连接查询学生表与班级表:
select * from students as s inner join classes as c on s.cls_id = c.id;
以左表为主根据条件查询右表数据,如果根据条件查询右表数据不存在使用null值填充
左连接查询效果图:
左连接查询语法格式:
select 字段 from 表1 left join 表2 on 表1.字段1 = 表2.字段2
说明:
例1:使用左连接查询学生表与班级表:
select * from students as s left join classes as c on s.cls_id = c.id;
以右表为主根据条件查询左表数据,如果根据条件查询左表数据不存在使用null值填充
右连接查询效果图:
右连接查询语法格式:
select 字段 from 表1 right join 表2 on 表1.字段1 = 表2.字段2
说明:
例1:使用右连接查询学生表与班级表:
select * from students as s right join classes as c on s.cls_id = c.id;
左表和右表是同一个表,根据连接查询条件查询两个表中的数据。
自连接查询的用法:
select c.title, c.pid, p.id, p.title from areas as c inner join areas as p on c.pid = p.id where p.title = '目标';
说明:
这个直连接没有配图说明,可能会难以理解
在一个 select 语句中,嵌入了另外一个 select 语句, 那么被嵌入的 select 语句称之为子查询语句,外部那个select语句则称为主查询.
主查询和子查询的关系:
例1. 查询大于平均年龄的学生:
select * from students where age > (select avg(age) from students);
例2. 查询学生在班的所有班级名字:
select name from classes where id in (select cls_id from students where cls_id is not null);
例3. 查找年龄最大,身高最高的学生:
select * from students where (age, height) = (select max(age), max(height) from students);
范式: 对设计数据库提出的一些规范,目前有迹可寻的共有8种范式,一般遵守3范式即可。
E-R模型即实体-关系模型,E-R模型就是描述数据库存储数据的结构模型。
E-R模型的使用场景:
E-R模型的效果图:
说明:
一对多的关系:
说明:
一对多的关系:
说明:
多对多的关系:
说明:
外键约束:对外键字段的值进行更新和插入时会和引用表中字段的数据进行验证,数据如果不合法则更新和插入会失败,保证数据的有效性
-- 为cls_id字段添加外键约束alter table students add foreign key(cls_id) references classes(id);
-- 创建学校表create table school( id int not null primary key auto_increment, name varchar(10));-- 创建老师表create table teacher( id int not null primary key auto_increment, name varchar(10), s_id int not null, foreign key(s_id) references school(id));
-- 需要先获取外键约束名称,该名称系统会自动生成,可以通过查看表创建语句来获取名称show create table teacher;-- 获取名称之后就可以根据名称来删除外键约束alter table teacher drop foreign key 外键名;
-- 创建 "京东" 数据库create database jing_dong charset=utf8;-- 使用 "京东" 数据库use jing_dong;-- 创建一个商品goods数据表create table goods( id int unsigned primary key auto_increment not null, name varchar(150) not null, cate_name varchar(40) not null, brand_name varchar(40) not null, price decimal(10,3) not null default 0, is_show bit not null default 1, is_saleoff bit not null default 0);-- 向goods表中插入数据insert into goods values(0,'r510vc 15.6英寸笔记本','笔记本','华硕','3399',default,default); insert into goods values(0,'y400n 14.0英寸笔记本电脑','笔记本','联想','4999',default,default);insert into goods values(0,'g150th 15.6英寸游戏本','游戏本','雷神','8499',default,default); insert into goods values(0,'x550cc 15.6英寸笔记本','笔记本','华硕','2799',default,default); insert into goods values(0,'x240 超极本','超级本','联想','4880',default,default); insert into goods values(0,'u330p 13.3英寸超极本','超级本','联想','4299',default,default); insert into goods values(0,'svp13226scb 触控超极本','超级本','索尼','7999',default,default); insert into goods values(0,'ipad mini 7.9英寸平板电脑','平板电脑','苹果','1998',default,default);insert into goods values(0,'ipad air 9.7英寸平板电脑','平板电脑','苹果','3388',default,default); insert into goods values(0,'ipad mini 配备 retina 显示屏','平板电脑','苹果','2788',default,default); insert into goods values(0,'ideacentre c340 20英寸一体电脑 ','台式机','联想','3499',default,default); insert into goods values(0,'vostro 3800-r1206 台式电脑','台式机','戴尔','2899',default,default); insert into goods values(0,'imac me086ch/a 21.5英寸一体电脑','台式机','苹果','9188',default,default); insert into goods values(0,'at7-7414lp 台式电脑 linux )','台式机','宏碁','3699',default,default); insert into goods values(0,'z220sff f4f06pa工作站','服务器/工作站','惠普','4288',default,default); insert into goods values(0,'poweredge ii服务器','服务器/工作站','戴尔','5388',default,default); insert into goods values(0,'mac pro专业级台式电脑','服务器/工作站','苹果','28888',default,default); insert into goods values(0,'hmz-t3w 头戴显示设备','笔记本配件','索尼','6999',default,default); insert into goods values(0,'商务双肩背包','笔记本配件','索尼','99',default,default); insert into goods values(0,'x3250 m4机架式服务器','服务器/工作站','ibm','6888',default,default); insert into goods values(0,'商务双肩背包','笔记本配件','索尼','99',default,default);
表结构说明:
查询类型cate_name为 ‘超级本’ 的商品名称、价格
select name,price from goods where cate_name = '超级本';
显示商品的分类
select cate_name from goods group by cate_name;
求所有电脑产品的平均价格,并且保留两位小数
select round(avg(price),2) as avg_price from goods;
显示每种商品的平均价格
select cate_name,avg(price) from goods group by cate_name;
查询每种类型的商品中 最贵、最便宜、平均价、数量
select cate_name,max(price),min(price),avg(price),count(*) from goods group by cate_name;
查询所有价格大于平均价格的商品,并且按价格降序排序
select id,name,price from goods where price > (select round(avg(price),2) as avg_price from goods) order by price desc;
目前只有一个goods表,我们想要增加一个商品分类信息,比如:移动设备这个分类信息,只通过goods表无法完成商品分类的添加,那么如何实现添加商品分类信息的操作?
答案:
-- 创建商品分类表create table good_cates( id int not null primary key auto_increment, name varchar(50) not null);
-- 查询goods表中商品的分类信息select cate_name from goods group by cate_name;-- 将查询结果插入到good_cates表中insert into good_cates(name) select cate_name from goods group by cate_name;-- 添加移动设备分类信息insert into good_cates(name) values('移动设备');
说明:
上一节课我们已经创建了一个商品分类表(good_cates),并完成了商品分类信息的插入,现在需要更新goods表中的商品分类信息,把商品分类名称改成商量分类id。
接下来我们实现第二步操作:
-- 查看goods表中的商品分类名称对应的商品分类idselect * from goods inner join good_cates on goods.cate_name = good_cates.name;-- 把该语句中from 后的语句理解为一张虚表 update goods g inner join good_cates gc on g.cate_name=gc.name set g.cate_name=gc.id;
前面我们完成了商品分类表(good_cates)的创建和商品分类信息的添加以及把商品表(goods)中的商品分类名称改成了对应的商品分类id,假如我们想要添加一个品牌,比如:双飞燕这个品牌信息,只通过goods表无法完成品牌信息的添加,那么如何实现添加品牌信息的操作?
答案:
-- 查询品牌信息 select brand_name from goods group by brand_name;-- 通过create table ...select来创建数据表并且同时插入数据-- 创建商品分类表,注意: 需要对brand_name 用as起别名,否则name字段就没有值create table good_brands ( id int unsigned primary key auto_increment, name varchar(40) not null) select brand_name as name from goods group by brand_name;
说明:
-- 将goods表中的品牌名称更改成品牌表中对应的品牌idupdate goods as g inner join good_brands gb on g.brand_name = gb.name set g.brand_name = gb.id;
目前我们已经把good表中的商品分类和品牌信息已经更改成了商品分类id和品牌id,接下来需要把 cate_name 和 brand_name 字段分别改成 cate_id和 brand_id 字段,类型都改成int类型
-- 查看表结构desc goods;-- 通过alter table语句修改表结构alter table goods change cate_name cate_id int not null, change brand_name brand_id int not null;
说明:
事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
事务的使用场景:
在日常生活中,有时我们需要进行银行转账,这个银行转账操作背后就是需要执行多个SQL语句,假如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现,要想解决这个问题就需要通过事务来完成。
原子性:
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
一致性:
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)
隔离性:
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)
持久性:
一旦事务提交,则其所做的修改会永久保存到数据库。
说明:
事务能够保证数据的完整性和一致性,让用户的操作更加安全。
在使用事务之前,先要确保表的存储引擎是 InnoDB 类型, 只有这个类型才可以使用事务,MySQL数据库中表的存储引擎默认是 InnoDB 类型。
表的存储引擎说明:
表的存储引擎就是提供存储数据一种机制,不同表的存储引擎提供不同的存储机制。
-- 查看MySQL数据库支持的表的存储引擎show engines;
说明:
查看goods表的创表语句:
-- 选择数据库use jing_dong;-- 查看goods表show create table goods;mysql root@(none):jing_dong> show create table goods;+-------+--------------------------------------------------------+| Table | Create Table |+-------+--------------------------------------------------------+| goods | CREATE TABLE `goods` ( || | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, || | `name` varchar(150) NOT NULL, || | `cate_id` int(10) unsigned NOT NULL, || | `brand_id` int(10) unsigned NOT NULL, || | `price` decimal(10,3) NOT NULL DEFAULT '0.000', || | `is_show` bit(1) NOT NULL DEFAULT b'1', || | `is_saleoff` bit(1) NOT NULL DEFAULT b'0', || | PRIMARY KEY (`id`) || | ) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8 |+-------+--------------------------------------------------------+
说明:
开启事务:
begin;或者start transaction;
说明:
开启事务后执行修改命令,变更数据会保存到MySQL服务端的缓存文件中,而不维护到物理表中
MySQL数据库默认采用自动提交(autocommit)模式,如果没有显示的开启一个事务,那么每条sql语句都会被当作一个事务执行提交的操作
当设置autocommit=0就是取消了自动提交事务模式,直到显示的执行commit和rollback表示该事务结束。
set autocommit = 0;insert into students(name) values('刘三峰');-- 需要执行手动提交,数据才会真正添加到表中, 验证的话需要重新打开一个连接窗口查看表的数据信息,因为是临时关闭自动提交模式commit-- 重新打开一个终端窗口,连接MySQL数据库服务端mysql -uroot -p-- 然后查询数据,如果上个窗口执行了commit,这个窗口才能看到数据select * from students;
提交事务:
将本地缓存文件中的数据提交到物理表中,完成数据的更新。
commit;
回滚事务:
放弃本地缓存文件中的缓存数据, 表示回到开始事务前的状态
rollback;
事务演练的SQL语句:
begin;insert into students(name) values('李白');-- 查询数据,此时有新增的数据, 注意: 如果这里后续没有执行提交事务操作,那么数据是没有真正的更新到物理表中select * from students;-- 只有这里提交事务,才把数据真正插入到物理表中commit;-- 新打开一个终端,重新连接MySQL数据库,查询students表,这时没有显示新增的数据,说明之前的事务没有提交,这就是事务的隔离性-- 一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的select * from students;
索引在MySQL中也叫做“键”,它是一个特殊的文件,它保存着数据表里所有记录的位置信息,更通俗的来说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。
应用场景:
当数据库中数据量很大时,查找数据会变得很慢,我们就可以通过索引来提高数据库的查询效率。
查看表中已有索引:
show index from 表名;
说明:
索引的创建:
-- 创建索引的语法格式-- alter table 表名 add index 索引名[可选](列名, ..)-- 给name字段添加索引alter table classes add index my_name (name);
说明:
索引的删除:
-- 删除索引的语法格式-- alter table 表名 drop index 索引名-- 如果不知道索引名,可以查看创表sql语句show create table classes;alter table classes drop index my_name;
创建测试表testindex:
create table test_index(title varchar(10));
向表中插入十万条数据:
from pymysql import connectdef main(): # 创建Connection连接 conn = connect(host='localhost',port=3306,database='python',user='root',password='mysql',charset='utf8') # 获得Cursor对象 cursor = conn.cursor() # 插入10万次数据 for i in range(100000): cursor.execute("insert into test_index values('ha-%d')" % i) # 提交数据 conn.commit()if __name__ == "__main__": main()
验证索引性能操作:
-- 开启运行时间监测:set profiling=1;-- 查找第1万条数据ha-99999select * from test_index where title='ha-99999';-- 查看执行的时间:show profiles;-- 给title字段创建索引:alter table test_index add index (title);-- 再次执行查询语句select * from test_index where title='ha-99999';-- 再次查看执行的时间show profiles;
联合索引又叫复合索引,即一个索引覆盖表中两个或者多个字段,一般用在多个字段一起查询的时候。
-- 创建teacher表create table teacher( id int not null primary key auto_increment, name varchar(10), age int);-- 创建联合索引alter table teacher add index (name,age);
联合索引的好处:
在使用联合索引的时候,我们要遵守一个最左原则,即index(name,age)支持 name 、name 和 age 组合查询,而不支持单独 age 查询,因为没有用到创建的联合索引。
最左原则示例:
-- 下面的查询使用到了联合索引select * from stu where name='张三' -- 这里使用了联合索引的name部分select * from stu where name='李四' and age=10 -- 这里完整的使用联合索引,包括 name 和 age 部分 -- 下面的查询没有使用到联合索引select * from stu where age=10 -- 因为联合索引里面没有这个组合,只有 name | name age 这两种组合
说明:
如何实现将100000条数据插入到MySQL数据库?
答案:
如果使用之前学习的MySQL客户端来完成这个操作,那么这个工作量无疑是巨大的,我们可以通过使用程序代码的方式去连接MySQL数据库,然后对MySQL数据库进行增删改查的方式,实现10000条数据的插入,像这样使用代码的方式操作数据库就称为数据库编程。
安装pymysql第三方包:
sudo pip3 install pymysql
说明:
pymysql的使用:
导入 pymysql 包
import pymysql
创建连接对象
调用pymysql模块中的connect()函数来创建连接对象,代码如下:
conn=connect(参数列表) * 参数host:连接的mysql主机,如果本机是'localhost' * 参数port:连接的mysql主机的端口,默认是3306 * 参数user:连接的用户名 * 参数password:连接的密码 * 参数database:数据库的名称 * 参数charset:通信采用的编码方式,推荐使用utf8
连接对象操作说明:
获取游标对象
获取游标对象的目标就是要执行sql语句,完成对数据库的增、删、改、查操作。代码如下:
# 调用连接对象的cursor()方法获取游标对象 cur =conn.cursor()
游标操作说明:
pymysql完成数据的查询操作
import pymysql# 创建连接对象conn = pymysql.connect(host='localhost', port=3306, user='root', password='mysql',database='python', charset='utf8')# 获取游标对象cursor = conn.cursor()# 查询 SQL 语句sql = "select * from students;"# 执行 SQL 语句 返回值就是 SQL 语句在执行过程中影响的行数row_count = cursor.execute(sql)print("SQL 语句执行影响的行数%d" % row_count)# 取出结果集中一行数据, 例如:(1, '张三')# print(cursor.fetchone())# 取出结果集中的所有数据, 例如:((1, '张三'), (2, '李四'), (3, '王五'))for line in cursor.fetchall(): print(line)# 关闭游标cursor.close()# 关闭连接conn.close()
pymysql完成对数据的增删改
import pymysql# 创建连接对象conn = pymysql.connect(host='localhost', port=3306, user='root', password='mysql',database='python', charset='utf8')# 获取游标对象cursor = conn.cursor()try: # 添加 SQL 语句 # sql = "insert into students(name) values('刘璐'), ('王美丽');" # 删除 SQ L语句 # sql = "delete from students where id = 5;" # 修改 SQL 语句 sql = "update students set name = '王铁蛋' where id = 6;" # 执行 SQL 语句 row_count = cursor.execute(sql) print("SQL 语句执行影响的行数%d" % row_count) # 提交数据到数据库 conn.commit()except Exception as e: # 回滚数据, 即撤销刚刚的SQL语句操作 conn.rollback()# 关闭游标cursor.close()# 关闭连接conn.close()
说明:
防止SQL注入
什么是SQL注入?
用户提交带有恶意的数据与SQL语句进行字符串方式的拼接,从而影响了SQL语句的语义,最终产生数据泄露的现象。
如何防止SQL注入?
SQL语句参数化
防止SQL注入的示例代码:
from pymysql import connectdef main(): find_name = input("请输入物品名称:") # 创建Connection连接 conn = connect(host='localhost',port=3306,user='root',password='mysql',database='jing_dong',charset='utf8') # 获得Cursor对象 cs1 = conn.cursor() # 非安全的方式 # 输入 ' or 1 = 1 or ' (单引号也要输入) # sql = "select * from goods where name='%s'" % find_name # print("""sql===>%s<====""" % sql) # # 执行select语句,并返回受影响的行数:查询所有数据 # count = cs1.execute(sql) # 安全的方式 # 构造参数列表 params = [find_name] # 执行select语句,并返回受影响的行数:查询所有数据 count = cs1.execute("select * from goods where name=%s", params) # 注意: # 如果要是有多个参数,需要进行参数化 # 那么params = [数值1, 数值2....],此时sql语句中有多个%s即可 # %s 不需要带引号 # 打印受影响的行数 print(count) # 获取查询的结果 # result = cs1.fetchone() result = cs1.fetchall() # 打印查询的结果 print(result) # 关闭Cursor对象 cs1.close() # 关闭Connection对象 conn.close()if __name__ == '__main__': main()
说明:
导包
import pymysql
创建连接对象
pymysql.connect(参数列表)
获取游标对象
cursor =conn.cursor()
执行SQL语句
row_count = cursor.execute(sql)
获取查询结果集
result = cursor.fetchall()
将修改操作提交到数据库
conn.commit()
回滚数据
conn.rollback()
关闭游标
cursor.close()
关闭连接
conn.close()
一. 准备数据
该案例使用前面章节中完成的京东商品数据库.
二. 程序菜单
print('1. 查询所有商品信息') print("2. 查询所有包含商品的分类") print("3. 添加新商品分类") print("4. 将所有商品价格加1000") print("5. 将所有笔记本的分类改为超级本") print("6. 根据id查询商品信息") print("7. 根据id查询商品信息安全方式") print("8. 退出系统")
三. 方法命名
# 显示菜单方法 def __print_menu(self): pass # 打印结果方法 def __show_query_result(self, result): pass # 服务器运行方法,实现主体逻辑 def run(self): pass # 1. 查询所有商品信息 def __fetch_all_info(self): pass # 2. 查询所有包含商品的分类 def __fetch_cate_of_goods(self): pass # 3. 添加商品分类 def __add_new_cate(self): pass # 4. 将所有商品价格加1000 def __update_price(self): pass # 5. 将所有笔记本的分类改为超级本 def __update_cate(self): pass # 6. 根据id查询商品信息 def __fetch_info_with_id(self): pass # 7. 根据id查询商品信息安全方式 def __fetch_info_with_id_safe(self): pass
四. 代码实现
判断当前是否是主程序入口
if __name__ == '__main__': main()
实现主函数
def main(): # 创建服务器对象,并传入相应参数 jd = JDServer('jd', 'root', '123123') # 启动服务器 jd.run()
实现 JD 类
import pymysql class JDServer(object): """JD 类,提供商品查询服务""" # 将数据库连接操作放到初化方法中,对象创建时,自动连接数据库 def __init__(self, target_db, username, password): # 连接数据库 self.db_connect = pymysql.Connect(host='localhost',port=3306,database=target_db, user=username, passwd=password, charset='utf8') # 将数据库关闭操作放到 __del__方法中,当对象销毁时,自动关闭数据库 def __del__(self): # 关闭数据库 self.db_connect.close()
实现 run 方法 因为需要重复选择,所以要用死循环
# 服务器运行方法,实现主体逻辑,通过判断输入选择相应的功能函数 def run(self): while True: self.__print_menu() select_id = input('请输入功能ID:') if select_id == '1': self.__fetch_all_info() elif select_id == '2': self.__fetch_cate_of_goods() elif select_id == '3': self.__add_new_cate() elif select_id == '4': self.__update_price() elif select_id == '5': self.__update_cate() elif select_id == '6': self.__fetch_info_with_id() elif select_id == '7': self.__fetch_info_with_id_safe() elif select_id == '8': break else: print('输入功能不正确,请重新输入')
实现一个显示方法,用来输出结果
# 用来显示结果的方法,私有,对外不可见 def __show_query_result(self, result): for item in result: print(item)
实现查询所有商品信息
# 1. 查询所有商品信息 def __fetch_all_info(self): cur = self.db_connect.cursor() sql_str = '''select * from goods''' cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
查询所有包含商品的分类
# 2. 查询所有包含商品的分类 def __fetch_cate_of_goods(self): cur = self.db_connect.cursor() sql_str = '''select distinct good_cates.name from goods inner join good_cates on goods.cate_id = good_cates.id;''' # sql_str = ''' select name from good_cates where id in (select distinct cate_id from goods); ''' cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
添加商品分类
# 3. 添加商品分类 def __add_new_cate(self): new_cate = input('请输入一个新商品分类:') sql_str = ''' insert into good_cates(name) values("%s") ''' % new_cate cur = self.db_connect.cursor() cur.execute(sql_str) self.db_connect.commit() cur.close()
将所有商品价格加1000
# 4. 将所有商品价格加1000 def __update_price(self): cur = self.db_connect.cursor() sql_str = ''' update goods set price = price + 1000''' cur.execute(sql_str) self.db_connect.commit() cur.close()
将所有笔记本的分类改为超级本
# 5. 将所有笔记本的分类改为超级本def __update_cate(self): cur = self.db_connect.cursor() sql_str = '''update goods set cate_id = (select id from good_cates where name = '超级本') where name like '%笔记本%';''' cur.execute(sql_str) self.db_connect.commit() cur.close()
根据id 查找商品,会产生SQL注入问题
# 6. 根据id查询商品信息def __fetch_info_with_id(self): s_id = input('请输入一个商品ID:') sql_str = ''' select * from goods where id = %s ''' % s_id print(sql_str) cur = self.db_connect.cursor() cur.execute(sql_str) result = cur.fetchall() self.__show_query_result(result) cur.close()
根据id查询商品信息安全方式,案例防注入
# 7. 根据id查询商品信息安全方式def __fetch_info_with_id_safe(self): s_id = input('请输入一个商品ID:') sql_str = ''' select * from goods where id = %s ''' print(sql_str) cur = self.db_connect.cursor() cur.execute(sql_str, (s_id,)) result = cur.fetchall() self.__show_query_result(result) cur.close()