开发字典

Python Web开发 – 集成百科

文章目录

  • Python Web开发 -- 集成百科
    • @[toc]
  • WEB开发框架(Django + Flask + 通用第三方)
    • Flask相关知识
      • 虚拟环境
      • 创建Flask的应用对象
      • 配置参数
      • Flask的数据库设置
      • 蓝图Blueprint
        • 蓝图:用于实现单个应用的视图、模版、静态文件的集合。
        • 蓝图的运行机制:
      • 蓝图的使用
    • Django相关知识
      • Django的安装
        • 官网文档
        • 安装
        • Django框架开发
          • 创建项目指令
      • Django项目的目录结构
      • HTTP请求
      • 模版 Templates
      • 应用的分布式路由
        • include 函数
      • Django下配置使用mysql数据库,以及ORM框架
        • 安装Pymysql包
        • 创建和配置数据库
        • Django的ORM框架
          • 模型(Models)
          • ORM
          • 查询谓词
          • 修改数据记录
          • 删除记录
          • 聚合查询
      • Django中设置缓存
        • 数据库缓存
        • 文件系统缓存
        • 本地内存缓存
      • Django中使用缓存
        • 在视图View中使用cache
        • 在路由中使用
        • 在模板中使用
      • 中间件 Middleware
        • 中间件概念
        • 中间件类
        • 编写中间件类
        • 注册中间件:
        • 中间件应用(实例):
      • 文件上传
        • 概念
        • 设置
      • Django-redis
        • 安装
        • cahe backend使用配置
        • session backend使用配置
      • Django认证系统
        • 概念
        • User模型类基本属性
        • auth基本模型操作
          • 创建用户
          • 删除用户
          • 修改密码set_password
          • 检查密码是否正确check_password
      • 生成CSV文件
      • 电子邮件发送
        • settings.py 设置
        • 视图函数中应用
      • app集中文件夹配置
      • 类视图
      • 装饰器
        • login_required
      • 模型管理器类
      • AJAX(阿贾克斯)
        • Python中的JSON处理
        • Django中的JSON处理
        • 前端中的JSON处理
    • 第三方库(方法)相关知识
      • itsdangerous模块(加密)
        • 安装 :(itsdangerous)
      • Celery 分布式消息传输的异步任务队列
        • 定义
        • 安装:celery
        • 使用场景
        • 名词解释:
        • 使用celery
  • 上线时执行
      • 富文本编辑器
      • tinymce
      • 文件系统FastDFS
        • 安装fastdfs依赖包
        • 安装FastDFS
        • 配置跟踪服务器 tracker
        • 配置存储服务器storage
        • 启动tracker和storage,nginx
        • 测试是否安装成功
        • 上传文件测试
        • Nginx配合FastDFS使用安装和配置
        • 使用Python客户端上传测试
        • 自定义文件存储类(Django二次开发)
          • **自定义类**
    • 项目部署
      • 安装相同版本的包
      • WSGI Django工作环境部署
      • uWSGI 网关接口配置 (ubuntu 18.04 配置)
        • 安装uwsgi
        • 配置uwsgi
        • uWSGI的运行管理
          • 启动uwsgi
          • 停止uwsgi
      • nginx 反向代理配置
        • 概念
        • 安装
        • nginx 配置
          • 修改nginx 的配置文件
        • nginx服务控制
        • 修改uWSGI配置
        • nginx 配置静态文件路径
      • 部署文档 【腾讯云】ubuntu 18.04
        • 404 界面
  • 前后端分离
    • 1.1 什么是前后端分离
    • 1.2 优点
    • 1.3 分离常见问题
    • 1.4 实现方式
  • token - 令牌
    • 须知:
    • 2.1 JWT - json-web-token
      • 1,三大组成
      • 2,jwt结果格式
      • 3,校验jwt规则
      • 4,pyjwt
  • CORS - Cross-origin resource sharing - 跨域资源共享
    • 1,什么是CORS
    • 2,特点
    • 3,简单请求(Simple requests)和预检请求(Preflighted requests)
    • 4,简单请求发送流程
    • 5,预检请求发送流程
    • 6,Django支持
  • RESTful -Representational State Transfer
    • 4.1,什么是RESTful
    • 4.2 RESTful的特征
    • 4.3 如何设计符合RESTful 特征的API
  • 数据库
    • Mysql
      • **安装**
      • ALTER USER
        • **修改当前登陆用户密码**
        • **设置密码过期**
        • **设置密码永不过期**
        • **设置密码按照默认过期的时间**
        • **设置密码指定的过期时间**
      • SET PASSWORD
        • **mysql 默认加密方法**
        • **mysql 中的 password 函数加密方法**
      • 启动链接
      • 基本SQL命令
        • 库管理
        • 表管理
        • 表记录管理
        • 表字段管理
    • Redis
      • 安装
      • 数据类型通用命令
        • 通用命令==适用于所有数据类型
        • 字符串常用命令
        • 数值操作字符串类型数字
        • 键命名规范
        • string命令汇总
      • 列表数据类型通用命令
        • 列表常用命令
      • 配置文件详解
      • 与Python交互
      • 位图操作bitmap
        • 设置某一位上的值(setbit)
        • 示例
        • 获取某一位上的值
      • Hash散列数据类型
        • 基本操作命令
        • Hash与python交互
        • Python代码hash散列
        • 应用场景:微博好友关注
        • **应用场景: redis+mysql+hash组合使用**
          • 原理
          • 代码实现
          • mysql数据库中数据更新信息后同步到redis缓存
      • 集合数据类型
        • 基本命令
        • 案例: 新浪微博的共同关注
        • python操作set
        • python代码实现微博关注
      • 有序集合sortedset
        • 有序集合常用命令
        • python操作sorted set
      • 数据持久化
        • 数据持久化分类之 - RDB模式(默认开启)
          • 默认模式
          • 创建rdb文件的两种方式
            • 方式一:服务器执行客户端发送的SAVE或者BGSAVE命令
            • 方式二:设置配置文件条件满足时自动保存**(使用最多)**
          • 数据持久化分类之 - AOF(AppendOnlyFile,默认未开启)
            • 特点
            • RDB缺点
            • AOF持久化原理及优点
            • 安全性问题考虑
            • 策略 - 配置文件
            • AOF文件中是否会产生很多的冗余命令?
            • 示例
            • AOF文件重写方法触发
            • RDB和AOF持久化对比
            • 数据恢复(无需手动操作)
            • 配置文件常用配置总结
            • **Redis相关文件存放路径**
      • Redis主从复制
        • 定义
        • 作用
        • 原理
        • 两种实现方式
          • 方式一:(Linux命令行实现1)
          • 方式一:(Redis命令行实现2)
          • 示例
          • 方式二:(修改配置文件)
          • 演示
        • 官方高可用方案Sentinel
          • Redis之哨兵 - sentinel
          • 案例演示
            • **环境搭建**
            • **安装并搭建sentinel哨兵**
            • **sentinel.conf解释**
            • **生产环境中设置哨兵sentinel**
        • 分布式锁
          • 博客项目解决高并发问题
      • Redis事务
        • 事务命令
        • 使用步骤
        • 事务中命令错误处理
        • **python使用pipeline()与execute()批量进行批量操作**
          • 示例
      • Redis常见问题汇总
        • Redis优点
        • 介绍一下redis中的数据类型
        • redis中的持久化方案
        • 使用过Redis分布式锁么,它是什么回事?
        • 缓存穿透
        • 缓存击穿
        • 缓存雪崩
      • Redis 存储 Session
        • 安装
        • 修改settings.py文件
        • 在view.py文件里,修改session_test视图
  • shell命令
    • 查看Redis进程

WEB开发框架(Django + Flask + 通用第三方)

Flask相关知识

  • **官网:**https://palletsprojects.com/p/flask/

虚拟环境

虚拟环境使用virtualenv创建,可以查看系统是否安装了virtualenv

# coding:urf-8
$ virtualenv  --version

安装虚拟环境(须在联网环境下)

$ sudo pip install virtualenv
$ sudo pip install virtualenvwrapper

安装完虚拟环境后,如果提示找不到mkvirtualenv命令,须配置环境变量

# 1 创建目录用来存放虚拟环境
mkdir SHOME/.virtualenv
# 2 打开~/.bashrc文件,并添加如下
export WORKON_HOME=SHOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
# 3 运行
source ~/.bashrc


创建虚拟环境(ubuntu里须在联网状态下)
# 虚拟环境是一个互相隔离的目录
$ mkvirtualenv Flask_py

pip freeze > requirements.txt
pip install -r requirements.txt

退出虚拟环境
如果所在环境为真实环境,会提示deactivate:未找到命令
$ deactivate Flask_py




创建Flask的应用对象

# __name__表示当前的模块名字
#flask 以这个模块所在的目录为总目录
app = Flask(__name__)  

# 访问静态资源的url前缀
app = Flask(__name__,
           static_url_path="/python",   # 访问静态资源的url前缀,默认是static
           static_folder="static",      # 静态文件目录,默认是static
           template_folder="templates", # 模版文件的目录,默认是templates
           )


@app.route("/")   # 路由
def index():
    # 定义的视图函数
	return "hello flask"

if __name__ == '__main__':
    # 启动flask程序
    app.run()

配置参数

app.config.from_pyfile(yourconfig.cfg)
# 或者
app.config.from_object()

Flask的数据库设置

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 设置连接数据库的url
app.config['SQLALCHEMY_DATABASE_URL']='mysql+pymysql://root:[email protected]:3306/idiom'

#设置每次请求结束后自动提交数据库中的改动
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

# 查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

蓝图Blueprint


  • 蓝图:用于实现单个应用的视图、模版、静态文件的集合。

    • 蓝图就是模块化处理的类。
    • 简单来说,蓝图就是一个储存操作路由映射方法的容器,主要用来实现客户端请求和URL相互关联的功能。在Flask中,使用蓝图可以帮助我们实现模块化应用的功能。
  • 蓝图的运行机制:

    • 蓝图是保存了一组将来可以在应用对象上执行的操作。注册路由就是一种操作,当在程序实例上调用route装饰器注册路由时,这个操作将修改对象的url_map路由映射列表。当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项。当执行应用对象的register_blueprint()方法时,应用对象从蓝图对象的defered_functions列表中取出每一项,即调用应用对象的add_url_rule()方法,这将会修改程序实例的路由映射列表。
  • 蓝图的使用

    # 创建蓝图对象
    admin = Blueprint('admin',__name__)   # 参数一:为蓝图起名字
    # 注册蓝图
    app.register_blueprint(admin,url_prefix="/admin") # 设置前缀
    
    

Django相关知识

Django的安装

官网文档

  • 官方网址:
    • http://www.djangoproject.com
  • 中文文档(第三方):
    • https://yiyibooks.cn/
    • http://djangobook.py3k.cn/

安装

  1. 安装

    $ sudo pip3 install django   	       # 安装django的最新版本
    $ sudo pip3 install django[==版本]      # 安装django的指定版本
    # 如:
    	$ sudo pip3 install django==1.11.8
    
  2. 卸载

    $ pip3 uninstall django
    

Django框架开发

创建项目指令
  • $ django-admin startproject [项目名称] # 创建项目
  • $ python3 manage.py runserver # 启动服务
  • $ python3 manage.py startapp[应用名称] # 创建应用
  • $ python3 manage.py migrate # 数据库迁移
  • $ python3 namege.py makemigrations

Django项目的目录结构

  • 示例:

    $ django-admin startproject mysite1
    $ tree mysite1/
    mysite1/
    ├── manage.py
    └── mysite1
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
    
    1 directory, 5 files
    
  • 项目目录结构解析:

    • manage.py

      • 此文件是项目管理的主程序,在开发阶段用于管理整个项目的开发运行的调式
      • manage.py 包含项目管理的子命令, 如:
        • python3 manage.py runserver 启动服务
        • python3 manage.py startapp 创建应用
        • python3 manage.py migrate 数据库迁移
        • ...
    • mysite1 项目包文件夹

      • 项目包的主文件夹(默认与项目名称一致)
      1. __init__.py

        • 包初始化文件,当此项目包被导入(import)时此文件会自动运行
      2. wsgi.py

        • WSGI 即 Web Server Gateway Interface 与 nginx 相连
        • WEB服务网关接口的配置文件,仅部署项目时使用
      3. urls.py

        • 项目的基础路由配置文件,所有的动态路径必须先走该文件进行匹配
      4. settings.py

        • Django项目的配置文件, 此配置文件中的一些全局变量将为Django框架的运行传递一些参数
        • setting.py 配置文件,启动服务时自动调用,
        • 此配置文件中也可以定义一些自定义的变量用于作用全局作用域的数据传递

        http://127.0.0.1:8000/admin/

  • settings.py 文件介绍

    1. BASE_DIR

      • 用于绑定当前项目的绝对路径(动态计算出来的), 所有文件都可以依懒此路径
    2. DEBUG

      • 用于配置Django项目的启用模式, 取值:
        1. True 表示开发环境中使用 调试模式(用于开发中)
        2. False 表示当前项目运行在生产环境中(不启用调试)
    3. ALLOWED_HOSTS

      • 设置允许访问到本项目的网络地址列表,取值:

        1. [] 空列表,表示只有127.0.0.1, localhost能访问本项目
        2. [’*’],表示任何网络地址都能访问到当前项目
        3. [‘192.168.1.3’, ‘192.168.3.3’] 表示只有当前两个主机能访问当前项目
        • 注意:
          • 如果要在局域网其它主机也能访问此主机,启动方式应使用如下模式:
      • python3 manage.py runserver 0.0.0.0:5000 # 指定网络设备所有主机都可以通过5000端口访问(需加ALLOWED_HOSTS = ['*'])

    4. INSTALLED_APPS

      • 指定当前项目中安装的应用列表
    5. MIDDLEWARE

      • 用于注册中间件
    6. TEMPLATES

      • 用于指定模板的配置信息
    7. DATABASES

      • 用于指定数据库的配置信息
    8. LANGUAGE_CODE

      • 用于指定语言配置
      • 取值:
        • 英文 : "en-us"
        • 中文 : "zh-Hans"
    9. TIME_ZONE

      • 用于指定当前服务器端时区
      • 取值:
        • 世界标准时间: "UTC"
        • 中国时区 : "Asia/Shanghai"
    10. ROOT_URLCONF

      • 用于配置根级 url 配置 ‘mysite1.urls’
      • 如:
        • ROOT_URLCONF = 'mysite1.urls'

    注: 此模块可以通过 from django.conf import settings 导入和使用

HTTP请求

  • HTTP1.1 请求详述

    序号 方法 描述
    1 GET 请求指定的页面信息,并返回实体主体。
    2 HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
    3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
    4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。
    5 DELETE 请求服务器删除指定的页面。
    6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    7 OPTIONS 允许客户端查看服务器的性能。
    8 TRACE 回显服务器收到的请求,主要用于测试或诊断。
  • HttpRequest对象

    • 视图函数的第一个参数是HttpRequest对象
    • 服务器接收到http协议的请求后,会根据请求数据报文创建HttpRequest对象
    • HttpRequest属性
      • path:字符串,表示请求的路由信息
      • path_info: URL字符串
      • method:字符串,表示HTTP请求方法,常用值:‘GET’、‘POST’
      • encoding:字符串,表示提交的数据的编码方式
        • 如果为None则表示使用浏览器的默认设置,一般为’utf-8’
        • 这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值
      • GET:QueryDict查询字典的对象,包含get请求方式的所有数据
      • POST:QueryDict查询字典的对象,包含post请求方式的所有数据
      • FILES:类似于字典的对象,包含所有的上传文件信息
      • COOKIES:Python字典,包含所有的cookie,键和值都为字符串
      • session:似于字典的对象,表示当前的会话,
      • body: 字符串,请求体的内容(POST或PUT)
      • environ: 字符串,客户端运行的环境变量信息
      • scheme : 请求协议(‘http’/‘https’)
      • request.get_full_path() : 请求的完整路径
      • request.get_host() : 请求的主机
      • request.META : 请求中的元数据(消息头)
        • request.META[‘REMOTE_ADDR’] : 客户端IP地址

模版 Templates

  • 什么是模板

    1. 模板是可以根据字典数据动态变化的html网页
    2. 模板可以根据视图中传递的字典数据动态生成相应的HTML网页。
  • 模板的配置

    • 创建模板文件夹<项目名>/templates
    • 在 settings.py 中有一个 TEMPLATES 变量
      1. BACKEND : 指定模板的引擎
      2. DIRS : 模板的搜索目录(可以是一个或多个)
      3. APP_DIRS : 是否要在应用中的 templates 文件夹中搜索模板文件
      4. OPTIONS : 有关模板的选项
  • 默认的模块文件夹templates

  • 修改settings.py文件,设置TEMPLATES的DIRS值为'DIRS': [os.path.join(BASE_DIR, 'templates')],

# file: settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 'DIRS': [],
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 添加模板路径
        'APP_DIRS': True,  # 是否索引各app里的templates目录
        ...
    },
]
  • 模版的加载方式
  1. 通过 loader 获取模板,通过HttpResponse进行响应

    from django.template import loader
    # 1.通过loader加载模板
    t = loader.get_template("模板文件名")
    # 2.将t转换成 HTML 字符串
    html = t.render(字典数据 )
    # 3.用响应对象将转换的字符串内容返回给浏览器
    return HttpResponse(html)
    
  2. 使用 render() 直接加载并响应模板

    from django.shortcuts import render
    return render(request,'模板文件名', 字典数据)
    

应用的分布式路由

include 函数

from django.conf.urls import url, include
APPEND_SLASH = False
# settings.py 设置为False则URL结尾不添加“/”
  • 作用:

  • 用于分发将当前路由转到各个应用的路由配置文件的 urlpatterns 进行分布式处理

  • 函数格式:

    • include(‘app命字.url模块名’)

      模块app命字/url模块名.py 文件件里必须有urlpatterns 列表

      使用前需要使用 from django.conf.urls import include 导入此函数


Django下配置使用mysql数据库,以及ORM框架

安装Pymysql包

  • 用作 python 和 mysql 的接口

    • $ sudo pip3 install pymysql
  • 安装 mysql 客户端(非必须)

    • $ sudo pip3 install mysqlclient

创建和配置数据库

  1. 创建数据库
  • create database 数据库名 default charset=utf8;
  1. 数据库配置

    • # file:settings.py
      DATABASES = {
          'default' : {
              'ENGINE': 'django.db.backends.mysql',
              'NAME': 'mywebdb',  # 数据库名称,需要自己定义
              'USER': 'root',
              'PASSWORD': '123456',  # 管理员密码
              'HOST': '127.0.0.1',
              'PORT': 3306,
          }
      }
      
  2. 修改项目中__init__.py 加入如下内容来提供pymysql引擎的支持

    import pymysql
    pymysql.install_as_MySQLdb()
    

Django的ORM框架

模型(Models)
  • 模型是一个Python类,它是由django.db.models.Model派生出的子类。
  • 一个模型类代表数据库中的一张数据表
  • 模型类中每一个类属性都代表数据库中的一个字段。
  • 模型是数据交互的接口,是表示和操作数据库的方法和方式
ORM
  1. 编写模型类Models

    • 模型类需继承自django.db.models.Model

      1. Models的语法规范

        from django.db import models
        class 模型类名(models.Model):
            字段名 = models.字段类型(字段选项)
        

        模型类名是数据表名的一部分,建议类名首字母大写
        字段名又是当前类的类属性名,此名称将作为数据表的字段名
        字段类型用来映射到数据表中的字段的类型
        字段选项为这些字段提供附加的参数信息

  2. 字段类型

    1. BooleanField()

      • 数据库类型:tinyint(1)
      • 编程语言中:使用True或False来表示值
      • 在数据库中:使用1或0来表示具体的值
    2. CharField()

      • 数据库类型:varchar
      • 注意:
        • 必须要指定max_length参数值
    3. DateField()

      • 数据库类型:date
      • 作用:表示日期
      • 编程语言中:使用字符串来表示具体值
      • 参数:
        • DateField.auto_now: 每次保存对象时,自动设置该字段为当前时间(取值:True/False)。
        • DateField.auto_now_add: 当对象第一次被创建时自动设置当前时间(取值:True/False)。
        • DateField.default: 设置当前时间(取值:字符串格式时间如: ‘2019-6-1’)。
        • 以上三个参数只能多选一
    4. DateTimeField()

      • 数据库类型:datetime(6)
      • 作用:表示日期和时间
      • auto_now_add=True
    5. DecimalField()

      • 数据库类型:decimal(x,y)

      • 编程语言中:使用小数表示该列的值

      • 在数据库中:使用小数

      • 参数:

        • DecimalField.max_digits: 位数总数,包括小数点后的位数。 该值必须大于等于decimal_places.
        • DecimalField.decimal_places: 小数点后的数字数量
      • 示例:

        money=models.DecimalField(
            max_digits=7,
            decimal_places=2,
            default=0.0
        )
        
    6. FloatField()

      • 数据库类型:double
      • 编程语言中和数据库中都使用小数表示值
    7. EmailField()

      • 数据库类型:varchar
      • 编程语言和数据库中使用字符串
    8. IntegerField()

      • 数据库类型:int
      • 编程语言和数据库中使用整数
    9. URLField()

      • 数据库类型:varchar(200)
      • 编程语言和数据库中使用字符串
    10. ImageField()

      • 数据库类型:varchar(100)

      • 作用:在数据库中为了保存图片的路径

      • 编程语言和数据库中使用字符串

      • 示例:

        image=models.ImageField(
            upload_to="static/images"
        )
        
      • upload_to:指定图片的上传路径
        在后台上传时会自动的将文件保存在指定的目录下

    11. TextField()

      • 数据库类型:longtext
      • 作用:表示不定长的字符数据
    • 参考文档 https://docs.djangoproject.com/en/1.11/ref/models/fields/#field-types
  3. 字段选项FIELD_OPTIONS

      • 字段选项, 指定创建的列的额外的信息
      • 允许出现多个字段选项,多个选项之间使用,隔开
      1. primary_key
        • 如果设置为True,表示该列为主键,如果指定一个字段为主键,则此数库表不会创建id字段
      2. blank
        • 设置为True时,字段可以为空。设置为False时,字段是必须填写的。字符型字段CharField和TextField是用空字符串来存储空值的。 默认值是False。
      3. null
        • 如果设置为True,表示该列值允许为空。日期型、时间型和数字型字段不接受空字符串。所以设置IntegerField,DateTimeField型字段可以为空时,需要将blank,null均设为True。
        • 默认为False,如果此选项为False建议加入default选项来设置默认值
      4. default
        • 设置所在列的默认值,如果字段选项null=False建议添加此项
      5. db_index
        • 如果设置为True,表示为该列增加索引
      6. unique
        • 如果设置为True,表示该字段在数据库中的值必须是唯一(不能重复出现的)
      7. db_column
        • 指定列的名称,如果不指定的话则采用属性名作为列名
      8. verbose_name
        • 设置此字段在admin界面上的显示名称。
      • 示例:

        # 创建一个属性,表示用户名称,长度30个字符,必须是唯一的,不能为空,添加索引
        name = models.CharField(max_length=30, unique=True, null=False, db_index=True)
        
    • 文档参见:
      • https://docs.djangoproject.com/en/1.11/ref/models/fields/#field-options
  4. ORM数据库基本操作

    • 管理器对象

      class MyModel(models.Model):
          ...
      MyModel.objects.create(...) # objects 是管理器对象
      
    • 查询数据

      • 数据库的查询需要使用管理器对象进行

      • 通过 MyModel.objects 管理器方法调用查询接口

        方法 说明
        all() 查询全部记录,返回QuerySet查询对象
        get() 查询符合条件的单一记录
        filter() 查询符合条件的多条记录
        exclude() 查询符合条件之外的全部记录
      1. all()方法

        • 方法: all()

        • 用法: MyModel.objects.all()

        • 作用: 查询MyModel实体中所有的数据

          • 等同于
            • select * from tabel
        • 返回值: QuerySet容器对象,内部存放 MyModel 实例

        • 示例:

          from bookstore import models
          books = models.Book.objects.all()
          for book in books:
              print("书名", book.title, '出版社:', book.pub)
          
      2. 在模型类中定义 def __str__(self): 方法可以将自定义默认的字符串

        class Book(models.Model):
            title = ...
            def __str__(self):
                return "书名: %s, 出版社: %s, 定价: %s" % (self.title, self.pub, self.price)
        
      3. 查询返回指定列(字典表示)

        • 方法: values(‘列1’, ‘列2’)

        • 用法: MyModel.objects.values(…)

        • 作用: 查询部分列的数据并返回

        • select 列1,列2 from xxx

        • 返回值: QuerySet

          • 返回查询结果容器,容器内存储结构为字典,每个字典代表一条数据,
          • 格式为: {‘列1’: 值1, ‘列2’: 值2}
        • 示例:

          from bookstore import models
          books = models.Book.objects.values("title", "pub")
          for book in books:
              print("书名", book["title"], '出版社:', book['pub'])
              print("book=", book)
          
      4. 查询返回指定列(元组表示)

        • 方法:values_list(‘列1’,‘列2’)

        • 用法:MyModel.objects.values_list(…)

        • 作用:

          • 返回元组形式的查询结果
        • 返回值: QuerySet容器对象,内部存放 元组

          • 会将查询出来的数据封装到元组中,再封装到查询集合QuerySet中
        • 示例:

          from bookstore import models
          books = models.Book.objects.values_list("title", "pub")
          for book in books:
          print("book=", book)  # ('Python', '清华大学出版社')...
          
      5. 排序查询

        • 方法:order_by

        • 用法:MyModel.objects.order_by(’-列’,‘列’)

        • 作用:

          • 与all()方法不同,它会用SQL 语句的ORDER BY 子句对查询结果进行根据某个字段选择性的进行排序
        • 说明:

        • 默认是按照升序排序,降序排序则需要在列前增加’-'表示

        • 示例:

          from bookstore import models
          books = models.Book.objects.order_by("-price")
          for book in books:
          print("书名:", book.title, '定价:', book.price)
          
      6. 根据条件查询多条记录

        • 方法: filter(条件)

        • 语法:

          MyModel.objects.filter(属性1=值1, 属性2=值2)

        • 返回值:

      • QuerySet容器对象,内部存放 MyModel 实例

      • 说明:

        • 当多个属性在一起时为"与"关系,即当Books.objects.filter(price=20, pub="清华大学出版社") 返回定价为20 出版社为"清华大学出版社"的全部图书
      • 示例:

        # 查询书中出版社为"清华大学出版社"的图书
        from bookstore import models
        books = models.Book.objects.filter(pub="清华大学出版社")
        for book in books:
            print("书名:", book.title)
        
        2. 查询Author实体中id1并且isActive为True- authors=Author.objects.filter(id=1,isActive=True)
        
查询谓词
  • 每一个查询谓词是一个独立的查询功能
  1. __exact : 等值匹配

    Author.objects.filter(id__exact=1)
    # 等同于select * from author where id = 1
    
  2. __contains : 包含指定值

    Author.objects.filter(name__contains='w')
    # 等同于 select * from author where name like '%w%'
    
  3. __startswith : 以 XXX 开始

  4. __endswith : 以 XXX 结束

  5. __gt : 大于指定值

    Author.objects.filer(age__gt=50)
    # 等同于 select * from author where age > 50
    
  6. __gte : 大于等于

  7. __lt : 小于

  8. __lte : 小于等于

  9. __in : 查找数据是否在指定范围内

    • 示例
    Author.objects.filter(country__in=['中国','日本','韩国'])
    # 等同于 select * from author where country in ('中国','日本','韩国')
    
  10. __range: 查找数据是否在指定的区间范围内

    # 查找年龄在某一区间内的所有作者
    Author.objects.filter(age__range=(35,50))
    # 等同于 SELECT ... WHERE Author BETWEEN 35 and 50;
    
  11. 详细内容参见: https://docs.djangoproject.com/en/1.11/ref/models/querysets/#field-lookups

修改数据记录
  1. 修改单个实体的某些字段值的步骤:

      • 通过 get() 得到要修改的实体对象
      • 通过 对象.属性 的方式修改数据
    1. 保存
      • 通过 对象.save() 保存数据
    • 如:

      from bookstore import models
      abook = models.Book.objects.get(id=10)
      abook.market_price = "10.5"
      abook.save()
      
  2. 通过 QuerySet 批量修改 对应的全部字段

    • 直接调用QuerySet的update(属性=值) 实现批量修改

    • 如:

      # 将 id大于3的所有图书价格定为0元
      books = Book.objects.filter(id__gt=3)
      books.update(price=0)
      
      # 将所有书的零售价定为100元
      books = Book.objects.all()
      books.update(market_price=100)
      
删除记录
  • 删除记录是指删除数据库中的一条或多条记录
  • 删除单个MyModel对象或删除一个查询结果集(QuerySet)中的全部对象都是调用 delete()方法
  1. 删除单个对象

    • 步骤

      1. 查找查询结果对应的一个数据对象
      2. 调用这个数据对象的delete()方法实现删除
    • 示例:

      try:
          auth = Author.objects.get(id=1)
          auth.delete()
      except:
          print(删除失败)
      
  2. 删除查询结果集

    • 步骤

      1. 查找查询结果集中满足条件的全部QuerySet查询集合对象
      2. 调用查询集合对象的delete()方法实现删除
    • 示例:

      # 删除全部作者中,年龄大于65的全部信息
      auths = Author.objects.filter(age__gt=65)
      auths.delete()
      
聚合查询
  • 聚合查询是指对一个数据表中的一个字段的数据进行部分或全部进行统计查询,查bookstore_book数据表中的全部书的平均价格,查询所有书的总个数等,都要使用聚合查询
  1. 不带分组聚合

    • 不带分组的聚合查询是指导将全部数据进行集中统计查询

    • 聚合函数:

      • 定义模块: django.db.models
      • 用法: from django.db.models import *
      • 聚合函数:
        • Sum, Avg, Count, Max, Min
    • 语法:

      • MyModel.objects.aggregate(结果变量名=聚合函数(‘列’))
    • 返回结果:

      • 由 结果变量名和值组成的字典
      • 格式为:
        • `{“结果变量名”: 值}
    • 示例:

      # 得到所有书的平均价格
      from bookstore import models
      from django.db.models import Count
      result = models.Book.objects.aggregate(myAvg=Avg('price'))
      print("平均价格是:", result['myAvg'])
      print("result=", result)  # {"myAvg": 58.2}
      
      # 得到数据表里有多少本书
      from django.db.models import Count
      result = models.Book.objects.aggregate(mycnt=Count('title'))
      print("数据记录总个数是:", result['mycnt'])
      print("result=", result)  # {"mycnt": 10}
      
      
  2. 分组聚合

    • 分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。

    • 语法:

      • QuerySet.annotate(结果变量名=聚合函数(‘列’))
    • 用法步骤:

      1. 通过先用查询结果MyModel.objects.value. 查找查询要分组聚合的列

        • MyModel.objects.value(‘列1’, ‘列2’)

        • 如:

          pub_set = models.Book.objects.values('pub')
          print(books)  # 
          
          
      2. 通过返回结果的 QuerySet.annotate 方法分组聚合得到分组结果

        • QuerySet.annotate(名=聚合函数(‘列’))

        • 返回 QuerySet 结果集,内部存储结果的字典

        • 如:

          pub_count_set = pub_set.annotate(myCount=Count('pub'))
          print(pub_count_set)  # 
          
      • .values(‘查询列名’)
    • 示例:

      • 得到哪儿个出版社共出版多少本书
      def test_annotate(request):
         - from django.db.models import Count
      from . import models
      
          # 得到所有出版社的查询集合QuerySet
          pub_set = models.Book.objects.values('pub')
          # 根据出版社查询分组,出版社和Count的分组聚合查询集合
          pub_count_set = pub_set.annotate(myCount=Count('pub'))  # 返回查询集合
          for item in pub_count_set:
              print("出版社:", item['pub'], "图书有:", item['myCount'])
          return HttpResponse('请查看服务器端控制台获取结果')
      

Django中设置缓存

数据库缓存

Django可以将其缓存的数据存储在您的数据库中

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
        'OPTIONS':{
            
            'MAX_ENTRIES': 300, #当前最大缓存数
            'CULL_FREQUENCY': 3 #删除频率  1/cull_frequency
        }
        
        
    }
}

创建缓存表

python3 manage.py createcachetable

文件系统缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',#这个是文件夹的路径
        #'LOCATION': 'c:\test\cache',#windows下示例
    }
}

本地内存缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake'
    }
}

Django中使用缓存

  • 在视图View中使用
  • 在路由URL中使用
  • 在模板中使用

在视图View中使用cache

from django.views.decorators.cache import cache_page

@cache_page(30)  -> 单位s
def my_view(request):
    ...

在路由中使用

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/', cache_page(60)(my_view)),
]

在模板中使用

{% load cache %}  
{% cache 50 sidebar  request.user.username %}   
    .. sidebar for logged in user ..
{% endcache %}
  • cache_key = sidebar + username
  • guoxiaonao访问 时 cache_key = sidebar + guoxiaonao

中间件 Middleware

中间件概念

  • 中间件是 Django 请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变 Django 的输入或输出。
  • 每个中间件组件负责做一些特定的功能。例如,Django 包含一个中间件组件 AuthenticationMiddleware,它使用会话将用户与请求关联起来。
  • 他的文档解释了中间件是如何工作的,如何激活中间件,以及如何编写自己的中间件。Django 具有一些内置的中间件,你可以直接使用。它们被记录在 built-in middleware reference 中。

中间件类

  • 中间件类须继承自 django.utils.deprecation.MiddlewareMixin
  • 中间件类须实现下列五个方法中的一个或多个:
    • **def process_request(self, request): 执行路由之前被调用,在每个请求上调用,返回None或HttpResponse对象 **
    • def process_view(self, request, callback, callback_args, callback_kwargs): 调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象
    • def process_response(self, request, response): 所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象
    • def process_exception(self, request, exception): 当处理过程中抛出异常时调用,返回一个HttpResponse对象
    • def process_template_response(self, request, response): 在视图刚好执行完毕之后被调用,在每个请求上调用,返回实现了render方法的响应对象
  • 注: 中间件中的大多数方法在返回None时表示忽略当前操作进入下一项事件,当返回HttpResponese对象时表示此请求结束,直接返回给客户端

编写中间件类

# file : middleware/mymiddleware.py
from django.http import HttpResponse, Http404
from django.utils.deprecation import MiddlewareMixin

class MyMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        print("中间件方法 process_request 被调用")

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print("中间件方法 process_view 被调用")

    def process_response(self, request, response):
        print("中间件方法 process_response 被调用")
        return response

    def process_exception(self, request, exception):
        print("中间件方法 process_exception 被调用")

    def process_template_response(self, request, response):
        print("中间件方法 process_template_response 被调用")
        return response

注册中间件:

# file : settings.py
MIDDLEWARE = [
    ...
    'middleware.mymiddleware.MyMiddleWare',
]
#单个中间件输出
MyMW process_request do---
MyMW process_views do ---
----this is test cache views ----
MyMW process_response do ---
#多个中间件时 输出
MyMW process_request do---
MyMW2 process_request do---
MyMW process_views do ---
MyMW2 process_views do ---
----this is test cache views ----
MyMW2 process_response do ---
MyMW process_response do ---

中间件应用(实例):

  • 中间件的执行过程

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8lFTS4Q-1587894431767)(C:/Users/zhang/Downloads/django/images/middleware.jpeg)]
  • 需求

    • 用中间件实现强制某个IP地址只能向/test 发送 5 次GET请求

    • 提示:

      • request.META[‘REMOTE_ADDR’] 可以得到远程客户端的IP地址
      • request.path_info 可以得到客户端访问的GET请求路由信息
    • 代码

      from django.http import HttpResponse, Http404
      from django.utils.deprecation import MiddlewareMixin
      import re
      class VisitLimit(MiddlewareMixin):
          '''此中间件限制一个IP地址对应的访问/user/login 的次数不能改过10次,超过后禁止使用'''
          visit_times = {}  # 此字典用于记录客户端IP地址有访问次数
          def process_request(self, request):
              ip_address = request.META['REMOTE_ADDR']  # 得到IP地址
              if not re.match('^/test', request.path_info):
                  return
              times = self.visit_times.get(ip_address, 0)
              print("IP:", ip_address, '已经访问过', times, '次!:', request.path_info)
              self.visit_times[ip_address] = times + 1
              if times < 5:
                  return
      
              return HttpResponse('你已经访问过' + str(times) + '次,您被禁止了')
      

文件上传

概念

  • 文件上传必须为POST提交方式
  • 表单
    中文件上传时必须有带有enctype="multipart/form-data" 时才会包含文件内容数据。
  • 表单中用标签上传文件
    • 名字xxx对应request.FILES['xxx'] 对应的内存缓冲文件流对象。可通能过request.FILES['xxx'] 返回的对象获取上传文件数据
    • file=request.FILES['xxx'] file 绑定文件流对象,可以通过文件流对象的如下信息获取文件数据
      file.name 文件名
      file.file 文件的字节流数据

设置

  • 在setting.py 中设置一个变量MEDIA_ROOT 用来记录上传文件的位置

    # file : settings.py
    ...
    MEDIA_ROOT = os.path.join(BASE_DIR, 'static/files')
    
  • 在当前项目文件夹下创建 static/files 文件夹

    $ mkdir -p static/files
    
  • 添加路由及对应的处理函数

    # file urls.py
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^upload', views.upload_view)
    ]
    
  • 上传文件的视图处理函数

    # file views.py
    from django.http import HttpResponse, Http404
    from django.conf import settings
    import os
    
    def upload_view(request):
        if request.method == 'GET':
            return render(request, 'index/upload.html')
        elif request.method == "POST":
            a_file = request.FILES['myfile']
            print("上传文件名是:", a_file.name)
    
            filename =os.path.join(settings.MEDIA_ROOT, a_file.name)
            with open(filename, 'wb') as f:
                data = a_file.file.read()
                f.write(data)
                return HttpResponse("接收文件:" + a_file.name + "成功")
        raise Http404
    

Django-redis

  • 介绍
    • django-redis 基于BSD许可,是一个使Django支持Redis cache/session后端的功能组件

安装

  • pip3 install django-redis

cahe backend使用配置

  • 更改配置文件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 backend使用配置

  • 更改配置文件settings.py
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

Django认证系统

概念

  • Django带有一个用户认证系统。 它处理用户账号、组、权限以及基于cookie的用户会话

  • 作用:

    1. 添加普通用户和超级用户
    2. 修改密码
  • 文档参见https://docs.djangoproject.com/en/1.11/topics/auth/

  • User模型类

    • 位置: from django.contrib.auth.models import User

User模型类基本属性

  • 默认user的基本属性有:

    属性名 类型 是否必选
    username 用户名
    password 密码
    email 邮箱 可选
    first_name
    last_name
    is_superuser 是否是管理员帐号(/admin)
    is_staff 是否可以访问admin管理界面
    is_active 是否是活跃用户,默认True。一般不删除用户,而是将用户的is_active设为False。
    last_login 上一次的登录时间
    date_joined 用户创建的时间
  • 数据库表现形式

    mysql> use myauth;
    mysql> desc auth_user;
    +--------------+--------------+------+-----+---------+----------------+
    | Field        | Type         | Null | Key | Default | Extra          |
    +--------------+--------------+------+-----+---------+----------------+
    | id           | int(11)      | NO   | PRI | NULL    | auto_increment |
    | password     | varchar(128) | NO   |     | NULL    |                |
    | last_login   | datetime(6)  | YES  |     | NULL    |                |
    | is_superuser | tinyint(1)   | NO   |     | NULL    |                |
    | username     | varchar(150) | NO   | UNI | NULL    |                |
    | first_name   | varchar(30)  | NO   |     | NULL    |                |
    | last_name    | varchar(30)  | NO   |     | NULL    |                |
    | email        | varchar(254) | NO   |     | NULL    |                |
    | is_staff     | tinyint(1)   | NO   |     | NULL    |                |
    | is_active    | tinyint(1)   | NO   |     | NULL    |                |
    | date_joined  | datetime(6)  | NO   |     | NULL    |                |
    +--------------+--------------+------+-----+---------+----------------+
    11 rows in set (0.00 sec)
    

auth基本模型操作

创建用户
  • 创建普通用户create_user

    from django.contrib.auth import models
    user = models.User.objects.create_user(username='用户名', password='密码', email='邮箱',...)
    
    ...
    user.save()
    
  • 创建超级用户create_superuser

    from django.contrib.auth import models
    user = models.User.objects.create_superuser(username='用户名', password='密码', email='邮箱',...)
    ...
    user.save()
    
删除用户
from django.contrib.auth import models
try:
    user = models.User.objects.get(username='用户名')
    user.is_active = False  # 记当前用户无效
    user.save()
    print("删除普通用户成功!")
except:
    print("删除普通用户失败")
return HttpResponseRedirect('/user/info')
修改密码set_password
from django.contrib.auth import models
try:
    user = models.User.objects.get(username='xiaonao')
    user.set_password('654321')
    user.save()
    return HttpResponse("修改密码成功!")
except:
    return HttpResponse("修改密码失败!")
检查密码是否正确check_password
from django.contrib.auth import models
try:
    user = models.User.objects.get(username='xiaonao')
    if user.check_password('654321'):  # 成功返回True,失败返回False
        return HttpResponse("密码正确")
    else:
        return HttpResponse("密码错误")
except:
    return HttpResponse("没有此用户!")

生成CSV文件

  • Django可直接在视图函数中生成csv文件 并响应给浏览器

    import csv
    from django.http import HttpResponse
    from .models import Book
    
    def make_csv_view(request):
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="mybook.csv"'
    	all_book = Book.objects.all()
        writer = csv.writer(response)
        writer.writerow(['id', 'title'])
        for b in all_book:    
        	writer.writerow([b.id, b.title])
        return response
    
  • 响应获得一个特殊的MIME类型text / csv。这告诉浏览器该文档是CSV文件,而不是HTML文件

  • 响应会获得一个额外的Content-Disposition标头,其中包含CSV文件的名称。它将被浏览器用于“另存为…”对话框

  • 对于CSV文件中的每一行,调用writer.writerow,传递一个可迭代对象,如列表或元组。


电子邮件发送

  • 利用QQ邮箱发送电子邮件

  • django.core.mail 子包封装了 电子邮件的自动发送SMT协议

  • 前期准备:

    1. 申请QQ号
    2. 用QQ号登陆QQ邮箱并修改设置
      • 用申请到的QQ号和密码登陆到 https://mail.qq.com/
      • 修改 QQ邮箱->设置->帐户->“POP3/IMAP......服务”
    3. 设置Django服务器端的,用简单邮件传输协议SMTP(Simple Mail Transfer Protocol) 发送电子邮件

settings.py 设置

# 发送邮件设置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 固定写法
EMAIL_HOST = 'smtp.qq.com' # 腾讯QQ邮箱 SMTP 服务器地址
EMAIL_PORT = 25  # SMTP服务的端口号
EMAIL_HOST_USER = '[email protected]'  # 发送邮件的QQ邮箱
EMAIL_HOST_PASSWORD = '******'  # 邮箱的授权码(即QQ密码)
EMAIL_USE_TLS = True  # 与SMTP服务器通信时,是否启动TLS链接(安全链接)默认false

mail.send_mail(subject='test1111', message='123',from_email='[email protected]',recipient_list=['[email protected]'], auth_password='gjy19881227')


视图函数中应用

from django.core import mail
mail.send_mail(
            subject,  #题目
            message,  # 消息内容
            from_email,  # 发送者[当前配置邮箱]
            recipient_list=['[email protected]'],  # 接收者邮件列表
            auth_password='xxxxxxx'  # 在QQ邮箱->设置->帐户->“POP3/IMAP......服务” 里得到的在第三方登录QQ邮箱授权码
            )

app集中文件夹配置

### 在settings.py里配置
import sys   
sys.path.insert(0,os.path.join(BASE_DIR,'apps'))   --->'apps'为集合文件夹名


class Meta:
    abstract = True   # 注明为抽象模型类

类视图

### 定义一个类视图
from django.views.generic import View

class ['类名'](View):   # 继承View
    def get(self,request):     # 获取get请求
        '...逻辑'
        return render(request,'页面')
    def post(self,request):     # 获取post请求
        '...逻辑'
        return render(request,'页面')
    
修改app里urls.py里的路由
from ['app名'].views import ['类名']
urlpatterns = [
    url(r'^register$',['类名'].as_view(),name='')
]

装饰器

login_required

  • 调用
from django.contrib.auth.decorators import login_required

class LoginRequiredMixin():
    @classmethod
    def as_view(cls,**initkwargs):
        # 调用父类的as_view
        view = super(LoginRequiredMixin,cls).as_view(**initkwargs)
        return login_required(view)
        
        

模型管理器类

AJAX(阿贾克斯)

Python中的JSON处理

    import json
    jsonStr = json.dumps(元组|列表|字典)
    return jsonStr

Django中的JSON处理

#方法1 使用Django中提供的序列化类来完成QuerySet到JSON字符串的转换
from django.core import serializers
json_str = serializers.serialize('json',QuerySet)
return HttpResponse(json_str)

#方法2
d = {'a': 1}
return JsonResponse(d)

前端中的JSON处理

  • 服务器端响应回来的数据是 String,需进行转换
JSON对象=JSON.parse(JSON字符串)

第三方库(方法)相关知识

itsdangerous模块(加密)

一般的,邮件激活时,激活链接需要一个携带用户信息的url,来判断是激活的哪一个用户,且用户信息必须加密。python中可以使用 itsdangerous 来进行加解密。

安装 :(itsdangerous)

  • pip3 install itsdangerous # 下载模块
  • 使用 itsdangerous 加密:
import itsdangerous
from itsdangerous import TimedJSONWebSignatureSerializer as Serialize
# TimedJSONWebSignatureSerializer -->   该类可以加密,还可以设置加密过期时间
from itsdangerous import SignatureExpired  # 捕获异常

使用方法:
    s = Serialize('sdfsfs',3600)   # 创建对象  参数1-->秘钥  参数2-->过期时间,秒
    info= {'code':1}		  	   # 需要加密的信息
    token = s.dumps(info)     	   # 加密,返回值 --> 加密后的信息
    s.loads(token)     	  	  	   # 解密
    
捕获异常:
	# s.loads(token) 如果过期等会出现异常
    try:
        info = s.loads(token)
    except SignatureExpired as e:
        # 捕获到异常表示token 已过期
        print(e)

Celery 分布式消息传输的异步任务队列

定义

  • Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统

    它是一个专注于实时处理的任务队列,同时也支持任务调度

  • 中文官网:http://docs.jinkan.org/docs/celery/

安装:celery

  • pip3 install celery

使用场景

  • 任务调度
  • 避开阻塞操作

名词解释:

  • broker - 消息传输的中间件,生产者一旦有消息发送,将发至broker;【RQ,redis】

  • backend - 用于存储消息/任务结果,如果需要跟踪和查询任务状态,则需添加要配置相关

  • worker - 工作者 - 消费/执行broker中消息/任务的进程

使用celery

  • 项目文件夹下创建python_package(创建Python包) —> celery_tasks文件

  • celery_tasks文件下创建py文件 —> tasks.py

  • 示例

# 导入celery类
from celery import Celery
# 创建一个Celery类的实例对象
# 参数一:文件位置   参数二:Redis数据库ip,端口号:数据库号
app = Celery('celery_tasks.tasks.py',
             broker='redis://172.16.179.130:6379/8')

@app.task    # 使用装饰器
# 定义任务函数  (注册发邮件)  
def send_register_active_email(email,username,token)
	pass
# 使用功能函数    使用装饰器后.delay()启用中间人Redis,参数传进delay()
send_register_active_email.delay(email,username,token)
  • **流程:**任务发起者,中间者broker(redis),处理者wroker

    • 三者可以在同一台电脑上启动

    • 三者也可以不在同一台电脑上(必须在同一个网段里)

    • 不在同一台电脑需要复试项目文件到另一台电脑

    • 启动wroker处理者(处理者也需要任务代码)

    celery -A celery_tasks.py.tasks wroker -l info
    或
    

celery -A tasks worker --loglevel=info

上线时执行

nohup celery -A mywiki worker -l info > c.log 2>&1 &


- 设置初始化,在任务处理者一端加

```python 
# 导入celery类
from celery import Celery
# django环境初始化   ###########处理者添加代码
import OS,django
os.environ.setdefault("DJANGO_SETTINGS_MODULE","Online_retailers.settings")
djano.setup()

# 创建一个Celery类的实例对象
# 参数一:文件位置   参数二:Redis数据库ip,端口号:数据库号
app = Celery('celery_tasks.tasks.py',
             broker='redis://172.16.179.130:6379/8')

@app.task    # 使用装饰器
# 定义任务函数  (注册发邮件)  
def send_register_active_email(email,username,token)
  pass
# 使用功能函数    使用装饰器后.delay()启用中间人Redis,参数传进delay()
send_register_active_email.delay(email,username,token

富文本编辑器

tinymce

pip3 install django-tinymce==2.6.0   # 下载富文本编辑器
# 安装完成后,可以使用在Admin管理中,也可以自定义表单使用.
1. 在settings.py 中为INSTALLED_APPS注册编辑器应用
INSTALLED_APPS = (
	...
	'tinymce',
)

2. 在settings.py 中添加编辑器配置
TINYMCE_DEFAULT_CONFIG = {
'theme':'advanced',
'width':600,
'height':400,
}

3. 在urls.py中配置编辑器url
url(r'^tinymce/',include('tinymce.urls'))

文件系统FastDFS

安装fastdfs依赖包

  • 依赖包
    1. 解压缩 libfastcommon-master.zip
    2. 进入到 libfastcommon-,master的目录中
    3. 执行 ./make.sh
    4. 执行 sudo ./make.sh install

安装FastDFS

  • 安装
    1. 解压缩 fastdfs-master.zip
    2. 进入到 fastdfs-master 目录中
    3. 执行 ./make.sh
    4. 执行 sudo ./make.sh install

配置跟踪服务器 tracker

1. sudo cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf`
2. 在/home/pyhon/目录中创建目录 fastdfs/tracker  (mkdir -p /home/pyhon/fastdfs/tracker)
3. 编辑/etc/fdfs/tracker.conf配置文件   
	sudo vim /etc/fdfs/tracker.conf
4. 修改base_path = /home/python/fastdfs/tracker

配置存储服务器storage

1. sudo cp /etc/fdfs/storage/conf.sample /etc/fdfs/storage/conf
2. 在/home/pyhon/目录中创建目录 fastdfs/storage  (mkdir -p /home/pyhon/fastdfs/storage)
3. 编辑/etc/fdfs/storage.conf配置文件   
	sudo vim /etc/fdfs/storage.conf
4. 修改base_path = /home/python/fastdfs/storage
	store_path0=/home/python/fastdfs/storage
	tracker_server=172.16.179.131:1202  #填本机IP 及端口号

启动tracker和storage,nginx

sudo serveice fdfs_trackerd start
sudo serveice fdfs_storaged start
sudo /usr/local/nginx/sbin/nginx

测试是否安装成功

1. sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
2. 编辑/etc/fdfs/client.conf配置文件  sudo vim /etc/fdfs/client.conf
# 修改内容
base_path = /home/python/fastdfs/tracker
tracker_server=172.16.179.131:22122  #填本机IP 及端口号

上传文件测试

  1. 执行预编命令 fdfs_upload_file /etc/fdfs/client.conf ~要上传的文件路径
  2. 如果返回类型dfjajfk.jpg的文件id则说明文件上传成功

Nginx配合FastDFS使用安装和配置

  • 安装Nginx及fastdfs-nginx-module
  • 进入Nginx目录中
  • 执行预编命令
1. sudo ./configure --prefix=/usr/local/nginx/ --add-module=fastdfs-nginx-module-master 解压后的目录的绝对路径/src
2. sudo ./make
3. sudo ./make install
4. sudo cp fastdfs-nginx-module-master 解压后的目录中src下的mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf
5. sudo vim /etc/fdfs/mod_fastdfs.conf
# 修改内容
connect_timeout=10
tracker_server=本机ip地址加端口号22122
url_have_group_name=true
store_path0=/home/python/fastdfs/storage
6. sudo cp 解压的fastdfs-master目录中的http.conf /etc/fdfs/http.conf
7. sudo cp 解压的fastdfs-master目录中的mime.types /etc/fdfs/mime.types
8. sudo vim /usr/local/nginx/conf/nginx.conf
# 在http部分中添加配置信息如下:
server {
			listen   8888;
			server_name_localhost;
			location ~/group[0-9]/{
				ngx_fastdfs_module;
			}
			error_page  500 502 503 504 /50x.html;
			location = /50x.html{
			root  html;
			}
}
# 启动 Nginx
sudo /usr/local/nginx/sbin/nginx

使用Python客户端上传测试

1. workon django_py3
2.进入fdfs_client-py-master.zip所在目录
3.pip install fdfs_client-py-master.zip

# python和fastDFS交互
from fdfs_client.client import Fdfs.client
client = Fdfs.client('/etc/fdfs/client.conf')
req = client.upload_by_filename('test')

# req 返回值为字典
{'Group name':'group1','Status':'Upload successed',
 'Remote file_id':'group1/M00/00/dafdaffa.py',
 ....  # 省略
    
}

自定义文件存储类(Django二次开发)

  • 概念:
    • Django默认上传文件保存使用FileSystemStorage类
自定义类
  • FileSystemStorage是继承Storage类的子类
  • 如果需要提供自定义文件存储,一个普通的例子实在某个远程系统上存储文件,可以通过定义一个自定义的存储类来实现,需要以下几个步骤
  1. 自定义类必须是django.core.files.storage.Storage的子类:
from django.core.files.storage import Storage

class MyStorage(Storage):
    ...
  1. Django必须能够不带任何参数来实例化自定义的存储类,这意味着任何设置都应该从djang.conf.settings中获取
from django.conf import settings
from django.core.files.storage import Storage

class MyStorage(Storage):
    def __init__(self,option=None):
        if not option:
            option = settings.CUSTOM_STORAGE_OPTIONS
        ...
  1. 自定义的存储类必须实现 _open()和 _save() 方法,以及任何适合于自定义类的其他方法.
from django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client
# fast dfs 文件存储类
class FDFSStorage(Storage):
    # 打开文件时用
    def _open(self,name,mode='rb'):
    	pass
    # 保存文件时使用
    def _save(self,name,content):
        # name:你选择上传文件的名字
        # content:包含你上传文件内容的File对象
        
        # 创建一个Fdfs_client对象
        client = Fdfs.client('项目路径下的/client.conf')
        # 上传文件到fast dfs系统中
        res = client.upload_by_buffer(content.read())
        # res 返回值为字典  详情请见 -->使用Python客户端上传测试
		if res.get('Status') != 'Upload successed':
            #上传失败
            raise Exception("上传文件到fast dfs失败")
        # 获取返回的文件ID
        filename = res.get('Remote file_id')
        return filename
    def exists(self,name):
        # Django判断文件名是否可用
        return False
    def url(self,name):
        # 返回访问文件的url路径
  • 配置settings.py文件使用自定义类
# 设置Django文件存储类
DEFAULT_FILE_STORAGE="XXX.XXX.storage.自定义类名"
#  xxx项目文件夹下的文件路径,不包含项目文件夹名

项目部署

  • 项目部署是指在软件开发完毕后,将开发机器上运行的开发板软件实际安装到服务器上进行长期运行
  • 部署要分以下几个步骤进行
    • 在安装机器上安装和配置同版本的数据库
    • django 项目迁移(在安装机器上配置与开发环境相同的python版本及依懒的包)
    • 用 uwsgi 替代python3 manage.py runserver 方法启动服务器
    • 配置 nginx 反向代理服务器
    • 用nginx 配置静态文件路径,解决静态路径问题

安装相同版本的包

  • 导出当前模块数据包的信息:

  • $ pip3 freeze > package_list.txt

  • 导入到另一台新主机:

  • $ pip3 install -r package_list.txt

  • 将当前项目源代码复制到远程主机上(scp 命令)

    • $ sudo scp -a 当前项目源代码 远程主机地址和文件夹

    • sudo scp -a /home/tarena/django/mysite1.zip [email protected]:/home/root/xxx
      请输入root密码:
      

WSGI Django工作环境部署

  • WSGI (Web Server Gateway Interface)Web服务器网关接口,是Python应用程序或框架和Web服务器之间的一种接口,被广泛使用
  • 它实现了WSGI协议、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。WSGI是一种Web服务器网关接口。

uWSGI 网关接口配置 (ubuntu 18.04 配置)

  • 使用 python manage.py runserver 通常只在开发和测试环境中使用。

  • 当开发结束后,完善的项目代码需要在一个高效稳定的环境中运行,这时可以使用uWSGI

  • uWSGI是WSGI的一种,它可以让Django、Flask等开发的web站点运行其中.

  • 安装uWSGI

安装uwsgi

  • 在线安装 uwsgi

    • $ sudo pip3 install uwsgi
      
  • 离线安装

    1. 在线下载安装包:

      $ pip3 download uwsgi
      
      • 下载后的文件为 uwsgi-2.0.18.tar.gz
    2. 安装

      $ tar -xzvf uwsgi-2.0.18.tar.gz
      $ cd uwsgi-2.0.18
      $ sudo python3 setup.py install
      

配置uwsgi

  • 添加配置文件 项目文件夹/uwsgi.ini

  • 示例:

    [uwsgi]
    # 套接字方式的 IP地址:端口号
    # socket=127.0.0.1:8000
    
    
    # Http通信方式的 IP地址:端口号
    http=127.0.0.1:8000
    # 项目当前工作目录
    chdir=/home/tarena/.../my_project 这里需要换为项目文件夹的绝对路径
    # 项目中wsgi.py文件的目录,相对于当前工作目录
    wsgi-file=my_project/wsgi.py
    # 进程个数
    process=4
    # 每个进程的线程个数
    threads=2
    # 服务的pid记录文件
    pidfile=uwsgi.pid
    # 服务的目志文件位置
    daemonize=uwsgi.log
    
    • 修改settings.py将 DEBUG=True 改为DEBUG=False

    • 修改settings.py 将 ALLOWED_HOSTS = [] 改为 ALLOWED_HOSTS = [’*’]

uWSGI的运行管理

启动uwsgi
$ cd 项目文件夹
$ sudo uwsgi --ini 项目文件夹/uwsgi.ini
停止uwsgi
$ cd 项目文件夹
$ sudo uwsgi --stop uwsgi.pid
  • **说明:**当uwsgi 启动后,当前django项目的程序已变成后台守护进程,在关闭当前终端时此进程也不会停止

nginx 反向代理配置

概念

  • Nginx是轻量级的高性能Web服务器,提供了诸如HTTP代理和反向代理、负载均衡、缓存等一系列重要特性,在实践之中使用广泛。

  • C语言编写,执行效率高

  • nginx 作用

    • 负载均衡, 多台服务器轮流处理请求
    • 反向代理
  • 原理:

  • 客户端请求nginx,再由nginx 请求 uwsgi, 运行django下的python代码

安装

$ sudo apt install nginx

nginx 配置

修改nginx 的配置文件
  • /etc/nginx/sites-available/default
# 在server节点下添加新的location项,指向uwsgi的ip与端口。
server {
    ...
    location / {
        uwsgi_pass 127.0.0.1:8000;  # 重定向到127.0.0.1的8000端口
        include /etc/nginx/uwsgi_params; # 将所有的参数转到uwsgi下
    }
    ...
}

nginx服务控制

$ sudo /etc/init.d/nginx start|stop|restart|status
# 或
$ sudo service nginx start|stop|restart|status

通过 start,stop,restart,status 可能实现nginx服务的启动、停止、重启、查扑克状态等操作 修改uWSGI配置

修改uWSGI配置

  • 修改项目文件夹/uwsgi.ini下的Http通信方式改为socket通信方式,如:
[uwsgi]
# 去掉如下
# http=127.0.0.1:8000
# 改为
socket=127.0.0.1:8000
  • 重启uWSGI服务
$ sudo uwsgi --stop uwsgi.pid
$ sudo uwsgi --ini 项目文件夹/uwsgi.ini

nginx 配置静态文件路径

  • 解决静态路径问题
# file : /etc/nginx/sites-available/default
# 新添加location /static 路由配置,重定向到指定的绝对路径
server {
    ...
    location /static {
        # root static文件夹所在的绝对路径,如:
        root /home/tarena/my_django_project; # 重定向/static请求的路径,这里改为你项目的文件夹
    }
    ...
}
  • 修改配置文件后需要重新启动 nginx 服务

部署文档 【腾讯云】ubuntu 18.04


1,安装pip3 sudo apt-get install python3-pip

2,pip3 install --upgrade pip 更新pip3版本

3,更新pip3至最新版本后 - 需要修改 pip3 文件,流程如下

​ 3.1 sudo vi /usr/bin/pip3

​ 3.2 修改代码至如下

from pip import __main__ if __name__ == '__main__': sys.exit(__main__._main())

4,安装django sudo pip3 install Django==1.11.8

5, 安装flask sudo pip3 install flask

6, 安装jwt sudo pip3 install pyjwt

7, 安装uwsgi sudo pip3 install uwsgi

8, 安装pymsql sudo pip3 install pymysql

9, 安装pillow sudo pip3 install pillow [注:django image字段用]

10,官网下载 django-cors-headers-3.0.2.tar ,安装源码包,方法如下

​ 10.1 解压 tar -zxvf django-cors-headers-3.0.2.tar

​ 10.2 cd 至 解压后的目录 并执行 sudo python3 setup.py install

11,安装nginx sudo apt-get install nginx

​ 11.1 flask client 的nginx 配置;进入 /etc/nginx/conf.d/目录;

​ 切换超级用户 sudo su; touch flask.conf 创建flask client配置

​ 配置细节如下:

server {

    listen 80; 

    server_name 192.144.179.50;
    charset utf-8;

    client_max_body_size 75M;

    location / {

        include uwsgi_params;                       # 导入uwsgi配置-
        uwsgi_pass 127.0.0.1:5555;                  # 转发端口,需要和uwsgi配置当中的监听端口一致
        uwsgi_param UWSGI_PYTHON /usr/bin/python3;  # Python解释器所在的路径,如果有虚拟环境可将路径设置为虚拟环境
        uwsgi_param UWSGI_CHDIR /home/ubuntu/tedu/dnblog/client;                 # 项目根目录
        uwsgi_param UWSGI_SCRIPT flask_client.py:app;                      # 项目的主程序,比如你测试用run.py文件,文件中app =   Flask(__name__),那么这里就填run.py:app

    }---
}
				

11, 博客项目 进入 client 目录下,执行 touch blog_client.ini 创建uwsgi配置文件

​ 11.1 配置详情如下

[uwsgi]
socket = 127.0.0.1:5555 #uwsgi监听的地址和端口
chdir = /home/ubuntu/tedu/dnblog/client   #项目目录-绝地路径
wsgi-file = flask_client.py				  #flask启动文件
callable = app 			                  #flask中初始化flask的变量
processes = 2
master = true							 #启动主进程管理子进程
vacuum = true							 #进程停止后 回收pid
daemonize = server.log 					 #守护进程的日志位置

​ 11.2 执行uwsgi --ini blog_client.ini 启动flask - client

​ 11.3 浏览器执行 公网地址/index 显示博客首页

12 django 启动

​ 12.1 进入项目目录 ,与settings平级目录的地方;执行 touch blog_django_uwsgi.ini 创建 uwsgi配置

​ 12.2 具体配置细节:

[uwsgi]                                       socket = 127.0.0.1:8080
chdir = /home/ubuntu/tedu/dnblog    #项目目录
wsgi-file = dnblog/wsgi.py			#相对chdir的wsgi.py所在位置路径
processes = 2
pidfile=duwsgi.pid
daemonize=duwsgi.log
vacuum=True
master=True

​ 12.3 nginx添加配置:

​ 进入 /etc/nginx/conf.d/目录;

​ 切换超级用户 sudo su; touch django.conf 创建配置

​ 配置细节如下:

server {                                                                                                                         listen 8000;
    server_name 127.0.0.1;
    charset utf-8;
    client_max_body_size 75M;

    location / {-

        include uwsgi_params;                       # 导入uwsgi配置- 
        uwsgi_pass 127.0.0.1:8080;                  # 转发端口,需要和uwsgi配置当中的监听端口一致
    }

    location /media {
	#指定静态资源
	root /home/tarena/PycharmProjects/tedu_blog_all/dnblog;

    }   

}

其他: 1,代码打包至远程服务器,2, 配置数据库 3, 修改client中所有templates中html带有http协议的地址【127.0.0.1 更换 公网IP】

404 界面

  • 在模板文件夹内添加 404.html 模版,当视图触发Http404 异常时将会被显示

  • 404.html 仅在发布版中(即setting.py 中的 DEBUG=False时) 才起作用

  • 当向应处理函数触发Http404异常时就会跳转到404界面

    from django.http import Http404
    def xxx_view(request):
        raise Http404  # 直接返回404
    

前后端分离

1.1 什么是前后端分离

​ 前端: 即客户端,负责渲染用户显示界面【如web的js动态渲染页面, 安卓, IOS,pc客户端等】

​ 后端:即服务器端,负责接收http请求,处理数据

​ API:Application Programming Interface 是一些预先定义的函数,或指软件系统不同组成部分衔接的约定

​ 前后端分离 完整请求过程

​ 1,前端通过http请求后端API

​ 2,后端以json形式返回前端数据

​ 3,前端生成用户显示界面【如html , ios , android】

判断前后端分离得核心标准: 谁生成显示页面

​ 1,后端生成【前后端未分离】 ex: flask->render_template django -> HttpResponse(html)

​ 2, 前端生成【前后端分离】

1.2 优点

​ 1,各司其职

​ 前端:视觉层面,兼容性,前端性能优化

​ 后端:并发,可用性,性能

​ 2,解耦,前端和后端均易于扩展

​ 3,后端灵活搭配各类前端 - 如安卓等

​ 4,提高用户体验

​ 5,前端+后端可完全并行开发,加快开发效率

1.3 分离常见问题

问题 答案
如何解决http无状态? 采用token(详情见下方章节)
如果前端为JS,如何解决跨域问题? 采用CORS(详情见下方章节)
如何解决csrf问题 采用token
Single Page web Application 是否会影响Search Engine Optimization效果 会,前后端分离后,往往页面不存在静态文字【例如新闻的详细内容】
”老板,这个逻辑到底是让前端做还是后端做啊?“ 底线原则: 数据校验需要前后端都做
”老板,前端工作压力太大了啊“ 团队协作不能只是嘴上说说
动静分离和前后端分离是一个意思么? 动静分离指的是 css/js/img这类静态资源跟服务器拆开部署,典型方案-静态资源交由CDN厂商处理

1.4 实现方式

1,Django/Flask 后端只返回json

2, 前端 -> ex: js向服务器发出ajax请求,获取数据,拿到数据后动态生成html

3, 前端服务和后端服务 分开部署


token - 令牌

须知:

1,base64 '防君子不防小人' 
方法 作用 参数 返回值
b64encode 将输入的参数转化为base64规则的串 预加密的明文,类型为bytes;例:b‘guoxiaonao’ base64对应编码的密文,类型为bytes;例:b’Z3VveGlhb25hbw==’
b64decode 将base64串 解密回 明文 base64密文,类型为bytes;例:b’Z3VveGlhb25hbw==’ 参数对应的明文,类型为bytes;例:b’guoxiaonao’
urlsafe_b64encode 作用同b64encode,但是会将 ‘+‘替换成 ‘-’,将’/‘替换成’_’ 同b64encode 同b64encode
urlsafe_b64decode 作用同b64decode 同b64decode 同b64decode

代码演示:

import base64
#base64加密
s = b'guoxiaonao'
b_s = base64.b64encode(s)
#b_s打印结果为 b'Z3VveGlhb25hbw=='

#base64解密
ss = base64.b64decode(b_s)
#ss打印结果为 b'guoxiaonao'

2,SHA-256  安全散列算法的一种(hash)

hash三大特点:

1)定长输出    2)不可逆    3) 雪崩
import hashlib
s = hashlib.sha256() #创建sha256对象
s.update(b'xxxx')  #添加欲hash的内容,类型为 bytes
s.digest()  #获取最终结果


3,HMAC-SHA256 是一种通过特别计算方式之后产生的消息认证码,使用**散列算法**同时结合一个**加密密钥**。它可以用来保证数据的完整性,同时可以用来作某个消息的身份验证
import hmac
#生成hmac对象
#第一个参数为加密的key,bytes类型,
#第二个参数为欲加密的串,bytes类型
#第三个参数为hmac的算法,指定为SHA256
h = hmac.new(key, str, digestmod='SHA256 ') 
h.digest() #获取最终结果
4,RSA256 非对称加密

	1,加密: 公钥加密,私钥解密

	2,签名: 私钥签名, 公钥验签

2.1 JWT - json-web-token

1,三大组成

1,header

	格式为字典-元数据格式如下
{'alg':'HS256', 'typ':'JWT'}
#alg代表要使用的 算法
#typ表明该token的类别 - 此处必须为 大写的 JWT
	 该部分数据需要转成json串并用base64 加密



2,payload

	格式为字典-此部分分为公有声明和私有声明

  公共声明:JWT提供了内置关键字用于描述常见的问题

此部分均为可选项,用户根据自己需求 按需添加key,常见公共声明如下:

{'exp':xxx, # Expiration Time 此token的过期时间的时间戳
 'iss':xxx,# (Issuer) Claim 指明此token的签发者
 'aud':xxx, #(Audience) Claim 指明此token的
 'iat':xxx, # (Issued At) Claim 指明此创建时间的时间戳
 'aud':xxx, # (Audience) Claim	指明此token签发面向群体
}
	私有声明:用户可根据自己业务需求,添加自定义的key,例如如下:
{'username': 'guoxiaonao'}
	公共声明和私有声明均在同一个字典中;转成json串并用base64加密

3,signature 签名

	签名规则如下:

	根据header中的alg确定 具体算法,以下用 HS256为例

	HS256(自定义的key ,   base64后的header + '.' + base64后的payload)

    解释:用自定义的key, 对base64后的header + '.' + base64后的payload进行hmac计算

2,jwt结果格式

	base64(header) + '.' + base64(payload) + '.' +  base64(sign)

	最终结果如下: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1b3hpYW9uYW8iLCJpc3MiOiJnZ2cifQ.Zzg1u55DCBqPRGf9z3-NAn4kbA-MJN83SxyLFfc5mmM'

3,校验jwt规则

	1,解析header, 确认alg

	2,签名校验 - 根据传过来的header和payload按 alg指明的算法进行签名,将签名结果和传过来的sign进行对比,若对比一致,则校验通过

	3,获取payload自定义内容

4,pyjwt

1,安装 pip3 install pyjwt
方法 参数说明 返回值
encode(payload, key, algorithm) payload: jwt三大组成中的payload,需要组成字典,按需添加公有声明和私有声明
例如: {‘username’: ‘guoxiaonao’, ‘exp’: 1562475112}
参数类型: dict
token串
返回类型:bytes
key : 自定义的加密key
参数类型:str
algorithm: 需要使用的加密算法[HS256, RSA256等]
参数类型:str
decode(token,key,algorithm,) token: token串
参数类型: bytes/str
payload明文
返回类型:dict
key : 自定义的加密key ,需要跟encode中的key保持一致
参数类型:str
algorithm: 同encode
issuer: 发布者,若encode payload中添加 ‘iss’ 字段,则可针对该字段校验
参数类型:str
若iss校验失败,则抛出jwt.InvalidIssuerError
audience:签发的受众群体,若encode payload中添加’aud’字段,则可针对该字段校验
参数类型:str
若aud校验失败,则抛出jwt.InvalidAudienceError

PS: 若encode得时候 payload中添加了exp字段; 则exp字段得值需为 当前时间戳+此token得有效期时间, 例如希望token 300秒后过期 {‘exp’: time.time() + 300}; 在执行decode时,若检查到exp字段,且token过期,则抛出jwt.ExpiredSignatureError


CORS - Cross-origin resource sharing - 跨域资源共享

1,什么是CORS

	允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制

2,特点

	1,浏览器自动完成(在请求头中加入特殊头 或 发送特殊请求)

	2,服务器需要支持(响应头中需要有特殊头)

3,简单请求(Simple requests)和预检请求(Preflighted requests)

	**满足以下全部条件**的请求为 **简单请求**

		1,请求方法如下:

				GET  or HEAD or POST

		2,请求头仅包含如下:

				Accept

				Accept-Language

				Content-Language

				Content-Type

		3,Content-Type 仅支持如下三种:

				application/x-www-form-urlencoded

				multipart/form-data

				text/plain

		**不满足以上任意一点的请求都是 预检请求**

4,简单请求发送流程

	1,请求

			请求头中 携带 Origin,该字段表明自己来自哪个域

	2,响应

			如果请求头中的Origin在服务器接受范围内, 则返回如下头
响应头 作用 备注
Access-Control-Allow-Origin 服务器接受得域
Access-Control-Allow-Credentials 是否接受Cooike 可选
Access-Control-Expose-Headers 默认情况下,xhr只能拿到如下响应头:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified;如果有需要获取其他头,需在此指定 可选
	如果服务器不接受此域,则响应头中不包含 Access-Control-Allow-Origin

5,预检请求发送流程

1,OPTION 请求发起,携带如下请求头
请求头 作用 备注
Origin 表明此请求来自哪个域 必选
Access-Control-Request-Method 此次请求使用方法 必选
Access-Control-Request-Headers 此次请求使用的头 必选
2,OPTION 接受响应阶段,携带如下响应头
响应头 作用 备注
Access-Control-Allow-Origin 同简单请求 必选
Access-Control-Allow-Methods 告诉浏览器,服务器接受得跨域请求方法 必选
Access-Control-Allow-Headers 返回所有支持的头部,当request有
‘Access-Control-Request-Headers’时,该响应头必然回复
必选
Access-Control-Allow-Credentials 同简单请求 可选
Access-Control-Max-Age OPTION请求缓存时间,单位s 可选
3,主请求阶段 
请求头 作用 备注
Origin 表明此请求来自哪个域
4,主请求响应阶段
响应头 作用 备注
Access-Control-Allow-Origin 当前服务器接受得域

6,Django支持

django-cors-headers官网 https://pypi.org/project/django-cors-headers/

直接pip 将把django升级到2.0以上,强烈建议用离线安装方式

配置流程

		1,INSTALLED_APPS 中添加 corsheaders
		2,MIDDLEWARE 中添加 corsheaders.middleware.CorsMiddleware
		   位置尽量靠前,官方建议 ‘django.middleware.common.CommonMiddleware’ 上方
		3,CORS_ORIGIN_ALLOW_ALL  布尔值  如果为True 白名单不启用
		4,CORS_ORIGIN_WHITELIST =[
			"https://example.com"
		]
		5, CORS_ALLOW_METHODS = (
				'DELETE',
				'GET',
				'OPTIONS',
				'PATCH',
				'POST',
				'PUT',
				)
		6, CORS_ALLOW_HEADERS = (
				'accept-encoding',
				'authorization',
				'content-type',
				'dnt',
				'origin',
				'user-agent',
				'x-csrftoken',
				'x-requested-with',
			)
		7, CORS_PREFLIGHT_MAX_AGE  默认 86400s
		8, CORS_EXPOSE_HEADERS  []
		9, CORS_ALLOW_CREDENTIALS  布尔值, 默认False

RESTful -Representational State Transfer

4.1,什么是RESTful

1,资源 **(Resources)**

	**网络上的一个实体,或者说是网络上的一个具体信息**,并且每个资源都有一个独一无二得URI与之对应;获取资源-直接访问URI即可

2,**表现层(Representation)**

	如何去表现资源  - 即资源得表现形式;如HTML , xml  , JPG , json等

3,**状态转化(State Transfer)**

	访问一个URI即发生了一次 客户端和服务端得交互;此次交互将会涉及到数据和状态得变化

	客户端需要通过某些方式触发具体得变化  -  HTTP method 如 GET, POST,PUT,PATCH,DELETE 等

4.2 RESTful的特征

1,每一个URI代表一种资源

2,客户端和服务器端之前传递着资源的某种表现

3,客户端通过HTTP的几个动作 对 资源进行操作 - 发生‘状态转化’

4.3 如何设计符合RESTful 特征的API

1,协议  - http/https

2,域名:

	域名中体现出api字样,如

	https://api.example.com

	or

	https://example.org/api/

3,  版本:

	https://api.example.com/v1/

4,路径 -

	路径中避免使用动词,资源用名词表示,案例如下
https://api.example.com/v1/users
https://api.example.com/v1/animals
5,HTTP动词语义
  • GET(SELECT):从服务器取出资源(一项或多项)。

  • POST(CREATE):在服务器新建一个资源。

  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

  • DELETE(DELETE):从服务器删除资源。

    具体案例如下:

    GET /zoos:列出所有动物园
    POST /zoos:新建一个动物园
    GET /zoos/ID:获取某个指定动物园的信息
    PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
    PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
    DELETE /zoos/ID:删除某个动物园
    GET /zoos/ID/animals:列出某个指定动物园的所有动物
    DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
    

    6,巧用查询字符串

    ?limit=10:指定返回记录的数量
    ?offset=10:指定返回记录的开始位置。
    ?page=2&per_page=100:指定第几页,以及每页的记录数。
    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
    ?type_id=1:指定筛选条件
    

    7,状态码

      1,用HTTP响应码表达 此次请求结果,例如
    
    200 OK - [GET]:服务器成功返回用户请求的数据
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误
    
      2, 自定义内部code 进行响应
      
      如 返回结构如下  {'code':200,  'data': {}, 'error': xxx}
    

    8,返回结果

    根据HTTP 动作的不同,返回结果的结构也有所不同

    GET /users:返回资源对象的列表(数组)
    GET /users/guoxiaonao:返回单个资源对象
    POST /users:返回新生成的资源对象
    PUT /users/guoxiaonao:返回完整的资源对象
    PATCH /users/guoxiaonao:返回完整的资源对象
    DELETE /users/guoxiaonao:返回一个空文档
    

数据库


Mysql

  • 概念
    • 关系型数据库

    • 跨平台

    • 基于磁盘存储,数据是以文件形式存放在数据库目录/var/lib/mysql下

安装

  • Ubuntu安装MySQL服务
安装服务端: sudo apt-get install mysql-server
安装客户端: sudo apt-get install mysql-client
配置文件:/etc/mysql
命令集: /usr/bin
数据库存储目录 :/var/lib/mysql
  • Windows安装MySQL

下载MySQL安装包(windows) https://dev.mysql.com/downloads/mysql/
mysql-installer***5.7.***.msi
安装教程去安装

  • CMD进入解压文件夹bin
1 C:Windows\system32>mysqld --remove //删除mysql服务
2 C:Windows\system32>mysqld --install //安装mysql服务
3 C:Windows\system32>mysqld --initialize //初始化
4 C:Windows\system32>net start mysql   //启动数据库
5 C:Windows\system32>net stop mysql    //停止数据库
  • 编辑mysql配置文件my.ini(在mysql的安装目录下,我的在D:\Program Files\MySQL\MySQL Server 5.0\my.ini),在[mysqld]这个条目下加入 skip-grant-tables 保存退出后重启mysql
  • 设置密码
mysql> use mysql;  #选择数据库
mysql> update user set authentication_string=password("123456") where user="root";
mysql> FLUSH PRIVILEGES;   #刷新权限
mysql> quit;

  • 更改用户名
mysql>use mysql;  #选择数据库
mysql> update user set user="新用户名" where user="root";    #将用户名为root的改为新用户名
mysql> flush privileges;    #刷新权限
mysql> quit;

ALTER USER

修改当前登陆用户密码

ALTER USER USER() IDENTIFIED BY '123456';

设置密码过期

ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE;

设置密码永不过期

ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE NEVER;

设置密码按照默认过期的时间

ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE DEFAULT;

设置密码指定的过期时间

ALTER USER 'jeffrey'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE INTERVAL 90 DAY;

SET PASSWORD

mysql 默认加密方法

SET PASSWORD FOR 'jeffrey'@'localhost' = '123456'

mysql 中的 password 函数加密方法

update user set authentication_string = password('123456'), password_expired = 'N', password_last_changed = now() where user = 'root';

启动链接

  • 服务端启动
sudo /etc/init.d/mysql start|stop|restart|status
sudo service mysql start|stop|restart|status
  • 客户端链接
mysql -hIP地址 -u用户名 -p密码
本地连接可省略 -h 选项

基本SQL命令

库管理

    1、查看已有库;
   		show databases;
    2、创建库并指定字符集;
		create database 库名 charset utf8;
		create database 库名 character set utf8;
    3、查看当前所在库;
      	select database();
    4、切换库;
      	use 库名
    5、查看库中已有表;
      	show tables;
    6、删除库;
      	drop database 库名;

表管理

    1、创建表并指定字符集;
      create table 表名(字段名 字段类型 xxx)charset=utf8;
    2、查看创建表的语句 (字符集、存储引擎);	show create table 表名;
      
    3、查看表结构;
      desc 表名;     
      
    4、删除表;   
      drop table 表名1,表名2

表记录管理

    1、增 : insert into 表名(字段名) values(),()
    2、删 : delete from 表名 where 条件
    3、改 : update 表名 set 字段名=值 where 条件 
    4、查 : select 字段名 from 表名 where 条件

表字段管理

    1、增 : alter table 表名 add 字段名 字段类型 first|after 字段名 
    2、删 : alter table 表名 drop 字段名;
    3、改 : alter table 表名 modify 字段名 字段类型 
    4、表重命名:
    	alter table 表名 rename 新表名

Redis

安装

  • Ubuntu
# 安装
sudo apt-get install redis-server
# 服务端启动
sudo /etc/init.d/redis-server status | start | stop | restart
# 客户端连接
redis-cli -h IP地址 -p 6379 -a 密码
  • Windows
1、下载安装包
   https://github.com/ServiceStack/redis-windows/blob/master/downloads/redis-64.3.0.503.zip
2、解压
3、启动服务端
   双击解压后的 redis-server.exe 
4、客户端连接
   双击解压后的 redis-cli.exe

# Windows下产生的问题:关闭终端后服务终止
# 解决方案:将Redis服务安装到本地服务
1、重命名 redis.windows.conf 为 redis.conf,作为redis服务的配置文件
2、cmd命令行,进入到redis-server.exe所在目录
3、执行:redis-server --service-install redis.conf --loglevel verbose
4、计算机-管理-服务-Redis-启动

# 卸载
到 redis-server.exe 所在路径执行:
1、redis-server --service-uninstall
2、sc delete Redis

数据类型通用命令

通用命令==适用于所有数据类型

# 切换库(number的值在0-15之间,db0 ~ db15)
select number
# 查看键
keys 表达式  # keys *
# 数据类型
TYPE key
# 键是否存在
exists key
# 删除键
del key
# 键重命名
rename key newkey
# 清除当前库中所有数据(慎用)
flushdb
# 清除所有库中所有数据(慎用)
flushall

字符串常用命令

# 1. 设置一个key-value
set key value
# 2. 获取key的值
get key
# 3. key不存在时再进行设置(nx)
set key value nx  # not exists
# 4. 设置过期时间(ex)
set key value ex seconds

# 5. 同时设置多个key-value
mset key1 value1 key2 value2 key3 value3
# 6. 同时获取多个key-value
mget key1 key2 key3 

####################################  字符串常用命令==稍微了解
# 1.获取长度
strlen key
# 2.获取指定范围切片内容
getrange key start stop
# 3.从索引值开始,value替换原内容
setrange key index value
# 4.追加拼接value的值
append key value

数值操作字符串类型数字

# 整数操作
INCRBY key 步长
DECRBY key 步长
INCR key : +1操作
DECR key : -1操作
# 应用场景: 抖音上有人关注你了,是不是可以用INCR呢,如果取消关注了是不是可以用DECR
# 浮点数操作: 自动先转为数字类型,然后再进行相加减,不能使用append
incrbyfloat key step

键命名规范

127.0.0.1:6379> mset wang:email [email protected] guo:email [email protected]
OK
127.0.0.1:6379> mget wang:email guo:email
1) "[email protected]"
2) "[email protected]"
127.0.0.1:6379> 

string命令汇总

# 字符串操作
1、set key value
2、set key value nx
3、get key
3、mset key1 value1 key2 value2
4、mget key1 key2 key3
5、set key value nx ex seconds
6、strlen key 
# 返回旧值并设置新值(如果键不存在,就创建并赋值)
7、getset key value
# 数字操作
7、incrby key 步长
8、decrby key 步长
9、incr key
10、decr key
11、incrbyfloat key number#(可为正数或负数)
# 设置过期时间的两种方式
# 方式一
1、set key value ex 3
# 方式二
1、set key value
2、expire key 5 # 秒
3、pexpire key 5 # 毫秒
# 查看存活时间
ttl key
# 删除过期
persist key
  • string数据类型注意

    # key值取值原则
    1、key值不宜过长,消耗内存,且在数据中查找这类键值的计算成本高
    2、不宜过短,可读性较差
    # 值
    1、一个字符串类型的值最多能存储512M内容
    

列表数据类型通用命令

  • 特点

    1、元素是字符串类型
    2、列表头尾增删快,中间增删慢,增删元素是常态
    3、元素可重复
    4、最多可包含2^32 -1个元素
    5、索引同python列表
    

列表常用命令

# 增
1、从列表头部压入元素
	LPUSH key value1 value2 
2、从列表尾部压入元素
	RPUSH key value1 value2
3、从列表src尾部弹出1个元素,压入到列表dst的头部
	RPOPLPUSH src dst
4、在列表指定元素后/前插入元素
	LINSERT key after|before value newvalue

# 查
5、查看列表中元素
	LRANGE key start stop
  # 查看列表中所有元素: LRANGE key 0 -1
6、获取列表长度
	LLEN key

# 删
7、从列表头部弹出1个元素
	LPOP key
8、从列表尾部弹出1个元素
	RPOP key
9、列表头部,阻塞弹出,列表为空时阻塞
	BLPOP key timeout
10、列表尾部,阻塞弹出,列表为空时阻塞
	BRPOP key timeout
  # 关于BLPOP 和 BRPOP
  	1、如果弹出的列表不存在或者为空,就会阻塞
		2、超时时间设置为0,就是永久阻塞,直到有数据可以弹出
		3、如果多个客户端阻塞再同一个列表上,使用First In First Service原则,先到先服务
11、删除指定元素
	LREM key count value
  count>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count
	count<0:表示从尾部开始向表头搜索,移除与value相等的元素,数量为count
	count=0:移除表中所有与value相等的值
12、保留指定范围内的元素
	LTRIM key start stop
  LRTIM mylist1 0 2 # 只保留前3条
  # 应用场景: 保存微博评论最后500条
  LTRIM weibo:comments 0 499

# 改
13、LSET key index newvalue

配置文件详解

  • 配置文件所在路径
1、Ubuntu
	/etc/redis/redis.conf
  mysql的配置文件在哪里? : /etc/mysql/mysql.conf.d/mysqld.cnf

2、windows 下载解压后的redis文件夹中
	redis.windows.conf 
	redis.conf
  • 设置连接密码
1、requirepass 密码
2、重启服务
   sudo /etc/init.d/redis-server restart
3、客户端连接
   redis-cli -h 127.0.0.1 -p 6379 -a 123456
   127.0.0.1:6379>ping
  • 允许远程连接
1、注释掉本地IP地址绑定
  69: # bind 127.0.0.1 ::1
2、关闭保护模式(把yes改为no)
  88: protected-mode no
3、重启服务
  sudo /etc/init.d/redis-server restart
  • 远程连接测试
    • Windows连接Ubuntu的Redis服务
# cmd命令行
1、e:
2、cd Redis3.0
3、redis-cli -h x.x.x.x -a 123456
4、x.x.x.x:6379>ping

与Python交互

  • Python与redis交互注意

    1、r.set('name','Tom',ex=5,nx=True)
    2、r.mset({'user1:name':'Tom','user1:age':'25'})
    # 有元素时返回弹出元素,否则返回None
    3、r.brpop('mylist',3)
    
  • 模块(redis)

    • Ubuntu

      sudo pip3 install redis
      
    • Windows

      # 方法1. python -m pip install redis
      # 方法2. 以管理员身份打开cmd命令行
              pip install redis
      
  • 使用流程

import redis
# 创建数据库连接对象
r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='123456')
  • 通用命令代码示例
import redis

# 创建连接对象
r = redis.Redis(host='192.168.153.146',port=6379,db=0)

# r.keys('*') -> 列表
key_list = r.keys('*')
for key in key_list:
  print(key.decode())

# b'list'
print(r.type('mylist'))
# 返回值: 0 或者 1
print(r.exists('spider:urls'))
# 删除key
r.delete('mylist2')
  • python操作list
import redis

r = redis.Redis(host='192.168.153.146',port=6379,db=0)

# pylist: ['pythonweb','socket','pybase']
r.lpush('pylist','pybase','socket','pythonweb')
# pylist: ['spider','pythonweb','socket','pybase']
r.linsert('pylist','before','pythonweb','spider')
# 4
print(r.llen('pylist'))
# [b'spider', b'pythonweb', b'socket', b'pybase']
print(r.lrange('pylist',0,-1))
# b'pybase'
print(r.rpop('pylist'))
# [b'spider', b'pythonweb']
r.ltrim('pylist',0,1)

while True:
  # 如果列表中为空时,则返回None
  result = r.brpop('pylist',1)
  if result:
    print(result)
  else:
    break


r.delete('pylist')
  • list案例: 一个进程负责生产url,一个进程负责消费url
import redis
import time
import random
from multiprocessing import Process

class Spider(object):
  def __init__(self):
  	r = redis.Redis(host='192.168.153.146',port=6379,db=0)

  def product(self):
    # 生产者开始生产url地址
    for page in range(0,67):
      url = 'http://app.mi.com/category/2#page=%s' % str(page)
      self.r.lpush('spider:urls',url)
      time.sleep(random.randint(1,3))

  def consumer(self):
    while True:
      # url: (b'spider:urls',b'http://xiaomixxx')
      url = self.r.brpop('spider:urls', 5)
      if url:
        print('正在抓取:', url[1].decode())
      else:
        print('抓取结束')
        break

  def run(self):
    p1 = Process(target=self.product)
    p2 = Process(target=self.consumer)
    p1.start()
    p2.start()
    p1.join()
    p2.join()

if __name__ == '__main__':
  spider = Spider()
  spider.run()
  • python操作string
import redis

r = redis.Redis(host='192.168.153.146',port=6379,db=0)

r.set('username','guods')
print(r.get('username'))
# mset参数为字典
r.mset({'username':'xiaoze','password':'123456'})
# 列表: [b'xiaoze', b'123456']
print(r.mget('username','password'))
# 6
print(r.strlen('username'))

# 数值操作
r.set('age','25')
r.incrby('age',10)
r.decrby('age',10)
r.incr('age')
r.decr('age')
r.incrbyfloat('age',3.3)
r.incrbyfloat('age',-3.3)
print(r.get('age'))

r.delete('username')

位图操作bitmap

  • 定义

    1、位图不是真正的数据类型,它是定义在字符串类型中
    2、一个字符串类型的值最多能存储512M字节的内容,位上限:2^32
    # 1MB = 1024KB
    # 1KB = 1024Byte(字节)
    # 1Byte = 8bit(位)
    

设置某一位上的值(setbit)

# 设置某一位上的值(offset是偏移量,从0开始)
setbit key offset value
# 获取某一位上的值
GETBIT key offset
# 统计键所对应的值中有多少个 1 
BITCOUNT key

示例

# 默认扩展位以0填充
127.0.0.1:6379> set mykey ab
OK
127.0.0.1:6379> get mykey
"ab"
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> get mykey
"\xe1b"
127.0.0.1:6379> 

获取某一位上的值

  • GETBIT key offset
127.0.0.1:6379> GETBIT mykey 3
(integer) 0
127.0.0.1:6379> GETBIT mykey 0
(integer) 1
127.0.0.1:6379> 
  • bitcount
    • 统计键所对应的值中有多少个 1
127.0.0.1:6379> SETBIT user001 1 1
(integer) 0
127.0.0.1:6379> SETBIT user001 30 1
(integer) 0
127.0.0.1:6379> bitcount user001
(integer) 2
127.0.0.1:6379> 
  • 应用场景案例
# 网站用户的上线次数统计(寻找活跃用户)
	用户名为key,上线的天作为offset,上线设置为1
# 示例
	用户名为 user1:login 的用户,今年第1天上线,第30天上线
	SETBIT user1:login 0 1 
	SETBIT user1:login 29 1
	BITCOUNT user1:login
  • 代码实现
import redis

r = redis.Redis(host='127.0.0.1',port=6379,db=0)

# user001: 一年中第5天和200天登录
r.setbit('user:001',4,1)
r.setbit('user:001',199,1)
# user002: 一年中第100天和第300天登录
r.setbit('user:002',99,1)
r.setbit('user:002',299,1)
# user:003: 登录了100次以上
for i in range(1,366,2):
  r.setbit('user:003',i,1)
# user:004: 登录了100次以上
for i in range(1,366,3):
  r.setbit('user:004',i,1)

user_list = r.keys('user:*')

# 存放活跃用户列表
active_users = []
# 存放不活跃用户列表
no_active_users = []

for user in user_list:
  login_count = r.bitcount(user)
  if login_count >= 100:
    active_users.append((user,login_count))
  else:
    no_active_users.append((user,login_count))

print('活跃用户:',active_users)
print('不活跃用户:',no_active_users)

Hash散列数据类型

  • 定义
1、由field和关联的value组成的键值对
2、field和value是字符串类型
3、一个hash中最多包含2^32-1个键值对
  • 优点
1、节约内存空间
2、每创建一个键,它都会为这个键储存一些附加的管理信息(比如这个键的类型,这个键最后一次被访问的时间等)
3、键越多,redis数据库在储存附件管理信息方面耗费内存越多,花在管理数据库键上的CPU也会越多
  • 缺点
1、使用二进制位操作命令:SETBIT、GETBIT、BITCOUNT等,如果想使用这些操作,只能用字符串键
2、使用过期键功能:键过期功能只能对键进行过期操作,而不能对散列的字段进行过期操作

基本操作命令

# 1、设置单个字段
HSET key field value
HSETNX key field value
# 2、设置多个字段
HMSET key field value field value
# 3、返回字段个数
HLEN key
# 4、判断字段是否存在(不存在返回0)
HEXISTS key field
# 5、返回字段值
HGET key field
# 6、返回多个字段值
HMGET key field filed
# 7、返回所有的键值对
HGETALL key
# 8、返回所有字段名
HKEYS key
# 9、返回所有值
HVALS key
# 10、删除指定字段
HDEL key field 
# 11、在字段对应值上进行整数增量运算
HINCRBY key filed increment
# 12、在字段对应值上进行浮点数增量运算
HINCRBYFLOAT key field increment

Hash与python交互

# 1、更新一条数据的属性,没有则新建
hset(name, key, value) 
# 2、读取这条数据的指定属性, 返回字符串类型
hget(name, key)
# 3、批量更新数据(没有则新建)属性,参数为字典
hmset(name, mapping)
# 4、批量读取数据(没有则新建)属性
hmget(name, keys)
# 5、获取这条数据的所有属性和对应的值,返回字典类型
hgetall(name)
# 6、获取这条数据的所有属性名,返回列表类型
hkeys(name)
# 7、删除这条数据的指定属性
hdel(name, *keys)

Python代码hash散列

'''设置1个字段,更改1个字段,设置多个字段,获取相关信息'''
import redis

r = redis.Redis(host='127.0.0.1',port=6379,db=0)
# 设置
r.hset('user1','name','bujingyun')
# 更新
r.hset('user1','name','kongci')
# 取数据
print(r.hget('user1','name'))
# 一次设置多个field和value
user_dict = {
  'password':'123456',
  'gender':'F',
  'height':'165'
}
r.hmset('user1',user_dict)
# 获取所有数据,字典
print(r.hgetall('user1'))

# 获取所有fields和所有values
print(r.hkeys('user1'))
print(r.hvals('user1'))

应用场景:微博好友关注

1、用户ID为key,Field为好友ID,Value为关注时间
       key       field    value
	 user:10000   user:606 20190520
	              user:605 20190521
2、用户维度统计
   统计数包括:关注数、粉丝数、喜欢商品数、发帖数
   用户为key,不同维度为field,value为统计数
   比如关注了5人
	 HSET user:10000 fans 5
	 HINCRBY user:10000 fans 1

应用场景: redis+mysql+hash组合使用

原理
用户想要查询个人信息
1、到redis缓存中查询个人信息
2、redis中查询不到,到mysql查询,并缓存到redis
3、再次查询个人信息
代码实现
import redis
import pymysql

# 1. 先到redis中查询
# 2. redis中没有,到mysql查询,缓存到redis(设置过期时间)
# 3. 再查询1次
r = redis.Redis(host='192.168.153.148',port=6379,db=0)
username = input('请输入用户名:')

result = r.hgetall(username)
if result:
  print(result)
else:
  # redis中没有缓存,需要到mysql中查询
  db = pymysql.connect(
    host='192.168.153.148',
    user='tiger',
    password='123456',
    database='userdb',
    charset='utf8'
  )
  cursor = db.cursor()
  sele = 'select age,gender from user where username=%s'
  cursor.execute(sele,[username])
  # userinfo: (('guoxiaonao',36,'M'),)
  userinfo = cursor.fetchall()
  if not userinfo:
    print('用户不存在')
  else:
    # 打印输出
    print('mysql',userinfo)
    # 缓存到redis
    user_dict = {
      'age':userinfo[0][0],
      'gender':userinfo[0][1]
    }
    r.hmset(username,user_dict)
    # 设置过期时间
    r.expire(username,30)
mysql数据库中数据更新信息后同步到redis缓存
  • 用户修改个人信息时,要将数据同步到redis缓存
import redis
import pymysql

class Update(object):
    def __init__(self):
        self.db = pymysql.connect('127.0.0.1', 'root', '123456','userdb', charset='utf8')
        self.cursor = self.db.cursor()
        self.r = redis.Redis(host='127.0.0.1', port=6379, db=0)

    # 更新mysql表记录
    def update_mysql(self,score,username):
        upd = 'update user set score=%s where name=%s'
        try:
            self.cursor.execute(upd,[score,username])
            self.db.commit()
            return True
        except Exception as e:
            self.db.rollback()
            print('Failed',e)

    # 同步到redis数据库
    def update_redis(self,username,score):
        result = self.r.hgetall(username)
        # 存在,更新score字段的值
        # 不存在,缓存整个用户信息
        if result:
            self.r.hset(username,'score',score)
        else:
            # 到mysql中查询最新数据,缓存到redis中
            self.select_mysql(username)

    #
    def select_mysql(self,username):
        sel = 'select age,gender,score from user where name=%s'
        self.cursor.execute(sel,[username])
        result = self.cursor.fetchall()
        # 缓存到redis数据库
        user_dict = {
            'age' : result[0][0],
            'gender' : result[0][1],
            'score' : result[0][2]
        }
        self.r.hmset(username,user_dict)
        self.r.expire(username,60)

    def main(self):
        username = input('请输入用户名:')
        new_score = input('请输入新成绩:')
        if self.update_mysql(new_score,username):
            self.update_redis(username,new_score)
        else:
            print('更改信息失败')


if __name__ == '__main__':
    syn = Update()
    syn.main()

集合数据类型

基本命令

# 1、增加一个或者多个元素,自动去重
SADD key member1 member2
# 2、查看集合中所有元素
SMEMBERS key
# 3、删除一个或者多个元素,元素不存在自动忽略
SREM key member1 member2
# 4、元素是否存在
SISMEMBER key member
# 5、随机返回集合中指定个数的元素,默认为1个
SRANDMEMBER key [count]
# 6、弹出成员
SPOP key [count]
# 7、返回集合中元素的个数,不会遍历整个集合,只是存储在键当中了
SCARD key
# 8、把元素从源集合移动到目标集合
SMOVE source destination member

# 9、差集(number1 1 2 3 number2 1 2 4 结果为3)
SDIFF key1 key2 
# 10、差集保存到另一个集合中
SDIFFSTORE destination key1 key2

# 11、交集
SINTER key1 key2
SINTERSTORE destination key1 key2

# 11、并集
SUNION key1 key2
SUNIONSTORE destination key1 key2

案例: 新浪微博的共同关注

# 需求: 当用户访问另一个用户的时候,会显示出两个用户共同关注过哪些相同的用户
# 设计: 将每个用户关注的用户放在集合中,求交集即可
# 实现:
	user001 = {'peiqi','qiaozhi','danni'}
	user002 = {'peiqi','qiaozhi','lingyang'}
  
user001和user002的共同关注为:
	SINTER user001 user002
	结果为: {'peiqi','qiaozhi'}

python操作set

# 1、给name对应的集合中添加元素
sadd(name,values)
r.sadd("set_name","tom")
r.sadd("set_name","tom","jim")

# 2、获取name对应的集合的所有成员: python集合
smembers(name)
r.smembers('set_name')

# 3、获取name对应的集合中的元素个数
scard(name)
r.scard("set_name")

# 4、检查value是否是name对应的集合内的元素:True|False
sismember(name, value)
r.sismember('set_name','tom')

# 5、随机删除并返回指定集合的一个元素
spop(name)
member = r.spop('set_name')

# 6、删除集合中的某个元素
srem(name, value) 
r.srem("set_name", "tom")

# 7、获取多个name对应集合的交集
sinter(keys, *args)

r.sadd("set_name","a","b")
r.sadd("set_name1","b","c")
r.sadd("set_name2","b","c","d")

print(r.sinter("set_name","set_name1","set_name2"))
#输出:{b'b'}

# 8、获取多个name对应的集合的并集: python集合
sunion(keys, *args)
r.sunion("set_name","set_name1","set_name2")

python代码实现微博关注

import redis

r = redis.Redis(host='127.0.0.1',port=6379,db=0)

# user1关注的人
r.sadd('user1:focus','peiqi','qiaozhi','danni')
# user2关注的人
r.sadd('user2:focus','peiqi','qiaozhi','lingyang')
# 共同关注: 求交集 {b'qiaozhi', b'peiqi'}
focus_set = r.sinter('user1:focus','user2:focus')

# 创建空集合,存放最终结果
result = set()

for focus in focus_set:
  result.add(focus.decode())

print(result)

有序集合sortedset

有序集合常用命令

# 在有序集合中添加一个成员
zadd key score member
# 查看指定区间元素(升序)
zrange key start stop [withscores]
# 查看指定区间元素(降序)
ZREVRANGE key start stop [withscores]
# 查看指定元素的分值
ZSCORE key member

# 返回指定区间元素
# offset : 跳过多少个元素
# count : 返回几个
# 小括号 : 开区间  zrangebyscore fruits (2.0 8.0
zrangebyscore key min max [withscores] [limit offset count]
# 每页显示10个成员,显示第5页的成员信息: 
# limit 40 10
# MySQL: 每页显示10条记录,显示第5页的记录
# limit 40,10
# limit 2,3   显示: 第3 4 5条记录

# 删除成员
zrem key member
# 增加或者减少分值
zincrby key increment member
# 返回元素排名
zrank key member
# 返回元素逆序排名
zrevrank key member
# 删除指定区间内的元素
zremrangebyscore key min max
# 返回集合中元素个数
zcard key
# 返回指定范围中元素的个数
zcount key min max
zcount salary 6000 8000 
zcount salary (6000 8000# 6000
zcount salary (6000 (8000#6000
# 并集
zunionstore destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]
# zunionstore salary3 2 salary salary2 weights 1 0.5 AGGREGATE MAX
# 2代表集合数量,weights之后 权重1给salary,权重0.5给salary2集合,算完权重之后执行聚合AGGREGATE
                     
# 交集:和并集类似,只取相同的元素
ZINTERSTORE destination numkeys key1 key2 WEIGHTS weight AGGREGATE SUM(默认)|MIN|MAX

python操作sorted set

import redis

r = redis.Redis(host='127.0.0.1',port=6379,db=0)
# 注意第二个参数为字典
# 命令行:ZADD salary 6000 tom 8000 jim
r.zadd('salary',{'tom':6000,'jim':8000,'jack':12000})
# 结果为列表中存放元组[(),(),()]
print(r.zrange('salary',0,-1,withscores=True))
print(r.zrevrange('salary',0,-1,withscores=True))
# start:起始值,num:显示条数
print(r.zrangebyscore('salary',6000,12000,start=1,num=2,withscores=True))
# 删除
r.zrem('salary','tom')
print(r.zrange('salary',0,-1,withscores=True))
# 增加分值
r.zincrby('salary',5000,'jack')
print(r.zrange('salary',0,-1,withscores=True))
# 返回元素排名
print(r.zrank('salary','jack'))
print(r.zrevrank('salary','jack'))
# 删除指定区间内的元素
r.zremrangebyscore('salary',6000,8000)
print(r.zrange('salary',0,-1,withscores=True))
# 统计元素个数
print(r.zcard('salary'))
# 返回指定范围内元素个数
print(r.zcount('salary',6000,20000))
# 并集
r.zadd('salary2',{'jack':17000,'lucy':8000})
r.zunionstore('salary3',('salary','salary2'),aggregate='max')
print(r.zrange('salary3',0,-1,withscores=True))
# 交集
r.zinterstore('salary4',('salary','salary2'),aggregate='max')
print(r.zrange('salary4',0,-1,withscores=True))

数据持久化

  • 持久化定义

    • 将数据从掉电易失的内存放到永久存储的设备上
  • 为什么需要持久化

    • 因为所有的数据都在内存上,所以必须得持久化

数据持久化分类之 - RDB模式(默认开启)

默认模式
1、保存真实的数据
2、将服务器包含的所有数据库数据以二进制文件的形式保存到硬盘里面
3、默认文件名 :/var/lib/redis/dump.rdb
创建rdb文件的两种方式
方式一:服务器执行客户端发送的SAVE或者BGSAVE命令
127.0.0.1:6379> SAVE
OK
# 特点
1、执行SAVE命令过程中,redis服务器将被阻塞,无法处理客户端发送的命令请求,在SAVE命令执行完毕后,服务器才会重新开始处理客户端发送的命令请求
2、如果RDB文件已经存在,那么服务器将自动使用新的RDB文件代替旧的RDB文件
# 工作中定时持久化保存一个文件

127.0.0.1:6379> BGSAVE
Background saving started
# 执行过程如下
1、客户端 发送 BGSAVE 给服务器
2、服务器马上返回 Background saving started 给客户端
3、服务器 fork() 子进程做这件事情
4、服务器继续提供服务
5、子进程创建完RDB文件后再告知Redis服务器

# 配置文件相关操作
/etc/redis/redis.conf
263行: dir /var/lib/redis # 表示rdb文件存放路径
253行: dbfilename dump.rdb  # 文件名

# 两个命令比较
SAVE比BGSAVE快,因为需要创建子进程,消耗额外的内存

# 补充:可以通过查看日志文件来查看redis都做了哪些操作
# 日志文件:配置文件中搜索 logfile
logfile /var/log/redis/redis-server.log
方式二:设置配置文件条件满足时自动保存**(使用最多)**
# 命令行示例
redis>save 300 10
  表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么自动执行BGSAVE命令
redis>save 60 10000
  表示如果距离上一次创建rdb文件已经过去60秒,并且服务器所有数据库总共已经发生了不少于10000次修改,那么执行bgsave命令

# redis配置文件默认
218行: save 900 1
219行: save 300 10
220行: save 60 10000
  1、只要三个条件中的任意一个被满足时,服务器就会自动执行BGSAVE
  2、每次创建RDB文件之后,服务器为实现自动持久化而设置的时间计数器和次数计数器就会被清零,并重新开始计数,所以多个保存条件的效果不会叠加
数据持久化分类之 - AOF(AppendOnlyFile,默认未开启)
特点
1、存储的是命令,而不是真实数据
2、默认不开启
# 开启方式(修改配置文件)
1、/etc/redis/redis.conf
  672行: appendonly yes # 把 no 改为 yes
  676行: appendfilename "appendonly.aof"
2、重启服务
  sudo /etc/init.d/redis-server restart
RDB缺点
1、创建RDB文件需要将服务器所有的数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,所以服务器需要隔一段时间才创建一个新的RDB文件,也就是说,创建RDB文件不能执行的过于频繁,否则会严重影响服务器的性能
2、可能丢失数据
AOF持久化原理及优点
# 原理
   1、每当有修改数据库的命令被执行时,服务器就会将执行的命令写入到AOF文件的末尾
   2、因为AOF文件里面存储了服务器执行过的所有数据库修改的命令,所以给定一个AOF文件,服务器只要重新执行一遍AOF文件里面包含的所有命令,就可以达到还原数据库的目的

# 优点
  用户可以根据自己的需要对AOF持久化进行调整,让Redis在遭遇意外停机时不丢失任何数据,或者只丢失一秒钟的数据,这比RDB持久化丢失的数据要少的多
安全性问题考虑
# 因为
  虽然服务器执行一个修改数据库的命令,就会把执行的命令写入到AOF文件,但这并不意味着AOF文件持久化不会丢失任何数据,在目前常见的操作系统中,执行系统调用write函数,将一些内容写入到某个文件里面时,为了提高效率,系统通常不会直接将内容写入硬盘里面,而是将内容放入一个内存缓存区(buffer)里面,等到缓冲区被填满时才将存储在缓冲区里面的内容真正写入到硬盘里

# 所以
  1、AOF持久化:当一条命令真正的被写入到硬盘里面时,这条命令才不会因为停机而意外丢失
  2、AOF持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间
  3、越早将命令写入到硬盘,发生意外停机时丢失的数据就越少,反之亦然
策略 - 配置文件
# 打开配置文件:/etc/redis/redis.conf,找到相关策略如下
1701: alwarys
   服务器每写入一条命令,就将缓冲区里面的命令写入到硬盘里面,服务器就算意外停机,也不会丢失任何已经成功执行的命令数据
2702: everysec(# 默认)
   服务器每一秒将缓冲区里面的命令写入到硬盘里面,这种模式下,服务器即使遭遇意外停机,最多只丢失1秒的数据
3703: no
   服务器不主动将命令写入硬盘,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失命令数量不确定

# 运行速度比较
always:速度慢
everysec和no都很快,默认值为everysec
AOF文件中是否会产生很多的冗余命令?
为了让AOF文件的大小控制在合理范围,避免胡乱增长,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件
  -- 新的AOF文件记录的数据库数据和原由的AOF文件记录的数据库数据完全一样
  -- 新的AOF文件会使用尽可能少的命令来记录数据库数据,因此新的AOF文件的提及通常会小很多
  -- AOF重写期间,服务器不会被阻塞,可以正常处理客户端发送的命令请求
示例
原有AOF文件 重写后的AOF文件
select 0 SELECT 0
sadd myset peiqi SADD myset peiqi qiaozhi danni lingyang
sadd myset qiaozhi SET msg ‘hello tarena’
sadd myset danni RPUSH mylist 2 3 5
sadd myset lingyang
INCR number
INCR number
DEL number
SET message ‘hello world’
SET message ‘hello tarena’
RPUSH mylist 1 2 3
RPUSH mylist 5
LPOP mylist
AOF文件重写方法触发
1、客户端向服务器发送BGREWRITEAOF命令
   127.0.0.1:6379> BGREWRITEAOF
   Background append only file rewriting started

2、修改配置文件让服务器自动执行BGREWRITEAOF命令
  auto-aof-rewrite-percentage 100
  auto-aof-rewrite-min-size 64mb
  # 解释
    1、只有当AOF文件的增量大于100%时才进行重写,也就是大一倍的时候才触发
        # 第一次重写新增:64M
        # 第二次重写新增:128M
        # 第三次重写新增:256M(新增128M)
RDB和AOF持久化对比
RDB持久化 AOF持久化
全量备份,一次保存整个数据库 增量备份,一次保存一个修改数据库的命令
保存的间隔较长 保存的间隔默认为一秒钟
数据还原速度快 数据还原速度一般,冗余命令多,还原速度慢
执行SAVE命令时会阻塞服务器,但手动或者自动触发的BGSAVE不会阻塞服务器 无论是平时还是进行AOF重写时,都不会阻塞服务器
# 用redis用来存储真正数据,每一条都不能丢失,都要用always,有的做缓存,有的保存真数据,我可以开多个redis服务,不同业务使用不同的持久化,新浪每个服务器上有4个redis服务,整个业务中有上千个redis服务,分不同的业务,每个持久化的级别都是不一样的。
数据恢复(无需手动操作)
既有dump.rdb,又有appendonly.aof,恢复时找谁?
先找appendonly.aof
配置文件常用配置总结
# 设置密码
1、requirepass password
# 开启远程连接
2、bind 127.0.0.1 ::1 注释掉
3、protected-mode no  把默认的 yes 改为 no
# rdb持久化-默认配置
4、dbfilename 'dump.rdb'
5、dir /var/lib/redis
# rdb持久化-自动触发(条件)
6、save 900 1
7、save 300 10 
8、save 60  10000
# aof持久化开启
9、appendonly yes
10、appendfilename 'appendonly.aof'
# aof持久化策略
11、appendfsync always
12、appendfsync everysec # 默认
13、appendfsync no
# aof重写触发
14、auto-aof-rewrite-percentage 100
15、auto-aof-rewrite-min-size 64mb
# 设置为从服务器
16、salveof <master-ip> <master-port>
Redis相关文件存放路径
1、配置文件: /etc/redis/redis.conf
2、备份文件: /var/lib/redis/*.rdb|*.aof
3、日志文件: /var/log/redis/redis-server.log
4、启动文件: /etc/init.d/redis-server
# /etc/下存放配置文件
# /etc/init.d/下存放服务启动文件

Redis主从复制

定义

  • 一个Redis服务可以有多个该服务的复制品,这个Redis服务成为master,其他复制品成为slaves
  • master会一直将自己的数据更新同步给slaves,保持主从同步
  • 只有master可以执行写命令,slave只能执行读命令

作用

  • 分担了读的压力(高并发)

原理

  • 从服务器执行客户端发送的读命令,比如GET、LRANGE、SMEMMBERS、HGET、ZRANGE等等,客户端可以连接slaves执行读请求,来降低master的读压力

两种实现方式

方式一:(Linux命令行实现1)
  • redis-server --slaveof
# 从服务端
redis-server --port 6300 --slaveof 127.0.0.1 6379
# 从客户端
redis-cli -p 6300
127.0.0.1:6300> keys * 
# 发现是复制了原6379端口的redis中数据
127.0.0.1:6300> set mykey 123
(error) READONLY You can't write against a read only slave.
127.0.0.1:6300> 
# 从服务器只能读数据,不能写数据
方式一:(Redis命令行实现2)
# 两条命令
1、>slaveof IP PORT
2、>slaveof no one
示例
# 服务端启动
redis-server --port 6301
# 客户端连接
tarena@tedu:~$ redis-cli -p 6301
127.0.0.1:6301> keys *
1) "myset"
2) "mylist"
127.0.0.1:6301> set mykey 123
OK
# 切换为从
127.0.0.1:6301> slaveof 127.0.0.1 6379
OK
127.0.0.1:6301> set newkey 456
(error) READONLY You can't write against a read only slave.
127.0.0.1:6301> keys *
1) "myset"
2) "mylist"
# 再切换为主
127.0.0.1:6301> slaveof no one
OK
127.0.0.1:6301> set name hello
OK
方式二:(修改配置文件)
# 每个redis服务,都有1个和他对应的配置文件
# 两个redis服务
  1、6379 -> /etc/redis/redis.conf
  2、6300 -> /home/tarena/redis_6300.conf

# 修改配置文件
vi redis_6300.conf
slaveof 127.0.0.1 6379
port 6300
# 启动redis服务
redis-server redis_6300.conf
# 客户端连接测试
redis-cli -p 6300
127.0.0.1:6300> hset user:1 username guods
(error) READONLY You can't write against a read only slave.
  • 问题总结:master挂了怎么办?
    • 一个Master可以有多个Slaves
    • Slave下线,只是读请求的处理性能下降
    • Master下线,写请求无法执行
    • 其中一台Slave使用SLAVEOF no one命令成为Master,其他Slaves执行SLAVEOF命令指向这个新的Master,从它这里同步数据
      • 以上过程是手动的,能够实现自动,这就需要Sentinel哨兵,实现故障转移Failover操作
演示
1、启动端口6400redis,设置为6379的slave
   redis-server --port 6400
   redis-cli -p 6400
   redis>slaveof 127.0.0.1 6379
2、启动端口6401redis,设置为6379的slave
   redis-server --port 6401
   redis-cli -p 6401
   redis>slaveof 127.0.0.1 6379
3、关闭6379redis
   sudo /etc/init.d/redis-server stop
4、把6400redis设置为master
   redis-cli -p 6401
   redis>slaveof no one
5、把6401的redis设置为6400redis的salve
   redis-cli -p 6401
   redis>slaveof 127.0.0.1 6400
# 这是手动操作,效率低,而且需要时间,有没有自动的???

官方高可用方案Sentinel

Redis之哨兵 - sentinel
  • Sentinel会不断检查Master和Slaves是否正常
  • 每一个Sentinel可以监控任意多个Master和该Master下的Slaves
案例演示
环境搭建
# 共3台redis的服务器,如果是不同机器端口号可以是一样的
1、启动6379的redis服务器
   	sudo /etc/init.d/redis-server start
2、启动6380的redis服务器,设置为6379的从
    redis-server --port 6380
    tarena@tedu:~$ redis-cli -p 6380
    127.0.0.1:6380> slaveof 127.0.0.1 6379
    OK
3、启动6381的redis服务器,设置为6379的从
   	redis-server --port 6381
   	tarena@tedu:~$ redis-cli -p 6381
   	127.0.0.1:6381> slaveof 127.0.0.1 6379
安装并搭建sentinel哨兵
# 1、安装redis-sentinel
sudo apt install redis-sentinel
验证: sudo /etc/init.d/redis-sentinel stop
# 2、新建配置文件sentinel.conf
port 26379
Sentinel monitor tedu 127.0.0.1 6379 1

# 3、启动sentinel
方式一: redis-sentinel sentinel.conf
方式二: redis-server sentinel.conf --sentinel

#4、将master的redis服务终止,查看从是否会提升为主
sudo /etc/init.d/redis-server stop
# 发现提升6381为master,其他两个为从
# 在6381上设置新值,6380查看
127.0.0.1:6381> set name tedu
OK

# 启动6379,观察日志,发现变为了6381的从
主从+哨兵基本就够用了
sentinel.conf解释
# sentinel监听端口,默认是26379,可以修改
port 26379
# 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
sentinel monitor <master-name> <ip> <redis-port> <quorum>
生产环境中设置哨兵sentinel
1、安装sentinel
  sudo apt-get install redis-sentinel
2、创建配置文件 sentinel.conf
  port 26379
  Sentinel monitor 名字 IP PORT 投票数
3、启动sentinel开始监控
  redis-sentinel sentinel.conf

分布式锁

高并发产生的问题?

1、购票: 多个用户抢到同一张票?
2、购物: 库存只剩1,被多个用户成功买到?
... ...

怎么办?

在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段

原理

1、多个客户端先到redis数据库中获取一把锁,得到锁的用户才可以操作数据库
2、此用户操作完成后释放锁,下一个成功获取锁的用户再继续操作数据库

实现

set key value nx ex 3
# 见图: 分布式锁原理.png
博客项目解决高并发问题
  • 在数据库中创建库 blog,指定字符编码utf8
mysql -uroot -p123456
mysql>create database blog charset utf8;
  • 同步数据库,并在user_profile中插入表记录
1、python3 manage.py makemigrations
2、python3 manage.py migrate
3、insert into user_profile values ('guoxiaonao','guoxiaonao','[email protected]','123456','aaaaaaaa','bbbbbbbb','cccccccc');
  • 启动django项目,并找到django路由测试 test函数
1、python3 manage.py runserver
2、查看项目的 urls.py 路由,打开firefox浏览器输入地址:http://127.0.0.1:8000/test/
# 返回结果:	{"code": 200}
  • 在数据库表中创建测试字段score
1、user/models.py添加:
   score = models.IntegerField(verbose_name=u'分数',null=True,default=0)
2、同步到数据库
   python3 manage.py makemigrations user
   python3 manage.py migrate user
3、到数据库中确认查看
  • 在blog/views.py中补充 test函数,对数据库中score字段进行 +1 操作
from user.models import UserProfile
def test(request):
    

    u = UserProfile.objects.get(username='guoxiaonao')
    u.score += 1
    u.save()

    return JsonResponse('HI HI HI')
  • 启多个服务端,模拟30个并发请求

    • 多台服务器启动项目

      python3 manage.py runserver 127.0.0.1:8000
      python3 manage.py runserver 127.0.0.1:8001
      
    • 在tools中新建py文件 test_api.py,模拟30个并发请求

      import threading
      import requests
      import random
      
      
      def getRequest():
          url='http://127.0.0.1:8000/test/'
          url2='http://127.0.0.1:8001/test/'
          get_url = random.choice([url, url2])
          requests.get(get_url)
      
      ts = []
      for i in range(30):
          t=threading.Thread(target=getRequest,args=())
          ts.append(t)
      
      if __name__ == '__main__':
      
          for t in ts:
              t.start()
      
          for t in ts:
              t.join()
      
  • python3 test_api.py

  • 在数据库中查看 score 字段的值

  • 并没有+30,而且没有规律,每次加的次数都不同,如何解决???

  • 解决方案:redis分布式锁

def test(request):
	# 解决方法二:redis分布式锁
    import redis
    pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
    r = redis.Redis(connection_pool=pool)
    while True:
        try:
            with r.lock('guoxiaonao', blocking_timeout=3) as lock:
                u = UserProfile.objects.get(username='guoxiaonao')
                u.score += 1
                u.save()
            break
        except Exception as e:
            print('lock is failed')
    
    return HttpResponse('HI HI HI')

Redis事务

事务命令

1、MULTI  # 开启事务
2、命令1  # 执行命令
3、命令2 ... ...
4、EXEC  # 提交到数据库执行
4、DISCARD # 取消事务

使用步骤

# 开启事务
127.0.0.1:6379> MULTI
OK
# 命令1入队列
127.0.0.1:6379> INCR n1
QUEUED
# 命令2入队列
127.0.0.1:6379> INCR n2
QUEUED
# 提交到数据库执行
127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 1

事务中命令错误处理

# 1、命令语法错误,命令入队失败,直接自动discard退出这个事务
  这个在命令在执行调用之前会发生错误。例如,这个命令可能有语法错误(错误的参数数量,错误的命令名)
  处理方案:客户端发生了第一个错误情况,在exec执行之前发生的。通过检查队列命令返回值:如果这个命令回答这个队列的命令是正确的,否者redis会返回一个错误。如果那里发生了一个队列命令错误,大部分客户端将会退出并丢弃这个事务

# 2、命令语法没错,但类型操作有误,则事务执行调用之后失败,无法进行事务回滚
   从我们施行了一个由于错误的value的key操作(例如对着String类型的value施行了List命令操作)
   处理方案:发生在EXEC之后的是没有特殊方式去处理的:即使某些命令在事务中失败,所有的其他命令都将会被执行。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set num 10
QUEUED
127.0.0.1:6379> LPOP num
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get num
"10"
127.0.0.1:6379> 

python使用pipeline()与execute()批量进行批量操作

示例
import redis

# 创建连接池并连接到redis
pool = redis.ConnectionPool(host = '192.168.153.150',db=0,port=6379)
r = redis.Redis(connection_pool=pool)

# 第一组
pipe = r.pipeline()
pipe.set('fans',50)
pipe.incr('fans')
pipe.incrby('fans',100)
pipe.execute()

# 第二组
pipe.get('fans')
pipe.get('pwd')
# [b'151', b'123']
result = pipe.execute()
print(result)

Redis常见问题汇总

Redis优点

1、读写速度快. 数据存放在内存中
2、支持数据类型丰富,string,hash,list,set,sorted
3、支持事务
4、可以用于缓存,消息队列,按key设置过期时间,到期后自动删除
5、支持数据持久化(将内存数据持久化到磁盘),支持AOF和RDB两种持久化方式,从而进行数据恢复操作,可以有效地防止数据丢失
5、支持主从(master-slave)复制来实现数据备份,主机会自动将数据同步到从机

介绍一下redis中的数据类型

类型 特点 使用场景
string 简单key-value类型,value可为字符串和数字 常规计数(微博数, 粉丝数等功能)
hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象 存储部分可能需要变更的数据(比如用户信息)
list 有序可重复列表 关注列表,粉丝列表,消息队列等
set 无序不可重复列表 存储并计算关系(如微博,关注人或粉丝存放在集合,可通过交集、并集、差集等操作实现如共同关注、共同喜好等功能)
sorted set 每个元素带有分值的集合 各种排行榜

redis中的持久化方案

# RDB
快照形式,定期把内存中的数据保存到磁盘。Redis默认支持的持久化方案。速度快但是服务器断电的时候会丢失部分数据

# AOF
把所有对redis数据库增删改操作的命令保存到文件中。数据库恢复时把所有的命令执行一遍即可。
# 两种持久化方案同时开启使用AOF文件来恢复数据库.能保证数据的完整性,但是速度慢。

使用过Redis分布式锁么,它是什么回事?

1、从redis2.8开始,set命令集成了两个参数,nx和ex,先拿nx来争抢锁,抢到之后,再用ex参数给锁加一个过期时间防止锁无法释放,造成死锁
  set username AAA nx ex 3
2、redis分布式锁原理见图

缓存穿透

# 原理
缓存和数据库都没有的数据,而用户反复发起请求, 如 假的用户ID

# 场景
比如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大

# 解决方案:
   1、请求校验,接口层增加校验,如对id做基础校验,id<=0的直接拦截
   2、都无法取到数据时也可以将key-value对写为key-null,缓存有效时间比如30秒左右,这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿

# 原理
缓存没有,数据库有,一般是缓存时间到期, 顺势并发太大

#解决方案
1、热点数据不过期  
2、上锁: 重新设计缓存的使用方式,当我们通过key去查询数据时,首先查询缓存,如果没有,就通过分布式锁进行加锁,取得锁的进程查DB并设置缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回缓存数据或者再次查询DB

缓存雪崩

# 原理
缓存中大批量数据过期,导致瞬时大批量不同请求注入DB

# 解决方案
解决方案
1、缓存设置随机时间(避免缓存设置相近的有效期;为有效期增加随机值)
2、热点数据不过期

Redis 存储 Session

安装

  • pip3 install django-redis-sessions==0.5.6

修改settings.py文件

  • 添加如下项:
SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 2
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session'

在view.py文件里,修改session_test视图

def session_test(request):
    request.session['hi'] = 'hello'
    # hi = request.session.get('hi')
    # del request.session['hi']
    # request.session.flush()
    return HttpResponse('ok')

shell命令

查看Redis进程

#显示进程数,ip,端口号
$ ps aux | grep redis

你可能感兴趣的:(python,后端开发)