关于 Python 系列其他文章的传送门

一、JavaScript¶
this 的坑¶
1 | var school = { |
上面这样调用的执行结果中,2 和 2.5 中的 this 不是期望的 school,而是 global 中的 this
原因:
getNamefunc 是 school 的一个属性,调用时会隐含的传入一个 this,所以 1 的 this 肯定是 school。而 func 这样第二级调用,得到的只是一个普通的函数,根本没有传入 this,this 自然也就不会是 school 了。
解决办法:
1、显式传入:使用 call、apply、bind 传入指定的 this 来解决这个问题:
1 | func.call(school, x, y) |
2、ES5 引入了 bind 方法
1 | func.bind(school) // bind 函数返回的是一个新的函数,类似 python 中的 partial 函数 |
3、使用 ES6 引入的箭头函数,就不会有这种问题了
Babel¶
js 转译工具
注意:以下仅适用于 babel 6 版本,并不适用于最新版本
开发中可以使用较新的ES6语法,通过转译器转换为指定的某些版本代码。
1. Node.js 项目初始化¶
1 | npm init |
2. 设置国内镜像加速¶
编辑 .npmrc 文件:
1 | echo "registry=https://registry.npm.taobao.org" > .npmrc |
可以放到 npm 的目录下 npmrc 文件中
可以放到用户家目录中
可以放到项目根目录中
3. 安装¶
项目根目录下执行
1 | npm install babel-core babel-cli --save-dev |
--save和--save-dev可以省掉你手动修改package.json文件的步骤。
--save自动把模块和版本号添加到dependencies部分--save-dev自动把模块和版本号添加到devdependencies部分
4. 修改 package.json¶
替换 scripts 的部分为:
1 | { |
babel src -d lib 意思是从 src 目录中转译后的文件输出到 lib 目录。
5. 配置 babel 和安装依赖¶
在目录根目录下创建 .babelrc 文件,Json 格式。
1 | { |
env 的意思是可以根据当前环境自动选择。
安装依赖
1 | npm install babel-preset-env --save-dev |
6. 转换¶
在项目根目录下执行命令:
1 | npm run build |
会把 src 中的 js 文件进行转换,到 lib 目录。
Antd 样式自动加载¶
使用 babel-plugin-import 项目可以实现 Antd 组件样式自动加载
项目 Github:https://github.com/ant-design/babel-plugin-import
安装:
1 | npm install babel-plugin-import -D |
ant-design 引入样式的几种方式:
1、全局引用:
- 引用的方式很容易,不过这实际上加载了全部的 antd 组件的样式文件,对前端性能是个隐患。
1 | import React, { Component } from 'react'; |
2、单个组件分别引入对应的样式文件:
- 这样也算是”按需加载“,不过比较麻烦,每次写一个新组件,都要先载入 antd 组件,再载入对应 css 文件,代码冗余(而且写着也麻烦,你多引几个组件就要多写几行代码,程序员嘛都是要偷下懒的)
1 | // 比如想要引入 Button 组件和其对应的 css 样式 |
3、使用 babel-plugin-import 实现按需加载:
在 .babelrc 配置文件中,添加以下配置代码:
1 | "plugins": [ |
需要在 webpack.config.js 的 exclude 中,把 node_modules 去掉:
1 | { |
导入样式的方式就变成了:
1 | // 会自动转换为 antd/lib/button,配合 style 属性可以做到模块样式按需自动加载,避免了代码冗余 |
阻止表单自动提交¶
要阻止页面刷新,其实就是阻止提交,使用 event.preventDefault()。
阻止后,可由自己控制发起异步提交等操作。
1 | event.preventDefault(); |
异步模块 Promise¶
Promise 对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。
简单说,就是处理异步请求的。
之所以叫做 Promise,就是我承诺,如果成功则怎么处理,失败则怎么处理。
1 | // 语法 |
关于 executor:
- executor 是一个带有 resolve 和 reject 两个参数的函数;
- executor 函数在 Promise 的构造函数执行时,同步执行,并被传递
resolve和reject函数(executor 函数在 Promise 的构造函数返回新建对象前被调用); - executor 内部通常会执行一些异步操作,一旦完成,可以调用
resolve函数来将 promise 状态改成 fulfilled 即完成,或者在发生错误时调用reject将它的状态改为 rejected 即失败; - 如果在 executor 函数中抛出一个错误,那么该 promise 状态为 rejected,executor 函数的返回值被忽略;
- 在 executor 中,
resolve或reject只能执行其中一个函数。
Promise 的状态:
- pending: 初始状态,不是成功或失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
Promise.then(onFulfilled, onRejected):
- 参数是 2 个函数,根据 Promise 的状态来调用不同的函数:
- fulfilled 状态走 onFulfilled 函数;
- rejected 状态走 onRejected 函数。
- then 的返回值是一个新的 promise 对象;
- 调用任何一个参数后,其返回值会被新的 promise 对象来 resolve,向后传递。
1 | // 简单使用 |
axios 异步库¶
axios 是一个基于 Promise 的 HTTP 异步库,可以用在浏览器或 nodejs 中。
使用 axios 发起异步调用,完成 POST、GET 方法的数据提交。可参照官网的例子
中文说明 https://www.kancloud.cn/yunye/axios/234845
安装 npm:
1 | npm install axios |
导入:
1 | import axios from 'axios'; |
GET:
1 | axios.get('/user?ID=12345') |
POST:
1 | axios.post( |
实例化(可以加 header 信息)¶
如要定义 header 信息,需要使用实例化的方式。
可以使用自定义配置新建一个 axios 实例,语法:
1 | axios.create([config]) // 这里的 [] 指语法中的可选意思,不是 js 中的 [] |
示例:
1 | var instance = axios.create({ |
可以通过设置一个 baseURL,便于为 axios 实例的方法传递 URL 的相对路径。
它会自动在 baseURL 的后面加上 url,除非 url 自己就是一个绝对 URL。例如:
1 | var instance = axios.create({ |
实例可用方法:
axios#request(config)axios#get(url[, config])axios#delete(url[, config])axios#head(url[, config])axios#post(url[, data[, config]])axios#put(url[, data[, config]])axios#patch(url[, data[, config]])
token 持久化 —— LocalStorage¶
推荐使用 LocalStorage 来存储 token。
LocalStorage 是浏览器端持久化方案之一,HTML5 标准增加的技术。
LocalStorage 是为了存储得到的数据,例如 Json。
数据存储就是键值对。
数据会存储在不同的域名下面。
不同浏览器对单个域名下存储数据的长度支持不同,有的最多支持 2MB。
SessionStorage 和 LocalStorage 差不多,不过 SessionStorage 是会话级的,浏览器关闭,会话结束,数据清除。
store.js¶
store.js 是一个兼容所有浏览器的 LocalStorage 包装器,不需要借助 Cookie 或者 Flash。
store.js 会根据浏览器自动选择使用 localStorage、globalStorage 或者 userData 来实现本地存储功能。
安装
1 | npm i store |
示例:
1 | import store from 'store'; |
安装 store 的同时,也安装了 expire 过期插件,可以在把 kv 对存储到 LS 中的时候顺便加入过期时长。
1 | import store from 'store'; |
Mobx 状态管理¶
state 的控制起来有一下繁琐。可以使用社区提供的状态管理库,有 Redux 和 Mobx。
Redux 和 Mobx 对比:
- Redux 代码优秀,使用严格的函数式编程思想,学习曲线陡峭,小项目使用的优势不明显;
- Mobx,非常优秀稳定的库,简单方便,适合中小项目使用。使用面向对象的方式,容易学习和接受。现在在项目中使用也非常广泛。
当 Mobx 和 React 一起,是一对强力组合。
Mobx 官网 https://mobx.js.org/
中文网 http://cn.mobx.js.org/
MobX 是由 Mendix、Coinbase、Facebook 开源,它实现了 观察者模式。
观察者模式:也称为发布订阅模式
观察者观察某个目标,如目标对象 Obserable
当状态发生了变化,就会通知已注册的观察者 Observer。
状态管理的 3 中实现¶
当一个组件的 onClick 触发事件响应函数,此函数会调用后台服务。
但是后台服务可能比较耗时,等后台处理完,需要引起组件的渲染操作。
要组件重新渲染,需要改变组件的 props 或 state。
方案 1:同步调用¶
同步调用中,实际上就是等着耗时的函数返回,属于等待状态,页面看起来就行卡死一样,强烈不建议使用。
例如:
1 | class Service { |
方案 2:使用 Promise 异步执行¶
使用 Promise 异步执行,如果成功执行,将调用回调。
1 | import React from 'react'; import ReactDom from 'react-dom'; |
只要 state 改变,都会触发 render 重绘。
方案 3:使用 Mobx 实现(推荐)¶
observable装饰器:设置 被观察者;observer装饰器:设置 观察者,将 React 组件转换为响应式组件。
1 | import React from 'react'; |
当 Service 中的 被观察者 ret 发生变化,会触发 观察者 调用 render 函数。
注意:如果在 render 中不使用这个被观察者 ret,render 函数就不会调用。
二、React¶
React 是 Facebook 开发并开源的前端框架。
当时他们的团队在市面上没有找到合适的 MVC 框架,就自己写了一个 Js 框架,用来假设大名鼎鼎的 Instagram (图片分享社交网络),在 2013 年将 React 开源。
React 解决的是前端 MVC 框架中的 View 视图层的问题。
webpack¶
这是一个符合 Commonjs,用来打包的模块,详细配置看 PDF。
React项目构建.pdf
url 重定向:
类似 httpd、nginx 等的 rewrite 功能。
官方可以查到:https://webpack.js.org/configuration/dev-server/#devserver-proxy。
使用 pathRewrite 可以完成路由重写,在配置文件中 webpack.config.dev.js 中 proxy 部分:
1 | proxy: { |
组件状态 state¶
每一个 React 组件都有一个状态变量 state,它是一个 JavaScript 对象,可以为它定义属性来保存值。
如果状态变化了,会触发 UI 的重新渲染。使用 setState() 方法可以修改 state 值。不可以对还在更新中的 state 使用 setState()。
注意:state 是组件自己内部使用的,是组件私有的属性。
属性 props¶
把 React 组件当做标签使用,可以为其增加属性,如下:
1 | <Toggle name="school" parent={this} /> |
name = "school":这个属性会作为一个单一的对象传递给组件,加入到组件的 props 属性中。
构造器 constructor¶
使用 ES6 的构造器,要提供一个参数 props ,并把这个参数使用 super 传递给父类:
1 | import React from 'react'; |
组件的生命周期¶
组件的生命周期可分成三个状态:
Mounting:已插入真实 DOM;Updating:正在被重新渲染;Unmounting:已移出真实 DOM。

无状态组件(函数式组件)¶
React 从 15.0 开始支持无状态组件,定义如下:
1 | import React from 'react'; |
无状态组件,也叫 函数式组件。
开发中,很多情况下,组件其实很简单,不需要 state 状态,也不需要使用生命周期函数。无状态组件很好的满足了需要。
无状态组件函数应该提供一个参数 props,返回一个 React 元素。
无状态组件函数本身就是 render 函数。
高阶组件 – 装饰器¶
新版 ES 2016 中增加了装饰器的支持,因此可以使用装饰器来改造上面的代码。
1 | import React from 'react'; |
高阶组件 – 带参装饰器¶
想给装饰器函数增加一个 id 参数:
1 | import React from 'react'; |
特别注意¶
搬到 React 组件中的时候,要将 class 属性改为 className,如:
1 | // 在 js 中 |
所有标签,需要闭合。
React-Route 前端路由¶
官网文档 https://reactrouter.com/web/guides/philosophy
基本例子 https://reactrouter.com/web/example/basic
示例:
1 | import React from 'react'; |
Route负责静态路由;path是匹配路径,如没有path则总是匹配;component是目标组件;exact:布尔值,true 时要求路径 完全匹配;strict:布尔值,true 时,要求 严格匹配,但是 url 字符串可以是自己的子串。
注入参数举例:
<Route path="/about" component={About} />- 匹配的
query信息会自动注入到组件的props中,之后在 About 中可以通过props.location.search提取 search;
- 匹配的
<Route path="/getpage/:id" component={Getpage} />- id 位置的匹配会自动传入 Getpage 组件,之后在 Getpage 中可以通过
props.match.params.id提取;
- id 位置的匹配会自动传入 Getpage 组件,之后在 Getpage 中可以通过
Redirect 重定向¶
使用 <Redirect> 可以设置重定向到其他 route 而不改变旧的 URL。
from:你想由哪个路径进行重定向,包括动态段。to:你想重定向的路径。query:默认情况下,query 的参数只会经过,但如果你需要你可以指定它们。
1 | /* /course/123/home -> /course/123/dashboard */} |
1 | // 我们需要从 `/profile/123` 改变到 `/about/123` |
使用 react-router 时刷新页面 404 问题解决方法¶
参考:https://www.jb51.net/article/131789.htm
使用 React 开发新项目时,遇见了刷新页面,直接访问二级或三级路由时,访问失败,出现 404 或资源加载异常的情况,本篇针对此问题进行分析并总结解决方案。
问题复现¶
举例:访问
localhost:9090是可以正常加载页面和 js 等文件的,但是当我们需要访问二级甚至三级路由或者刷新页面时,如localhost:9090/posts/92时,可能会出现两种情况:
- 页面加载失败,返回
Cannot Get(404);- 服务响应,但是没有返回 webpack 处理输出的 html 文件,导致无法加载 js 资源
分析问题¶
判断这个问题一般是两方面原因造成:
react-router前端路由配置;webpack-dev-server服务配置;
解决办法¶
去查询 react-router 路由库相关配置信息,发现文档中提到了使用 browserHistory 时,会创建真实的 URL,处理初始 / 请求没有问题,但是对于跳转路由后,刷新页面或者直接访问该 URL 时,会发现无法正确相应,更多信息查看参考文档,文档中也提供了几种服务器配置解决方式:
Nginx:
如果使用的是 nginx 服务器,则只需要使用 try_files 指令:
1 | server { |
Apache:
如果使用 Apache 服务器,则需要在项目根目录创建 .htaccess 文件,文件包含如下内容:
1 | RewriteBase / |
以上就是针对服务器的配置解决。
博客项目中,高阶装饰器的转换过程¶
装饰器函数的整个演变过程如下:
1 | import React from 'react'; |
简要概括版本:
1 | import React from 'react'; |
三、Django¶
概述¶
Django 采用 MVC 架构设计的开源的 WEB 快速开发框架。
优点:
- 能够快速开发,如 Auth, Cache, 模板
- MVC 设计模式
- 实用的管理后台
- 自带 ORM, Template, Form, Auth 核心
- 组件
- 简洁的 url 设计
- 周边插件丰富
缺点:
- 重,因为东西大而全
- 同步阻塞
所以 Django 的设计目标就是一款大而全,便于企业快速开发项目的框架,因此企业应用较广。
安装、配置、初始化¶
安装 Django¶
1 | pip install django |
创建 django 项目¶
1 | django-admin startproject blog . |
重要文件说明¶
manage.py:本项目管理的命令行工具。应用创建、数据库迁移等,都使用它完成;blog/settings.py:本项目的配置文件。数据库参数等;blog/urls.py:URL 路径映射配置。默认情况下,只配置/admin的路由;blog/wsgi:定义 WSGI 接口信息。一般无须改动。
数据库配置¶
使用数据库,需要修改默认的数据库配置。
在主项目的 settings.py 下的 DATABASES。
默认使用的 sqlite,修改为 mysql。
1 | DATABASES = { |
Django 支持 MySQL 5.5+
Django 官方推荐使用本地驱动 mysqlclient:
1 | pip install mysqlclient |
创建应用¶
创建用户应用,这里以 user 这个名字为例,
1 | python manage.py startapp user |
创建应用后,项目根目录下产生一个以 user 为名的目录,有如下文件:
admin.py:管理站点模型的声明文件;models.py:模型层 Model 类定义;views.py:定义 URL 响应函数;migrations:包数据迁移文件生成目录;apps.py:应用的信息定义文件。
注册应用¶
在 settings.py 中,增加 user 应用。目的是为了后台管理 admin 使用,或迁移 migrate 使用。
1 | INSTALLED_APPS = [ |
模型 Model¶
| 字段类 | 说明 |
|---|---|
| AutoField | 自增的整数字段。 如果不指定,django 会为模型类自动增加主键字段。 |
| BooleanField | 布尔值字段,True 和 False。对应表单控件 CheckboxInput。 |
| NullBooleanField | 比 BooleanField 多一个 null 值。 |
| CharField | 字符串,max_length 设定字符长度。对应表单控件 TextInput。 |
| TextField | 大文本字段,一般超过 4000 个字符使用。 对应表单控件 Textarea。 |
| IntegerField | 整数字段。 |
| BigIntegerField | 更大整数字段,8 字节。 |
| DecimalField | 使用 Python 的 Decimal 实例表示十进制浮点数。max_digits 总位数,decimal_places 小数点后的位数。 |
| FloatField | Python 的 Float 实例表示的浮点数 |
| DateField | 使用 Python 的 datetime.date 实例表示的日期。auto_now=False 每次修改对象自动设置为当前时间。auto_now_add=False 对象第一次创建时自动设置为当前时间。auto_now_add、auto_now、default 互斥。对应控件为 TextInput,关联了一个 Js 编写的日历控件。 |
| TimeField | 使用 Python 的 datetime.time 实例表示的时间,参数同上。 |
| DateTimeField | 使用 Python 的 datetime.datetime 实例表示的时间,参数同上。 |
| FileField | 一个上传文件的字段。 |
| ImageField | 继承了 FileField 的所有属性和方法,但是对上传的文件进行校验,确保是一个有效的图片。 |
字段选项:
| 值 | 说明 |
|---|---|
| db_column | 表中字段的名称。如果未指定,则使用属性名。 |
| primary_key | 是否主键。 |
| unique | 是否是唯一键。 |
| default | 缺省值。这个缺省值不是数据库字段的缺省值,而是新对象产生的时候被填入的缺省值。 |
| null | 表的字段是否可为 null,默认为 False。 |
| blank | Django 表单验证中,是否可以不填写,默认为 False。 |
| db_index | 字段是否有索引。 |
关系类型字段类:
| 类 | 说明 |
|---|---|
| ForeignKey | 外键,表示一对多 ForeignKey('production.Manufacturer') 自关联 ForeignKey('self ') |
| ManyToManyField | 表示多对多。 |
| OneToOneField | 表示一对一。 |
一对多时,自动创建会增加 _id 后缀。
- 从一访问多,使用
对象.小写模型类_set; - 从一访问一,使用
对象.小写模型类
访问 id 对象.属性_id
创建 Model 类¶
Model 类:
- 要从基类
models.Model继承; - 表名不指定则默认使用
<appname>_<model_name>,使用 Meta 类修改表名
创建一个 User 的 Model 类:
1 | class User(models.Model): |
迁移 Migration¶
迁移:Django 会从模型定义生成数据库的表
1、生成迁移文件:
1 | python manage.py makemigrations |
若之后修改过 Model 类,还需要再执行一次 python manage.py makemigrations 进行 migrate,迁移文件的序号会增加。
注意:迁移的应用必须在 settings.py 的 INSTALLED_APPS 中注册过。
2、执行迁移,生成数据库的表:
1 | python manage.py migrate |
执行迁移,Django 会连接到数据库,按照 model 创建 user 表,还会同时生成 admin 管理用的表。
本地化¶
settings.py 中设置语言、时区。
1 | LANGUAGE_CODE = 'zh-Hans' |
语言名称可以查看 django\contrib\admin\locale 目录。
启动 WEB Server¶
1 | python manage.py runserver |
默认启动 8000 端口
Django 后台管理¶
1、创建管理员
1 | manage.py createsuperuser |
后台登录地址:http://127.0.0.1:8000/admin
路由¶
编写 WSGI 框架项目中,路由功能就是实现 URL 模式匹配和处理函数之间的映射。对于 Django 也是如此。
路由配置要在项目的 urls.py 中配置,也可以多级配置,在每一个应用中,建立一个 urls.py 文件配置路由映射。
url 函数
url(regex, view, kwargs=None, name=None) 进行模式匹配:
regex:正则表达式,与之匹配的 URL 会执行对应的第二个参数;view:用于执行与正则表达式匹配的 URL 请求;kwargs:视图使用的字典类型的参数;name:用来反向获取 URL。
多级路由¶
为了避免项目的总 urls.py 文件中条目过多,也为了让应用自己管理路由。
使用 include 函数,参数写 应用.路由模块,例如 include('user.urls'),该函数就会动态导入指定的包的模块,从模块里面读取 urlpatterns,并返回三元组。
而 url 函数的第二参数如果不是可调用对象,而是元组或列表,则会从路径中除去已匹配的部分,将剩余部分与应用中的路由模块的 urlpatterns 进行匹配。
例如,要访问的路径为 /user/reg:
- 项目中的
urls.py文件负责解析第一级:/user; - 应用中的
urls.py文件负责解析第二级/reg。
1 | urlpatterns = [ |
之后在应用 user 中新建 urls.py 文件:
1 | from django.conf.urls import url |
在浏览器中输入 http://127.0.0.1:8000/user/reg 测试一下,可以看到响应的数据。
JSON 数据处理 – simplejson¶
simplejson 比标准库方便好用,功能强大。
1 | pip install simplejson |
解析的方式同 json 模块,但是 simplejson 更方便。
错误处理¶
Django 中有很多异常类,定义在 django.http 下,这些类都继承自 HttpResponse。
所以如果返回异常,要返回实例,而不是异常类
而且要用 return HttpResponseBadRequest();
而不是 raise HttpResponseBadRequest()。
前端是通过状态码来判断是否异常的,所以不能 raise,只能使用 return 返回状态码给前端。
模型操作(ORM)¶
Django 会为模型类提供一个 objects 对象,它是 django.db.models.manager.Manager 类型,用于与数据库交互。
当定义模型类的时候没有指定管理器,则 Django 会为模型类提供一个默认的 objects 的管理器。
如果在模型类中手动指定管理器后,Django 就不再提供默认的 objects 的管理器了。
不过大部分情况不会手动指定管理器,一般不会遇到。
Django 默认是在 save()、delete() 的时候事务自动提交。
查询¶
查询集¶
查询会返回结果的集,它是 django.db.models.query.QuerySet 类型。
它是惰性求值,和 sqlalchemy 一样, 结果就是查询的集。
它是可迭代对象。
-
惰性求值:
创建查询集不会带来任何数据库的访问,直到调用数据时,才会访问数据库。
在迭代、序列化、if语句中都会立即求值。 -
缓存:
每一个查询集都包含一个缓存,来最小化对数据库的访问。
新建查询集,缓存为空。首次对查询集求值时,会发生数据库查询,Django 会把查询的结果存在这个缓存中,并返回请求的结果。
- 没有使用缓存,每次都要去查库,查了 2 次库:
1 | [user.name for user in User.objects.all()] |
- 下面的语句使用缓存,因为使用同一个结果集:
1 | qs = User.objects.all() |
限制查询集(切片)¶
查询集对象可以直接使用索引下标的方式 (不支持负索引),相当于 SQL 语句中的 limit 和 offset 子句。
注意使用索引返回的新的结果集,依然是惰性求值,不会立即查询。
1 | # LIMIT 20 OFFSET 20 |
过滤器¶
返回 查询集 的方法,称为过滤器,如下:
| 名称 | 说明 |
|---|---|
| all() | |
| filter() | 过滤,返回满足条件的数据 |
| exclude() | 排除,排除满足条件的数据 |
| order_by() | |
| values() | 返回一个对象字典的列表,像 json。 |
filter(k1=v1).filter(k2=v2) 等价于 filter(k1=v1, k2=v2);
主键 pk¶
filter(pk=10) 这里 pk 指的就是主键,这样使用不用关心主键字段名,当然也可以使用使用主键名。
返回单个值的方法¶
| 名称 | 说明 |
|---|---|
| get() | 仅返回单个满足条件的对象 如果未能返回对象则抛出 DoesNotExist 异常; 如果能返回多条,抛出 MultipleObjectsReturned 异常。 |
| count() | 返回当前查询的总条数 |
| first() | 返回第一个对象 |
| last() | 返回最后一个对象 |
| exist() | 判断查询集中是否有数据,如果有则返回 True |
1 | user = User.objects.filter(email=email).get() # 期待查询集只有一行,否则抛出异常 |
字段查询(Field Lookup)表达式¶
字段查询表达式可以作为 filter()、exclude()、get() 的参数,实现 where 子句的效果。
语法:属性(字段)名称__比较运算符 = 值。
注意:属性名和运算符之间使用双下划线
可用比较运算符如下:
| 名称 | 举例 | 说明 |
|---|---|---|
| exact | filter(isdeleted=False)filter(isdeleted__exact=False) |
严格等于,可省略不写 |
| contains | exclude(title__contains='天') |
是否包含。 大小写敏感,等价于 like '% 天 %' |
| statswith endswith |
filter(title__startswith='天') |
以什么开头或结尾,大小写敏感 |
| isnull isnotnull |
filter(title__isnull=False) |
是否为 null |
| iexact icontains istartswith iendswith |
i 的意思是忽略大小写 |
|
| in | filter(pk__in=[1,2,3,100] |
是否在指定范围数据中 |
| gt、gte lt、lte |
filter(id__gt=3)filter(pk__lte=6)filter(pub_date__gt=date(2000,1,1)) |
大于、小于、等于 |
| year、month、day week_day hour、minute、second |
filter(pub_date__year=2000) |
对日期类型属性处理 |
Q 对象¶
虽然 Django 提供传入条件的方式,但是不方便,它还提供了 Q 对象来解决。
Q 对象是 django.db.models.Q,可以使用 &(and)、|(or) 操作符来组成逻辑表达式。~ 表示 not。
1 |
|
可使用 &、| 和 Q 对象来构造复杂的逻辑表达式。
过滤器函数可以使用一个或多个Q对象。
返回字段的 “人类可读” 值¶
对于每一个设置了 choice 的字段,该对象将会有一个 get_FOO_display() 方法,其中 FOO 是字段的名称。该方法返回字段的 “人类可读” 值。
例如,model 中字段设置了 choice
1 | from django.db import models |
1 | p = Person(name="Fred Flintstone", shirt_size="L") |
Django 模板技术¶
无 session 方案 – JWT¶
既然服务端就是需要一个 ID 来表示身份,那么不使用 session 也可以创建一个 ID 返回给客户端。但是,要保证客户端不可篡改。
服务端生成一个标识,并使用某种算法对标识签名。
服务端收到客户端发来的标识,需要检查签名。这种方案的缺点是,加密、解密需要消耗 CPU 计算资源,无法让浏览器自己主动检查过期的数据以清除。
这种技术称作 JWT(Json WEB Token)。
别再使用 JWT 作为 Session 系统!问题重重且很危险。
pyjwt
JWT(Json WEB Token) 是一种采用 Json 方式安装传输信息的方式。
这次使用 PyJWT,它是 Python 对 JWT 的实现。
安装
1 | pip install pyjwt |
jwt 的原理演示:
1 | import jwt |
由此,可知 jwt 生成的 token 分为三部分:
- header: 由数据类型、加密算法构成;
- payload: 负载就是要传输的数据,一般来说放入 python 对象即可,会被 json 序列化的;
- signature: 签名部分。是前面 2 部分数据分别 base64 编码后使用点号连接后,通过加密算法使用 key 计算好一个结果,再被 base64 编码,得到签名。
所有数据都是明文传输的,只是做了 base64,如果是敏感信息,请不要使用 jwt。
数据签名的目的不是为了隐藏数据,而是保证数据不被篡改。如果数据篡改了,发回到服务器端,服务器使用自己的 key 再计算一遍,然后进行签名比对,一定对不上签名。
过期时间
pyjwt 支持过期设定,在 decode 的时候,如果过期,则抛出异常。
需要在 payload 中增加一个 exp。exp 要求是一个整数 int 的时间戳。
1 | AUTH_EXPIRE = 8 * 60 * 60 # 8 小时过期 |
密码加强方案 – bcrypt¶
早期,都是明文的密码存储。
后来,使用 MD5 存储,但是,目前也不安全,网上有很多 MD5 的网站,使用反查方式找到密码。
加盐,使用 hash(password + salt) 的结果存入数据库中,就算拿到数据库的密码反查,也没有用了。
如果是固定加盐,还是容易被找到规律,或者从源码中泄露。
使用随机加盐,每一次盐都变,就增加了破解的难度。
通过暴力破解,什么密码都不能保证不被暴力破解,例如穷举。
所以要使用慢 hash 算法,例如使用 bcrypt,就会让每一次计算都很慢,都是秒级的,这样穷举的时间就会很长,为破解一个密码,在当前 CPU 或者 GPU 的计算能力下可能需要几十年以上。
安装:
1 | pip install bcrypt |
使用:
1 | import bcrypt |
从耗时看出,bcrypt 加密、验证非常耗时,所以如果穷举,非常耗时。
而且碰巧攻破一个密码,由于盐不一样,还得穷举另一个。
四、博客网站打包部署¶
项目打包¶
打包后端 python¶
在项目虚拟环境中,使用 pip 生成依赖包文件列表:
1 | pip freeze > requirements |
创建 setup.py 文件:
1 | from setuptools import setup # 内部返回了 return distutils.core.setup(**attrs) |
打包:
1 | python setup.py sdist --format=gztar # 指定打成 gz 格式 |
打包前端 React 项目¶
在 webpack.config.prod.js 中增加 css 的 loader,否则编译可能出错
1 | { |
在项目目录下编译:
1 | npm run build |
编译好的在 dist 目录下:
1 | ls dist |
项目部署¶
后端:Django 和 uWSGI 部署¶
虚拟环境¶
别忘了在 Linux 系统中使用 virtualenv 创建一个 虚拟环境 目录,如未安装使用 pip3 install virtualenv:
1 | mkdir blog_deploy |
在虚拟环境并解压包:
1 | tar -xf blog-0.1.1.tar.gz |
安装依赖:
1 | root] pip install -r requirements |
配置文件¶
修改 Django 配置文件:blog/settings.py,为了:
- 关闭
DEBUG功能; - 设置允许访问的主机;
- 设置实际的数据库地址。
1 | 修改 Django 配置文件:`blog/settings.py` |
注意:ALLOWED_HOSTS = ["*"] 这是所有都可以访问,生产环境应指定具体可以访问的 IP,而不是所有。
在 Mac 上,sed 命令有一些区别,sed 命令直接操作文件的时候,必须指定备份的格式:
1 | sed -i '' -e 's/DEBUG.*/DEBUG = False/' -e 's/ALLOWED_HOSTS.*/ALLOWED_HOSTS = ["*"]/' -e "s/'HOST':.*/'HOST': '192.168.50.30',/" blog/settings.py |
uWSGI 部署¶
uWSGI 是一个 C 语言的项目,提供一个 WEB 服务器,它支持 WSGI 协议,可以和 Python 的 WSGI 应用程序通信。
在这里,uWSGI 负责在 socket 上监听并接收 Nginx 发来的 uWSGI 协议的请求,并通过 WSGI 协议调用 Django 应用。
安装:
1 | pip install uwsgi $ uwsgi --help |
项目目录下放 uWSGI 的配置文件 uwsgi.ini:
1 | [uwsgi] |
启动 uWSGI:
1 | uwsgi blog.ini |
至此,后端 Django 应用部署完成。
测试服务,如成功返回数据,说明 Django 应用成功部署:
1 | curl http://192.168.50.30:8000/post?page=2&size=2 |
当使用 DockerFile 时¶
1 | FROM python:3 |
构建镜像:
1 | docker build -t blog-app ./blog_deploy |
前端:React 和 Nginx 部署¶
Nginx 配置¶
1 | server { |
静态文件部署¶
把之前编译好的静态文件拷贝到 html 目录,按照以下结构:
1 | tree html |
启动 Nginx¶
1 | systemctl start nginx |
当使用 Docker 时¶
1 | docker run --name nginx -v ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro -v ./nginx/html:/usr/share/nginx/html:ro -p80:80 -d nginx |
启动数据库的参考:
1 | docker run --name db -v ./mariadb:/var/lib/mysql -p192.168.50.30:3306:3306 -e MYSQL_ROOT_PASSWORD=123qwe -e TZ=Asia/Shanghai mariadb |