專注於軟件測試開發領域 開源技術,工具,經驗,落地實踐,解決方案分享。
學習全文大概需要 12分鍾,內容實戰性較強。
1. 前言
本篇將基於Python 3.7+Django 3.0結合Vue.js前端框架,為大家介紹如何基於這三者的技術棧來實現一個前端后離的Web開發項目。為了簡化,方便讀者理解,本文將以開發一個單體頁面應用作為實戰演示。
2. 先搞清楚什么是前后端分離
在正式開始實戰示例之前,我們有必要先弄清楚一個概念:什么是前后端分離?
前后端分離目前已成為互聯網項目開發的業界標准使用方式,在聊前后端分離之前,相信也有很多讀者,對如何區分前端還是后端,還搞不清楚(是不是讓我戳中了你的痛處了)。本着“致良知”,先科譜一下知識。
通常情況下,我們說的前端,大多是指瀏覽器這一端,一般是用Html+CSS+JS來實現的,所以通常會引申為用Html+CSS+JS寫的大部分程序都是前端,包括App,小程序,H5等。
PS: 在NodeJS出現之后,用NodeJS寫的后端部分,也會被人歸類為前端,為了區分之前的前端,就給他們起了一個名字,叫做“大前端”。
久而久之,人們習慣把Html+CSS+JS,運行在瀏覽器端執行的,稱之為前端。
而Java,C,Python,PHP這些運行在服務器端的,統一稱之為后端。
但,這種以語言為分界點去區分前后端,真的合理么?顯然不合理!
前后端的定義,不應該是以語言來定義,而是應該以它的運行環境,如果是在服務器端,就應該被稱之為后端,代表着你看不見,摸不着。而如果運行在用戶端,就應該被稱之為前端,代表你是可以看得到的。
在不分前后端的時候,無論是Java還是JS,全都是一個人來寫。
為什么現在很多互聯網公司在項目開發時,都建議要進行前后端分離,或者說前后端分離能帶來哪些優勢?(好處多多,這里僅提兩個點)
-
第一個,並行開發、獨立部署、實現前后端解耦,前后端的進度互不影響,在過去,前后端不分離的情況下,項目代碼耦合嚴重相互影響,且前后端人員工作量分布不均。
-
第二個,術業有專攻(開發人員分離),以前的JavaWeb項目大多數都是Java程序員又當爹又當媽,又搞前端,又搞后端。前后端分離之后,前端工程師只管前端的事情,后端工程師只管后端的事情。
我們先看看一個 Web 系統,在前后端不分離時架構設計是什么樣的。

用戶在瀏覽器上發送請求,服務器端接收到請求,根據 Header 中的 token 進行用戶鑒權,從數據庫取出數據,處理后將結果數據填入 HTML 模板,返回給瀏覽器,瀏覽器將 HTML 展現給用戶。
而采用前后端分離之后,分離的是人員職責,人員職責分離了,因此架構也發生變化。

前后端分離后,前端人員和后端人員約定好接口,前端人員不用再關心業務處理是怎么回事,他只需要把界面做好就可以了,后端人員也不用再關系前端界面是什么樣的,他只需要做好業務邏輯處理即可。
小結一下,前后端分離是什么?
前后端分離是一種架構模式,或者說是最佳實踐,它主張將前端開發人員和后端開發人員的工作進行解耦,盡量減少他她們之間的交流成本,幫助他她們更能專注於自己擅長的工作。
PS: 本篇實戰示例,使用Vue.js作為前端框架,代替Django本身自帶的模板引擎,Django則作為服務端提供API接口,從而實現前后端分離。
3. 環境准備
本實戰示例,基礎環境對應安裝版本如下:
-
Python 3.7.4
-
Mysql 5.7
-
Pycharm (建議專業版)
-
Node
PS: 其中Python、Mysql、Pycharm、Node安裝過程皆較為簡單,不是本文介紹重點,讀者可直接參考官網安裝方法。
4. 新建獨立的虛擬開發環境
1、創建一個用於Django項目開發的獨立虛擬環境,切換到本地開發目錄,輸入如下命令:
python3 -m venv venv
2、創建完成后,目錄結構如下:
➜ 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.cfg4 directories, 11 files
3、進入到bin目錄,輸入命令source activate 命令,激活虛擬環境。

4、虛擬環境激活后,如上圖所示。接下來,在虛擬環境安裝Django庫。
安裝Django (最新版本為3.0)
(venv) ➜ pip install Django
Django 項目源碼:
https://github.com/django/django
Django3.0 版本特性可查閱官網:
https://docs.djangoproject.com/en/3.0/releases/3.0/
5、安裝完成后,可檢查一下版本信息:
(venv) ➜ pythonPython 3.7.4 (default, Jul 9 2019, 18:15:00)[Clang 10.0.0 (clang-1000.11.45.5)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> import django>>> print(django.get_version())3.0
可以發現,在虛擬環境中已經成功安裝好了Django 3.0。
5. 創建Django后端項目
1、創建Django項目,采用Pycharm或者命令行創建皆可。此處,以命令行方式作為演示,項目名為django_vue。
(venv) ➜ django-admin startproject django_vue
2. Django項目創建完成后,目錄結構如下所示。
├── django_vue│ ├── django_vue│ │ ├── __init__.py│ │ ├── asgi.py│ │ ├── settings.py│ │ ├── urls.py│ │ └── wsgi.py│ └── manage.py
3、執行同步數據庫文件(Django默認數據庫為db.sqlite3),執行同步過程如下:
(venv) ➜ python manage.py migrateOperations to perform:Apply all migrations: admin, auth, contenttypes, sessionsRunning migrations:Applying contenttypes.0001_initial... OKApplying auth.0001_initial... OKApplying admin.0001_initial... OKApplying admin.0002_logentry_remove_auto_add... OKApplying admin.0003_logentry_add_action_flag_choices... OKApplying contenttypes.0002_remove_content_type_name... OKApplying auth.0002_alter_permission_name_max_length... OKApplying auth.0003_alter_user_email_max_length... OKApplying auth.0004_alter_user_username_opts... OKApplying auth.0005_alter_user_last_login_null... OKApplying auth.0006_require_contenttypes_0002... OKApplying auth.0007_alter_validators_add_error_messages... OKApplying auth.0008_alter_user_username_max_length... OKApplying auth.0009_alter_user_last_name_max_length... OKApplying auth.0010_alter_group_name_max_length... OKApplying auth.0011_update_proxy_permissions... OKApplying sessions.0001_initial... OK
4、啟動Django Server ,驗證默認配置是否正常。
(venv) ➜ python manage.py runserver 0.0.0.0:8000Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).December 15, 2019 - 08:36:28Django 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,一切正常的話,可見到如下界面。

6. 將Django數據庫更換為Mysql
1、假設在前面,我們已經安裝配置好了Mysql,輸入如下命令進入到Mysql。
mysql -u root -p
2、創建數據庫,數據庫取名為django_vue_db,並設置字符集為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數據庫引擎。
```pythonDATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'django_vue_db','USER': 'root','PASSWORD': 'xxxxxxx','HOST': '127.0.0.1',}}```
5、執行同步操作,將數據遷移到Mysql。
python manage.py migrate
6、驗證是否切庫成功,進入到Mysql客戶端,查看django初化表是否有生成。
mysql> use django_vue_db;Database changedmysql> 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、運行Django Server,重新訪問http://localhost:8000。
python manage.py runserver 0.0.0.0:8000
如果能正常訪問,過程沒有報錯,說明切換數據庫已經成功了。
7. 創建Django實戰項目App
1、創建Django App,進入django_vue項目主目錄,輸入如下命令:
(venv) ➜ python manage.py startapp api_test
2、App創建完成后,目錄結構如下所示:
├── api_test│ ├── __init__.py│ ├── admin.py│ ├── apps.py│ ├── migrations│ │ └── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py
並把api_test加入到settings文件中的installed_apps列表里:
```pythonINSTALLED_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如下:
```python# -*- coding: utf-8 -*-from __future__ import unicode_literalsfrom django.db import modelsclass Book(models.Model):book_name = models.CharField(max_length=128)add_time = models.DateTimeField(auto_now_add=True)def __unicode__(self):return self.book_name```
只有兩個字段,書名book_name和添加時間add_time。如果沒有指定主鍵的話Django會自動新增一個自增id作為主鍵。
4、在api_test目錄下的views里我們新增兩個接口,一個是show_books返回所有的書籍列表(通過JsonResponse返回能被前端識別的json格式數據),二是add_book接受一個get請求,往數據庫里添加一條book數據。
```pythonfrom django.shortcuts import renderfrom django.views.decorators.http import require_http_methodsfrom django.core import serializersfrom django.http import JsonResponseimport jsonfrom .models import Bookdef add_book(request):response = {}try:book = Book(book_name=request.GET.get('book_name'))book.save()response['msg'] = 'success'response['error_num'] = 0except Exception as e:response['msg'] = str(e)response['error_num'] = 1return JsonResponse(response)def show_books(request):response = {}try:books = Book.objects.filter()response['list'] = json.loads(serializers.serialize("json", books))response['msg'] = 'success'response['error_num'] = 0except Exception as e:response['msg'] = str(e)response['error_num'] = 1return JsonResponse(response)```
可以看出,在ORM的幫忙下,我們的接口實際上不需要自己去組織SQL代碼。
5、在api_test目錄下,新增一個urls.py文件,把我們新增的兩個接口添加到路由里:
from django.conf.urls import url, includefrom .views import *urlpatterns = [url(r'add_book$', add_book, ),url(r'show_books$', show_books, ),]```
6、我們還要把api_test下的urls添加到項目django_vue下的urls中,才能完成路由:
```pythonfrom django.contrib import adminfrom django.urls import pathfrom django.conf.urls import url, includefrom django.contrib import adminfrom django.views.generic import TemplateViewimport api_test.urlsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^api/', include(api_test.urls)),]```
7、在項目的根目錄,輸入命令:
python manage.py makemigrations api_testpython manage.py migrate
8、查詢數據庫,看到book表已經自動創建了:
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、在項目的根目錄,輸入命令:
python manage.py runserver 0.0.0.0:800
啟動服務,通過httpie測試一下我們剛才寫的兩個接口。
10、通過調用接口向Django App中添加兩條書名記錄。
```shell➜ http http://127.0.0.1:8000/api/add_book\?book_name\=mikezhou_talkHTTP/1.1 200 OKContent-Length: 34Content-Type: application/jsonDate: Sun, 15 Dec 2019 09:11:12 GMTServer: WSGIServer/0.2 CPython/3.7.4X-Content-Type-Options: nosniffX-Frame-Options: DENY{"error_num": 0,"msg": "success"}➜ http http://127.0.0.1:8000/api/add_book\?book_name\=測試開發技術HTTP/1.1 200 OKContent-Length: 34Content-Type: application/jsonDate: Sun, 15 Dec 2019 09:11:44 GMTServer: WSGIServer/0.2 CPython/3.7.4X-Content-Type-Options: nosniffX-Frame-Options: DENY{"error_num": 0,"msg": "success"}```
11、通過調用接口,顯示Django App中所有書名列表:
```shell➜ http http://127.0.0.1:8000/api/show_booksHTTP/1.1 200 OKContent-Length: 305Content-Type: application/jsonDate: Sun, 15 Dec 2019 09:13:48 GMTServer: WSGIServer/0.2 CPython/3.7.4X-Content-Type-Options: nosniffX-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": "測試開發技術"},"model": "api_test.book","pk": 2}],"msg": "success"}```
8. 新建Vue.js前端項目
1、有關Vue的模塊(包括vue)可以使用node自帶的npm包管理器安裝。推薦使用淘寶的 cnpm 命令行工具代替默認的 npm。
npm install -g cnpm --registry=https://registry.npm.taobao.org
2、先用cnpm安裝vue-cli腳手架工具(vue-cli是官方腳手架工具,能迅速幫你搭建起vue項目的框架):
cnpm install -g vue-cli
3、安裝好后,在django_vue項目根目錄下,新建一個前端工程目錄:
vue-init webpack frontend
在創建項目的過程中會彈出一些與項目相關的選項需要回答,按照真實情況進行輸入即可。
4、安裝 vue 依賴模塊
cd frontendcnpm installcnpm install vue-resourcecnpm install element-ui
5、現在我們可以看到整個文件目錄結構是這樣的:

本文為了讀者方便查看,是直接將vue前端工程放在django項目目錄下,實際多人協作開發過程中,完全是可以放在不同代碼倉庫下面的。
6、在frontend目錄src下包含入口文件main.js,入口組件App.vue等。后綴為vue的文件是Vue.js框架定義的單文件組件,其中標簽中的內容可以理解為是類html的頁面結構內容。
7、在src/component文件夾下新建一個名為Home.vue的組件,通過調用之前在Django上寫好的api,實現添加書籍和展示書籍信息的功能。在樣式組件上我們使用了餓了么團隊推出的element-ui,這是一套專門匹配Vue.js框架的功能樣式組件。由於組件的編碼涉及到了很多js、html、css的知識,並不是本文的重點,因此在此只貼出部分代碼:
Home.vue文件代碼:
```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="添加時間" 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路由中:
```jsimport Vue from 'vue'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文件中,導入element-ui、vue-resource庫。
```jsimport 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、如果出現跨域問題,需要在Django層注入header,用Django的第三方包django-cors-headers來解決跨域問題:
pip install django-cors-headers
settings.py 修改:
```pythonMIDDLEWARE = ['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自帶的服務器,瀏覽器會自動打開, 我們能看到頁面:

13、嘗試新增書籍,如填入:“自動化測試實戰寶典”,新增的書籍信息會實時反映到頁面的列表中,這得益於Vue.js的數據雙向綁定特性。

14、在前端工程frontend目錄下,輸入npm run build,如果項目沒有錯誤的話,就能夠看到所有的組件、css、圖片等都被webpack自動打包到dist目錄下了:

9. 整合Django和Vue.js前端
目前我們已經分別完成了Django后端和Vue.js前端工程的創建和編寫,但實際上它們是運行在各自的服務器上,和我們的要求是不一致的。因此我們須要把Django的TemplateView指向我們剛才生成的前端dist文件即可。
1、 找到project目錄的urls.py,使用通用視圖創建最簡單的模板控制器,訪問 『/』時直接返回 index.html:
```pythonurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^api/', include(api_test.urls)),url(r'^$', TemplateView.as_view(template_name="index.html")),]```
2、上一步使用了Django的模板系統,所以需要配置一下模板使Django知道從哪里找到index.html。在project目錄的settings.py下:
```pythonTEMPLATES = [{'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、 我們還需要配置一下靜態文件的搜索路徑。同樣是project目錄的settings.py下:
```python# Add for vuejsSTATICFILES_DIRS = [os.path.join(BASE_DIR, "frontend/dist/static"),]```
4、 配置完成,我們在project目錄下輸入命令python manage.py runserver,就能夠看到我們的前端頁面在瀏覽器上展現:

注意此時服務的端口已經是Django服務的8000而不是node服務的8080了,說明我們已經成功通過Django集成了Vue前端工程。
該實戰示例為大家充分展示了現在主流的前后端分離方式,由前端框架,如Vue.js來構建實現前端界面,再通過后端框架,如Django來實現API數據提供,兩者通過接口進行通訊、相輔相成、最終實現一個完整Web項目
