Django結(jié)合Vue.js框架構(gòu)建前后端分離
一宴抚、先搞清楚什么是前后端分離
前后端分離能帶來哪些優(yōu)勢袄膏?(好處多多践图,這里僅提兩個點(diǎn))
第一個,并行開發(fā)沉馆、獨(dú)立部署码党、實(shí)現(xiàn)前后端解
耦
,前后端的進(jìn)度互不影響斥黑,在過去揖盘,前后端不分離的情況下,項(xiàng)目代碼耦合嚴(yán)重相互影響心赶,且前后端人員工作量分布不均扣讼。第二個,術(shù)業(yè)有專攻(開發(fā)人員分離)缨叫,以前的JavaWeb項(xiàng)目大多數(shù)都是Java程序員又當(dāng)?shù)之?dāng)媽椭符,又搞前端,又搞后端耻姥。前后端分離之后销钝,前端工程師只管前端的事情,后端工程師只管后端的事情琐簇。
我們先看看一個 Web 系統(tǒng)蒸健,在前后端不分離時(shí)架構(gòu)設(shè)計(jì)是什么樣的。
用戶在瀏覽器上發(fā)送請求婉商,服務(wù)器端接收到請求似忧,根據(jù) Header 中的 token 進(jìn)行用戶鑒權(quán),從數(shù)據(jù)庫取出數(shù)據(jù)丈秩,處理后將結(jié)果數(shù)據(jù)填入 HTML 模板盯捌,返回給瀏覽器,瀏覽器將 HTML 展現(xiàn)給用戶蘑秽。
而采用前后端分離之后饺著,分離的是人員職責(zé),人員職責(zé)分離了肠牲,因此架構(gòu)也發(fā)生變化幼衰。
前后端分離后,前端人員和后端人員約定好接口缀雳,前端人員不用再關(guān)心業(yè)務(wù)處理是怎么回事渡嚣,他只需要把界面做好就可以了,后端人員也不用再關(guān)系前端界面是什么樣的,他只需要做好業(yè)務(wù)邏輯處理即可识椰。
小結(jié)一下扬绪,前后端分離是什么?
前后端分離是一種架構(gòu)模式裤唠,或者說是最佳實(shí)踐挤牛,它主張將前端開發(fā)人員和后端開發(fā)人員的工作進(jìn)行解耦,盡量減少他她們之間的交流成本种蘸,幫助他她們更能專注于自己擅長的工作墓赴。
PS: 本篇實(shí)戰(zhàn)示例,使用Vue.js作為前端框架航瞭,代替Django本身自帶的模板引擎诫硕,Django則作為服務(wù)端提供API接口,從而實(shí)現(xiàn)前后端分離刊侯。
二章办、環(huán)境準(zhǔn)備
本實(shí)戰(zhàn)示例,基礎(chǔ)環(huán)境對應(yīng)安裝版本如下:
- Python 3.7.4
- Mysql 5.7
- Pycharm (建議專業(yè)版)
- Node
三. 新建獨(dú)立的虛擬開發(fā)環(huán)境
1滨彻、創(chuàng)建一個用于Django項(xiàng)目開發(fā)的獨(dú)立虛擬環(huán)境藕届,切換到本地開發(fā)目錄,輸入如下命令:
python3 -m venv venv
2亭饵、創(chuàng)建完成后休偶,目錄結(jié)構(gòu)如下:
? venv tree -L 2
.
├── bin
│ ├── activate
│ ├── activate.csh
│ ├── activate.fish
│ ├── easy_install
│ ├── easy_install-3.7
│ ├── pip
│ ├── pip3
│ ├── pip3.7
│ ├── python -> python3
│ └── python3 -> /usr/local/bin/python3
├── include
├── lib
│ └── python3.7
└── pyvenv.cfg
4 directories, 11 files
3、進(jìn)入到bin目錄辜羊,輸入命令source activate 命令踏兜,激活虛擬環(huán)境。
4八秃、虛擬環(huán)境激活后碱妆,如上圖所示。接下來昔驱,在虛擬環(huán)境安裝Django庫疹尾。
安裝Django (最新版本為3.0)
(venv) ? pip install Django
5、安裝完成后舍悯,可檢查一下版本信息:
(venv) ? python
Python 3.7.4 (default, Jul 9 2019, 18:15:00)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> print(django.get_version())
3.0
可以發(fā)現(xiàn)航棱,在虛擬環(huán)境中已經(jīng)成功安裝好了Django 3.0睡雇。
四萌衬、創(chuàng)建Django后端項(xiàng)目
1、創(chuàng)建Django項(xiàng)目它抱,采用Pycharm或者命令行創(chuàng)建皆可秕豫。此處,以命令行方式作為演示,項(xiàng)目名為django_vue混移。
(venv) ? django-admin startproject django_vue
2. Django項(xiàng)目創(chuàng)建完成后祠墅,目錄結(jié)構(gòu)如下所示。
├── django_vue
│ ├── django_vue
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ └── manage.py
3歌径、執(zhí)行同步數(shù)據(jù)庫文件(Django默認(rèn)數(shù)據(jù)庫為db.sqlite3)毁嗦,執(zhí)行同步過程如下:
(venv) ? python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying sessions.0001_initial... OK
4、啟動Django Server 回铛,驗(yàn)證默認(rèn)配置是否正常狗准。
(venv) ? python manage.py runserver 0.0.0.0:8000
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
December 15, 2019 - 08:36:28
Django version 3.0, using settings 'django_vue.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
5、打開瀏覽器茵肃,訪問http://localhost:8000腔长,一切正常的話,可見到如下界面验残。
五捞附、將Django數(shù)據(jù)庫更換為Mysql
1、假設(shè)在前面您没,我們已經(jīng)安裝配置好了Mysql鸟召,輸入如下命令進(jìn)入到Mysql。
mysql -u root -p
2氨鹏、創(chuàng)建數(shù)據(jù)庫药版,數(shù)據(jù)庫取名為django_vue_db,并設(shè)置字符集為utf-8喻犁。
mysql> CREATE DATABASE django_vue_db CHARACTER SET utf8;
Query OK, 1 row affected (0.01 sec)
3槽片、安裝myslqclient庫
(venv) ? pip install mysqlclient
4、配置settings.py文件肢础,配置Mysql數(shù)據(jù)庫引擎还栓。
?```python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_vue_db',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
}
}
5、執(zhí)行同步操作传轰,將數(shù)據(jù)遷移到Mysql剩盒。
python manage.py migrate
6、驗(yàn)證是否切庫成功慨蛙,進(jìn)入到Mysql客戶端辽聊,查看django初化表是否有生成。
mysql> use django_vue_db;
Database changed
mysql> show tables;
+----------------------------+
| Tables_in_django_vue_db |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
10 rows in set (0.00 sec)
7期贫、運(yùn)行Django Server跟匆,重新訪問http://localhost:8000。
python manage.py runserver 0.0.0.0:8000
如果能正常訪問通砍,過程沒有報(bào)錯玛臂,說明切換數(shù)據(jù)庫已經(jīng)成功了烤蜕。
六、創(chuàng)建Django實(shí)戰(zhàn)項(xiàng)目App
1迹冤、創(chuàng)建Django App讽营,進(jìn)入django_vue項(xiàng)目主目錄,輸入如下命令:
(venv) ? python manage.py startapp api_test
2泡徙、App創(chuàng)建完成后橱鹏,目錄結(jié)構(gòu)如下所示:
├── api_test
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
并把a(bǔ)pi_test加入到settings文件中的installed_apps列表里:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api_test',
]
3、 在api_test目錄下的models.py里我們簡單寫一個model如下:
# -*- coding: utf-8 -*-
from django.db import models
class Book(models.Model):
book_name = models.CharField(max_length=128)
add_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.book_name
只有兩個字段堪藐,書名book_name和添加時(shí)間add_time蚀瘸。如果沒有指定主鍵的話Django會自動新增一個自增id作為主鍵。
4庶橱、在api_test目錄下的views里我們新增兩個接口贮勃,一個是show_books返回所有的書籍列表(通過JsonResponse返回能被前端識別的json格式數(shù)據(jù)),二是add_book接受一個get請求苏章,往數(shù)據(jù)庫里添加一條book數(shù)據(jù)寂嘉。
from django.shortcuts import render
from django.views.decorators.http import require_http_methods
from django.core import serializers
from django.http import JsonResponse
import json
from .models import Book
@require_http_methods(["GET"])
def add_book(request):
response = {}
try:
book = Book(book_name=request.GET.get('book_name'))
book.save()
response['msg'] = 'success'
response['error_num'] = 0
except Exception as e:
response['msg'] = str(e)
response['error_num'] = 1
return JsonResponse(response)
@require_http_methods(["GET"])
def show_books(request):
response = {}
try:
books = Book.objects.filter()
response['list'] = json.loads(serializers.serialize("json", books))
response['msg'] = 'success'
response['error_num'] = 0
except Exception as e:
response['msg'] = str(e)
response['error_num'] = 1
return JsonResponse(response)
可以看出,在ORM的幫忙下枫绅,我們的接口實(shí)際上不需要自己去組織SQL代碼泉孩。
5、在api_test目錄下并淋,新增一個urls.py文件寓搬,把我們新增的兩個接口添加到路由里:
from django.conf.urls import url, include
from .views import *
urlpatterns = [
url(r'add_book$', add_book, ),
url(r'show_books$', show_books, ),
]
6、我們還要把a(bǔ)pi_test下的urls添加到項(xiàng)目django_vue下的urls中县耽,才能完成路由:
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from django.contrib import admin
from django.views.generic import TemplateView
import api_test.urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(api_test.urls)),
]
7句喷、在項(xiàng)目的根目錄,輸入命令:
python manage.py makemigrations api_test
python manage.py migrate
8兔毙、查詢數(shù)據(jù)庫唾琼,看到book表已經(jīng)自動創(chuàng)建了:
mysql> show tables;
+----------------------------+
| Tables_in_django_vue_db |
+----------------------------+
| api_test_book |
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
11 rows in set (0.00 sec)
mysql> desc api_test_book;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| book_name | varchar(128) | NO | | NULL | |
| add_time | datetime(6) | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql>
Django生成的表名將以app名加上model中的類名組合而成。
9澎剥、在項(xiàng)目的根目錄锡溯,輸入命令:
python manage.py runserver 0.0.0.0:8000
啟動服務(wù),通過httpie測試一下我們剛才寫的兩個接口哑姚。
10祭饭、通過調(diào)用接口向Django App中添加兩條書名記錄。
? http http://127.0.0.1:8000/api/add_book\?book_name\=mikezhou_talk
HTTP/1.1 200 OK
Content-Length: 34
Content-Type: application/json
Date: Sun, 15 Dec 2019 09:11:12 GMT
Server: WSGIServer/0.2 CPython/3.7.4
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"error_num": 0,
"msg": "success"
}
? http http://127.0.0.1:8000/api/add_book\?book_name\=測試開發(fā)技術(shù)
HTTP/1.1 200 OK
Content-Length: 34
Content-Type: application/json
Date: Sun, 15 Dec 2019 09:11:44 GMT
Server: WSGIServer/0.2 CPython/3.7.4
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"error_num": 0,
"msg": "success"
}
11叙量、通過調(diào)用接口倡蝙,顯示Django App中所有書名列表:
? http http://127.0.0.1:8000/api/show_books
HTTP/1.1 200 OK
Content-Length: 305
Content-Type: application/json
Date: Sun, 15 Dec 2019 09:13:48 GMT
Server: WSGIServer/0.2 CPython/3.7.4
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"error_num": 0,
"list": [
{
"fields": {
"add_time": "2019-12-15T09:11:12.673Z",
"book_name": "mikezhou_talk"
},
"model": "api_test.book",
"pk": 1
},
{
"fields": {
"add_time": "2019-12-15T09:11:44.305Z",
"book_name": "測試開發(fā)技術(shù)"
},
"model": "api_test.book",
"pk": 2
}
],
"msg": "success"
}
七、新建Vue.js前端項(xiàng)目
1宛乃、有關(guān)Vue的模塊(包括vue)可以使用node自帶的npm包管理器安裝悠咱。推薦使用淘寶的 cnpm 命令行工具代替默認(rèn)的 npm。
npm install -g cnpm --registry=https://registry.npm.taobao.org
2征炼、先用cnpm安裝vue-cli腳手架工具(vue-cli是官方腳手架工具析既,能迅速幫你搭建起vue項(xiàng)目的框架):
cnpm install -g vue-cli
3、安裝好后谆奥,在django_vue項(xiàng)目根目錄下眼坏,新建一個前端工程目錄:
vue-init webpack frontend
在創(chuàng)建項(xiàng)目的過程中會彈出一些與項(xiàng)目相關(guān)的選項(xiàng)需要回答,按照真實(shí)情況進(jìn)行輸入即可酸些。
4宰译、安裝 vue 依賴模塊
cd frontend
cnpm install
cnpm install vue-resource
cnpm install element-ui
5、現(xiàn)在我們可以看到整個文件目錄結(jié)構(gòu)是這樣的:
本文為了讀者方便查看魄懂,是直接將vue前端工程放在django項(xiàng)目目錄下沿侈,實(shí)際多人協(xié)作開發(fā)過程中,完全是可以放在不同代碼倉庫下面的市栗。
6缀拭、在frontend目錄src下包含入口文件main.js,入口組件App.vue等填帽。后綴為vue的文件是Vue.js框架定義的單文件組件蛛淋,其中標(biāo)簽中的內(nèi)容可以理解為是類html的頁面結(jié)構(gòu)內(nèi)容。
7篡腌、在src/component文件夾下新建一個名為Home.vue的組件褐荷,通過調(diào)用之前在Django上寫好的api,實(shí)現(xiàn)添加書籍和展示書籍信息的功能嘹悼。在樣式組件上我們使用了餓了么團(tuán)隊(duì)推出的element-ui叛甫,這是一套專門匹配Vue.js框架的功能樣式組件。由于組件的編碼涉及到了很多js杨伙、html合溺、css的知識,并不是本文的重點(diǎn)缀台,因此在此只貼出部分代碼:
Home.vue文件代碼:
<template>
<div class="home">
<el-row display="margin-top:10px">
<el-input v-model="input" placeholder="請輸入書名" style="display:inline-table; width: 30%; float:left"></el-input>
<el-button type="primary" @click="addBook()" style="float:left; margin: 2px;">新增</el-button>
</el-row>
<el-row>
<el-table :data="bookList" style="width: 100%" border>
<el-table-column prop="id" label="編號" min-width="100">
<template slot-scope="scope"> {{ scope.row.pk }} </template>
</el-table-column>
<el-table-column prop="book_name" label="書名" min-width="100">
<template slot-scope="scope"> {{ scope.row.fields.book_name }} </template>
</el-table-column>
<el-table-column prop="add_time" label="添加時(shí)間" min-width="100">
<template slot-scope="scope"> {{ scope.row.fields.add_time }} </template>
</el-table-column>
</el-table>
</el-row>
</div>
</template>
<script>
export default {
name: 'home',
data () {
return {
input: '',
bookList: []
}
},
mounted: function () {
this.showBooks()
},
methods: {
addBook () {
this.$http.get('http://127.0.0.1:8000/api/add_book?book_name=' + this.input)
.then((response) => {
var res = JSON.parse(response.bodyText)
if (res.error_num === 0) {
this.showBooks()
} else {
this.$message.error('新增書籍失敗棠赛,請重試')
console.log(res['msg'])
}
})
},
showBooks () {
this.$http.get('http://127.0.0.1:8000/api/show_books')
.then((response) => {
var res = JSON.parse(response.bodyText)
console.log(res)
if (res.error_num === 0) {
this.bookList = res['list']
} else {
this.$message.error('查詢書籍失敗')
console.log(res['msg'])
}
})
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
8、在src/router目錄的index.js中膛腐,我們把新建的Home組件睛约,配置到vue-router路由中:
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Home from '@/components/Home'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
}
]
})
9、在src/main.js文件中哲身,導(dǎo)入element-ui辩涝、vue-resource庫。
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import VueResource from 'vue-resource'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
Vue.use(VueResource)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
10勘天、如果出現(xiàn)跨域問題怔揩,需要在Django層注入header捉邢,用Django的第三方包django-cors-headers
來解決跨域問題:
pip install django-cors-headers
settings.py 修改:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True
PS: 注意中間件的添加順序。
12商膊、在前端工程frontend目錄下伏伐,輸入npm run dev
啟動node自帶的服務(wù)器,瀏覽器會自動打開晕拆, 我們能看到頁面:
13藐翎、嘗試新增書籍,如填入:“自動化測試實(shí)戰(zhàn)寶典”实幕,新增的書籍信息會實(shí)時(shí)反映到頁面的列表中吝镣,這得益于Vue.js的數(shù)據(jù)雙向綁定特性。
14昆庇、在前端工程frontend目錄下末贾,輸入npm run build
,如果項(xiàng)目沒有錯誤的話整吆,就能夠看到所有的組件未舟、css、圖片等都被webpack自動打包到dist目錄下了:
八掂为、整合Django和Vue.js前端
目前我們已經(jīng)分別完成了Django后端和Vue.js前端工程的創(chuàng)建和編寫裕膀,但實(shí)際上它們是運(yùn)行在各自的服務(wù)器上,和我們的要求是不一致的勇哗。因此我們須要把Django的`TemplateView指向我們剛才生成的前端dist文件即可昼扛。
1、 找到project目錄的urls.py欲诺,使用通用視圖創(chuàng)建最簡單的模板控制器抄谐,訪問 『/』時(shí)直接返回 index.html:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/', include(api_test.urls)), url(r'^$', TemplateView.as_view(template_name="index.html")),]```
``2、上一步使用了Django的模板系統(tǒng)扰法,所以需要配置一下模板使Django知道從哪里找到index.html蛹含。在project目錄的settings.py下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS':['frontend/dist'],
'APP_DIRS':True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
3、 我們還需要配置一下靜態(tài)文件的搜索路徑塞颁。同樣是project目錄的settings.py下:
# Add for vuejs
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/dist/static"),
]
4浦箱、 配置完成,我們在project目錄下輸入命令python manage.py runserver
祠锣,就能夠看到我們的前端頁面在瀏覽器上展現(xiàn):
注意此時(shí)服務(wù)的端口已經(jīng)是Django服務(wù)的8000而不是node服務(wù)的8080了酷窥,說明我們已經(jīng)成功通過Django集成了Vue前端工程。
該實(shí)戰(zhàn)示例為大家充分展示了現(xiàn)在主流的前后端分離方式伴网,由前端框架蓬推,如Vue.js來構(gòu)建實(shí)現(xiàn)前端界面,再通過后端框架澡腾,如Django來實(shí)現(xiàn)API數(shù)據(jù)提供沸伏,兩者通過接口進(jìn)行通訊糕珊、相輔相成、最終實(shí)現(xiàn)一個完整Web項(xiàng)目毅糟。