项目实战二
# 项目实战二
# 需求
以前后端分离的方式实现学生的增删改查操作
# 学生列表接口
url:/students/
请求方法:get
参数:
- 格式:查询参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
page | int | 否 | 页码,默认为1 |
size | init | 否 | 每页数据条数默认为10 |
name | str | 否 | 根据姓名过滤 |
age | int | 否 | 根据年龄过滤 |
sex | int | 否 | 根据性别过滤 |
phone | str | 否 | 根据手机过滤 |
channel | int | 否 | 根据渠道过滤 |
响应:
状态码:200
格式:json
响应示例:
{
"total": 7,
"page": 1,
"next_page": null,
"pre_page": null,
"results": [
{
"id": 8,
"name": "yaya",
"age": 18,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-09-01T06:30:48.417Z"
},
{
"id": 7,
"name": "yaya",
"age": 18,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-09-01T06:10:41.869Z"
},
{
"id": 6,
"name": "心蓝",
"age": 20,
"sex": 1,
"phone": "15873061798",
"channel": "",
"c_time": "2022-08-31T12:21:04.068Z"
},
{
"id": 5,
"name": "小简",
"age": 16,
"sex": 0,
"phone": null,
"channel": "抖音",
"c_time": "2022-08-23T13:10:05.317Z"
},
{
"id": 3,
"name": "张柏芝",
"age": null,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-08-23T08:00:15.165Z"
},
{
"id": 2,
"name": "刘德华",
"age": null,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-08-23T08:00:08.035Z"
},
{
"id": 1,
"name": "心蓝",
"age": null,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-08-23T07:59:29.417Z"
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 学生详情接口
url:/students/pk/
请求方法:get
参数:
- 格式:路径参数
响应数据:
状态码:200
格式:json
响应实例:
{
"id": 1,
"name": "心蓝",
"age": null,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-08-23T07:59:29.417Z"
}
2
3
4
5
6
7
8
9
# 学生添加接口
url:/students/
请求方法:post
参数:
- 格式:json
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
name | str | 是 | 姓名 |
age | int | 否 | 年龄 |
sex | int | 否 | 性别 |
phone | str | 否 | 手机 |
channel | int | 否 | 渠道id |
- 请求实例:
{
"name": "刘德华",
"age": 60,
"sex": 1,
"phone": '13888888888',
"channel": 1
},
2
3
4
5
6
7
响应:
状态码:201
格式:json
响应示例:
{
"id": 8,
"name": "yaya",
"age": 18,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-09-01T06:10:41.869Z"
}
2
3
4
5
6
7
8
9
# 学生修改接口
url:/students/pk/
请求方法:put/patch
参数:
- 格式:json,路径
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
name | str | 否 | 姓名 |
age | int | 否 | 年龄 |
sex | int | 否 | 性别 |
phone | str | 否 | 手机 |
channel | int | 否 | 渠道id |
- 请求实例:
{
"name": "刘德华",
"age": 60,
"sex": 1,
"phone": '13888888888',
"channel": 1
},
2
3
4
5
6
7
响应:
状态码:200
格式:json
响应示例:
{
"id": 8,
"name": "yaya",
"age": 18,
"sex": 1,
"phone": null,
"channel": "",
"c_time": "2022-09-01T06:10:41.869Z"
}
2
3
4
5
6
7
8
9
# 学生删除接口
url:/students/pk/
请求方法:delete
参数:
- 格式:路径
响应:
- 状态码:204
- 格式:无响应内容
# 后端代码
# 视图
class StudentView(View):
def serialize(self, item):
return {
'id': item.id,
'name': item.name,
'age': item.age,
'sex': item.sex,
'phone': item.phone,
'channel': item.channel.title if item.channel else '',
'c_time': item.c_time
}
def get(self, request, pk=None):
if pk is not None:
# 查看学生详情
obj = self.get_obj(pk)
data = self.serialize(obj)
return JsonResponse(data)
# 1. 获取查询参数
query_params = {key: value for key, value in request.GET.items()}
# 2. 获取分页参数
page = int(query_params.pop('page', 1))
size = int(query_params.pop('size', 10))
# 3. 获取查询集
queryset = Student.objects.all()
for key, value in query_params.items():
try:
queryset = queryset.filter(**{key: value})
except:
pass
# 4. 分页处理
# 数据总条数
total_num = queryset.count()
# 总页数
total_page = math.ceil(total_num / size)
# 下一页
absolute_url = self.request.build_absolute_uri()
next_url = None
pre_url = None
if page < total_page:
if 'page' in absolute_url:
next_url = re.sub(r'page=\d*', 'page={}'.format(page+1), absolute_url)
else:
next_url = absolute_url + '&page={}'.format(page+1)
# 上一页
if page > 1:
if 'page' in absolute_url:
pre_url = re.sub(r'page=\d*', 'page={}'.format(page-1), absolute_url)
else:
pre_url = absolute_url + '&page={}'.format(page-1)
# 分页过滤
queryset = queryset[(page - 1) * size:page * size]
# 5.序列化
students = [
self.serialize(item) for item in queryset
]
data = {
'total': total_num,
'page': page,
'next_page': next_url,
'pre_page': pre_url,
'results': students
}
# 6. 返回响应
return JsonResponse(data)
def post(self, request):
# 1.接受参数
create_data = json.loads(request.body)
# 2.实例化表达
form = StudentForm(create_data)
# 3.校验
if form.is_valid():
instance = form.save()
# 4.序列化
data = self.serialize(instance)
return JsonResponse(data, status=201)
else:
# 5.错误信息
data = {'errors': form.errors}
return JsonResponse(data, status=400)
def get_obj(self, pk):
obj = get_object_or_404(Student, pk=pk)
return obj
def put(self, request, pk):
# 1. 获取对象
obj = self.get_obj(pk)
# 2. 接收参数
update_data = json.loads(request.body)
# 2. 实例化表单
form = StudentForm(update_data, instance=obj)
# 3. 校验
if form.is_valid():
instance = form.save()
# 4. 序列化
data = self.serialize(instance)
return JsonResponse(data, status=200)
else:
# 5.错误信息
data = {'errors': form.errors}
return JsonResponse(data, status=400)
def delete(self, request, pk):
# 1. 获取对象
obj = self.get_obj(pk)
# 2. 删除对象
try:
obj.delete()
return HttpResponse(status=204)
except Exception as e:
return JsonResponse(data={'errors': str(e)}, status=400)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 路由
path('students/', views.StudentView.as_view(), name='student-list-create'),
path('students/<int:pk>/', views.StudentView.as_view(), name='student-retrieve-update-delete')
2
# 前端代码
# 列表页面
<!-- student_list_single.html -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap 101 Template</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://fastly.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div class="container" style="width: 1000px" id="app">
<h1>学生列表</h1>
<a class="btn btn-success" style="float: right" href="./student_detail_single.html">添加</a>
<table class="table table-hover table-bordered table-condensed" v-cloak>
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>phone</th>
<th>渠道</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(stu, index) in students">
<td>{{ index }}</td>
<td>{{ stu.name }}</td>
<td>{{ stu.sex }}</td>
<td>{{ stu.age }}</td>
<td>{{ stu.phone }}</td>
<td>{{ stu.channel }}</td>
<td>{{ stu.c_time }}</td>
<td style="text-align: center;padding: 0.75em 0"><a :href="'./student_detail_single.html?id='+stu.id"
class="btn btn-primary btn-sm">编辑</a>
<button class="btn btn-danger btn-sm" @click="delStudent(stu.id, index)">删除</button>
</td>
</tr>
</tbody>
</table>
<nav aria-label="...">
<ul class="pager">
<li class="previous" :class="{ disabled: !pre_url }"><a
href="{{ students.pre_url }}"><span
aria-hidden="true">←</span> 上一页</a></li>
<li class="next" :class="{ disabled: !next_url }"><a
href="{{ students.next_url }}">下一页 <span
aria-hidden="true">→</span></a>
</li>
</ul>
</nav>
</div>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://fastly.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"
integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
crossorigin="anonymous"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
let baseUrl = 'http://127.0.0.1:8000'
const app = new Vue({
el: '#app',
data: {
students: [],
next_url: null,
pre_url: null,
},
computed: {
},
methods: {
delStudent(sid, index) {
axios.delete(baseUrl + '/crm/students/' + sid + '/').then(res => {
this.students.splice(index, 1)
})
}
},
created() {
axios.get(baseUrl + '/crm/students/').then(res => {
this.students = res.data.results
this.next_url = res.data.next_url
this.pre_url = res.data.pre_url
})
}
})
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 详情页
<!-- student_detail_single.html -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Bootstrap 101 Template</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://fastly.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<div class="container" style="width: 800px" id="app" v-cloak>
<h1>{{ title }}</h1>
<form class="form-horizontal">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">姓名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="name" name="name" v-model="stu.name" placeholder="姓名">
</div>
</div>
<div class="form-group">
<label for="sex" class="col-sm-2 control-label">性别</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="sex" name="sex" v-model="stu.sex" placeholder="性别">
</div>
</div>
<div class="form-group">
<label for="age" class="col-sm-2 control-label">年龄</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="age" name="age" v-model="stu.age" placeholder="年龄">
</div>
</div>
<div class="form-group">
<label for="qq" class="col-sm-2 control-label">qq</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="qq" name="qq" v-model="stu.qq" placeholder="qq">
</div>
</div>
<div class="form-group">
<label for="phone" class="col-sm-2 control-label">手机号码</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="phone" name="phone" v-model="stu.phone"
placeholder="手机号码">
</div>
</div>
<div class="form-group">
<label for="channel" class="col-sm-2 control-label">渠道</label>
<div class="col-sm-10">
<select name="channel" id="channel" class="form-control" v-model="stu.channel">
<option value="">--------</option>
<option v-for="channel in channels" :value="channel.id">{{ channel.name }}</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-info float-right"
@click.prevent="btnSubmit(stu)">{{ btnName }}</button>
</div>
</div>
</form>
</div>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://fastly.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"
integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
crossorigin="anonymous"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
let baseUrl = 'http://127.0.0.1:8000'
const app = new Vue({
el: '#app',
data: {
title: '',
stu: {},
channels: [{name: '百度', id: 1}, {name: '抖音', id: 2}],
btnName: ''
},
methods: {
delStudent(sid, index) {
axios.delete(baseUrl + '/crm/students/' + sid + '/' ).then(res => {
this.students.splice(index, 1)
})
},
getParams() {
let query = window.location.search.substring(1)
let vars = query.split("&");
let data = {}
for (let v of vars) {
v = v.split('=');
data[v[0]] = v[1]
}
return data
},
saveStu(sid, stu){
axios.put(baseUrl + '/crm/students/' + sid + '/' , stu).then(res=>{
location.href = 'student_list_single.html'
})
},
createStu(stu){
axios.post( baseUrl + '/crm/students/', stu).then(res=>{
location.href = 'student_list_single.html'
})
},
btnSubmit(stu){
let sid = this.getParams().id;
if (sid){
this.saveStu(sid, stu)
}else {
this.createStu(stu)
}
}
},
created() {
let sid = this.getParams().id;
if (sid) {
this.title = '学生详情';
this.btnName = '保存';
axios.get(baseUrl + '/crm/students/' + sid + '/' ).then(res => {
this.stu = res.data
})
} else {
this.title = '添加学生';
this.btnName = '添加'
}
}
})
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# CORS
后端服务写好后,通过postman可以正常访问,但通过浏览器时,发送ajax请求回报CORS
错误。
要理解CORS需要先了解几个概念
# 同源策略
同源策略是一个重要的安全策略,它是浏览器最核心最基本的安全功能。
它限制web应用程序只能从加载应用程序的同一个域请求HTTP资源。
当向不同的域请求HTTP资源时就发生了跨域,默认请情况下浏览器会阻止跨域的请求。
那如何判断是否同源呢?
如果两个URL的协议,端口和主机都相同的话,则这两个URL是同源。
例如以下所有资源都具有相同的来源:
http://example.com/
http://example.com:80/
http://example.com/path/file
2
3
每个url都有相同的协议,主机和端口号。
而以下每个资源都与其他不同源:
http://example.com/
http://example.com:8080/
http://www.example.com/
https://example.com:80/
https://example.com/
http://example.org/
http://ietf.org/
2
3
4
5
6
7
所以所谓的同源策略简单的理解就是,打开某个页面后,这个页面上的ajax请求默认只能向和页面同源的url发送http请求。
同源策略固然保证了安全,但同时也限制了应用的灵活性,所以出现了CORS.
# 什么是CORS
CORS是一个W3C标准,全称是"跨域资源共享"(cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于前端开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
# CORS原理
# 跨域请求
浏览器将跨域请求分为两类:简单请求和非简单请求。
只要同时满足一下两个条件,就属于简单请求:
- 请求方法是一下三种方法之一:
- head
- get
- post
- http请求的头信息不超出以下几种字段:
- accept
- accept-language
- content-language
- Last-Event-ID
- Content-Type的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理是不一样的。
# 简单请求CORS步骤
对于简单请求CORS的基本流程如下:
# 第一步:客户端(浏览器)请求
当浏览器发出跨域请求时,该浏览器会添加一个包含当前源(协议,主机和端口)的Origin
头。
# 第二步:服务器响应
在服务器,当服务器看到Origin
头并想要允许访问时,就需要在响应中加入一个Access-Control-Allow-Origin
响应头来指定请求源(例如加入*
表示允许任何源)
# 第三步:浏览器接受响应
当浏览器看到带有相应Access-Control-Allow-Origin
响应头的响应时,即允许与客户端网站共享响应数据。否则抛出CORS异常。
注意同源策略只是浏览器遵守的规则,使用别的工具进行请求不会遵循同源策略的影响。
# 复杂请求CORS步骤
# 第一步:发送预检请求
浏览器会根据需要创建预检请求。该请求是一个options
请求,会在实际请求消息之前被发送。
预检请求中关键请求头是origin
表示请求来自哪个源。除了origin
字段,预检请求头的信息还包含两个特殊字段。
access-control-request-method
该字段是必须的,用来列出接下来的CORS请求会用到哪些HTTP方法,上面图片中的是PATCH
access-control-request-headers
这个字段是一个逗号分隔的字符串,指定接下来的CORS请求还会携带哪些额外的字段,上面图片中的是content-type
# 第二步:响应预检请求
服务器收到预检请求后,检查origin
,access-control-request-method
,access-control-request-headers
字段后,就可以返回响应。
响应中的access-control-allow-origin
字段表示允许跨域的源,*表示允许任意跨域请求。其他CORS相关响应头如下:
Access-Control-Allow-Methods
逗号分隔的一个字符串,表明服务器允许的跨域请求方法
Access-Control-Allow-Headers
逗号分隔的一个字符串,表明服务器支持的头字段
Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上图中的有效期是一天(86400秒),在此期间不用发出另一条预检请求。
注意:如果服务器否定了预检请求,也会返回一个正常的HTTP响应,但是不包含任何CORS相关的响应头。
# 第三步:发送跨域请求
一旦服务器通过了预检请求,以后每次浏览器正常的CORS请求,就跟简单请求一样,会有一个origin
头字段。服务器的回应,也会有一个access-control-allow-origin
头信息字段。
# cookie跨域
出于隐私原因,CORS请求默认不带cookie。如果想要在使用CORS时发送cookie,就需要发送请求时携带cookie并且服务器也同意。
# 请求
ajax请求需要打开withCredentials
属性才可以携带cookie:
const request = axios.create({
baseURL: 'http://127.0.0.1:8000',
timeout: 5000,
withCredentials: true // 设置为true 跨域时会携带cookie
})
2
3
4
5
# 响应
如果要接受cookie跨域,access-control-allow-origin
就不能设置为星号,必须指定明确,并且响应头中必须包含字段Access-Control-Allow-Credentials
,值为true
。
同时cookie依然遵循同源策略,只有服务器指明的域名的cookie才会上传。
# django-cors-headers
在django项目中要实现CORS可以手写(重复造轮子),也可以使用成熟插件(推荐)。
django-cors-headers是一个处理跨域资源共享(CORS)所需服务器器头信息的django应用。
它的使用非常简单:
# 安装
pip install django-cors-headers
# 添加到apps
INSTALLED_APPS = [
...,
"corsheaders",
...,
]
2
3
4
5
# 设置中间件
Django-cors-headers
是通过中间件实现cors头设置的,所以需要设置对应的中间件
MIDDLEWARE = [
...,
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
...,
]
2
3
4
5
6
CorsMiddleware应该放在尽可能高的位置,特别是在任何可以生成响应的中间件之前,比如django的CommonMiddleware
。否则无法将CORS头添加到这些响应中。
# 配置
要使用CORS,还需要在settings.py
模块中添加如下配置:
# CORS设置
# 允许跨域的域名列表
CORS_ALLOWED_ORIGINS = [
'http://localhost:8080'
]
CORS_ALLOW_ALL_ORIGINS = True # 表示y
# 允许cookies跨域
CORS_ALLOW_CREDENTIALS = True
2
3
4
5
6
7
8
更多配置详见https://pypi.org/project/django-cors-headers/
本文完,感谢你的耐心阅读,如有需要可加我微信,备注「博客」并说明原因,我们一起进步,下次见。
