帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(4)

上一篇: 帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(3)
后臺數(shù)據(jù)模型設(shè)計
Demo: https://didi-taxi.herokuapp.com/

Django Channels

為了支持即時消息收發(fā)、群發(fā)群收稍浆、異步處理族铆,我們對后臺添加Channels。

  1. 安裝Channels:
    pip install channels channels-redis

  2. 修改配置文件:

  • 添加channels APP
  • 添加ASGI_APPLICATION
  • 添加CHANNEL_LAYERS,使用Redis作為后臺
# /backend/settings/dev.py
INSTALLED_APPS = [
    'channels',
。。语婴。

ASGI_APPLICATION = 'backend.routing.application'

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'dist', 'media')

REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': { 'hosts': [REDIS_URL]},
    }
}
  1. 創(chuàng)建ASGI application
    運(yùn)行django時,會切換到ASGI服務(wù)模式驶睦,同時處理HTTP和Websockets訪問砰左。
# /backend/asgi.py
import os

import django

from channels.routing import get_default_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings.dev')
django.setup()
application = get_default_application()
  1. 創(chuàng)建ASGI路由文件
    前端以/ws/taxi/地址來進(jìn)行WebSockets請求。
# backend/routing.py

from django.urls import path 
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter

from .api.consumers import TaxiConsumer

# changed
application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path('ws/taxi/', TaxiConsumer),
        ])
    )
})
  1. 創(chuàng)建Consumer
    Consumer是實(shí)現(xiàn)類似View的功能场航,處理WebSockets消息缠导。
# api/consumers.py

from channels.generic.websocket import AsyncJsonWebsocketConsumer

class TaxiConsumer(AsyncJsonWebsocketConsumer):

    async def connect(self):
        user = self.scope['user']
        if user.is_anonymous:
            await self.close()
        else:
            await self.accept()
            content = {
              'type': 'from Django',
              'data': "welcome, you're connected to Channels!"
            }
          await self.send_json(content)

拒絕非登錄用戶。對于已登錄用戶溉痢,則接收連接僻造,并返回歡迎信息。

目錄改動不少孩饼,當(dāng)前結(jié)構(gòu)為:

git/didi-project$ tree -I node_modules -L 3
.
├── LICENSE
├── Pipfile
├── Pipfile.lock
├── Procfile
├── README.md
├── app.json
├── backend
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── consumers.py
│   │   ├── migrations
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── asgi.py
│   ├── routing.py
│   ├── settings
│   │   ├── __init__.py
│   │   ├── dev.py
│   │   └── prod.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
├── dist
├── manage.py
├── package.json
├── public
│   ├── index.html
│   ├── manifest.json
│   ├── robots.txt
│   └── static
│       ├── favicon.ico
│       └── img
├── src
│   ├── App.vue
│   ├── assets
│   ├── components
│   ├── config.js
│   ├── main.js
│   ├── plugins
│   │   └── vuetify.js
│   ├── registerServiceWorker.js
│   ├── router.js
│   ├── services
│   │   ├── api.js
│   │   └── messageService.js
│   ├── store
│   │   ├── index.js
│   │   └── modules
│   │   │   └── messages.js
│   └── views
│       ├── Home.vue
│       ├── My404.vue
│       ├── Signin.vue
│       └── Signup.vue
├── vue.config.js
└── yarn.lock

運(yùn)行ASGI

啟動Redis服務(wù):redis-server&
運(yùn)行Django: python manage.py runserver

看到如下提示髓削,就說明ASGI服務(wù)成功啟動了:

(didi-project) git/didi-project$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 19, 2019 - 07:28:17
Django version 2.2.1, using settings 'backend.settings.dev'
Starting ASGI/Channels version 2.2.0 development server at http://127.0.0.1:8000/

驗證一下,之前的HTTP登錄镀娶、注冊等功能一切正常立膛。

前端添加WebSockets支持

直接寫到<script>里也可以,
const websocket = new WebSocket(...)
但我們可以將公用方法提煉出來梯码,跟axios ajax一樣宝泵,方便復(fù)用。

# /src/service/ws_api.js
// import http from '@/services/http'
import config from '@/config'
// import store from '@/store'

class Ws {
  constructor (path) {
    this.path = path
  }

  init () {
    this.websocket = new WebSocket(`${config.wsUrl}/ws/${this.path}/`)
    return this.websocket
  }
}

export default Ws

Vuex store里轩娶,新建一個module - ws.js儿奶,專門處理如下actions:

  • initWS
  • wsOnOpen
  • wsOnError
  • wsOnClose
  • wsOnMessage
    在建立、中斷WS時鳄抒,通過setAlert action提示
# /src/store/modules/ws.js
import Ws from '@/services/ws_api.js'

const state = {
  websocket: {
    ws: null,
    status: 'DISCONNECTED',
    content: {},
    code: null
  }
}

const getters = {
  websocket: state => {
    return state.websocket
  }
}

const mutations = {
  initWS (state, ws) {
    state.websocket.status = 'CONNECTING'
    state.websocket.ws = ws
  },
  wsOnOpen (state, path) {
    state.websocket[path] = {}
    state.websocket[path].status = 'CONNECTED'
  },
  wsOnError (state, e) {
    state.websocket.status = 'ERROR'
    state.websocket.code = JSON.stringify(e)
  },
  wsOnClose (state, e) {
    state.websocket.status = 'CLOSED'
    state.websocket.code = JSON.stringify(e)
  },
  wsOnMessage (state, data) {
    // state.websocket.status = 'MESSAGE'
    state.websocket.content = data
  },
  closeWS (state) {
    state.websocket.status = 'CLOSED'
    state.websocket.ws.close()
    state.websocket.ws = null
  }
}

const actions = {
  initWS ({ dispatch, commit }, path) {
    let websocket = new Ws(path)
    const ws = websocket.init()
    ws.onopen = () => dispatch('wsOnOpen', path)
    ws.onerror = (e) => dispatch('wsOnError', e)
    ws.onclose = (e) => dispatch('wsOnClose', e)
    ws.onmessage = (e) => dispatch('wsOnMessage', e)
    commit('initWS', ws)
  },
  async wsOnMessage ({ commit }, e) {
    const rdata = JSON.parse(e.data)
    console.log('WS received: ' + JSON.stringify(rdata))
    // await messageService.wsOnMessage(rdata)
    commit('wsOnMessage', rdata)
  },
  async sendWSMessage ({ commit }, message) {
    let data = JSON.stringify(message)
    await state.websocket.ws.send(data)
  },
  async wsOnOpen ({ commit }, path) {
    commit('wsOnOpen', path)
    let msg = `Websocket(${path}) CONNECTED!`
    commit('setAlert', { type: 'info', msg: msg }, { root: true })
    await console.log(msg)
  },
  async wsOnError ({ commit }, e) {
    commit('wsOnError', e)
    await console.log(`Websocket ERROR: ${e}`)
  },
  async wsOnClose ({ commit }, e) {
    commit('wsOnClose', e)
    commit('setAlert', { type: 'info', msg: `Websocket CLOSED! ${e}` }, { root: true })
    await console.log(`Websocket CLOSED! ${e}`)
  }
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

測試環(huán)境下闯捎,Vue需要代理轉(zhuǎn)發(fā)WS:

# /vue.config.js
module.exports = {
  outputDir: 'dist',
  assetsDir: 'static',
  devServer: {
    proxy: {
      '/api*': {
        // Forward frontend dev server request for /api to django dev server
        target: 'http://localhost:8000/'
      },
      '/ws/*': {
        // forward websocket
        target: 'ws://localhost:8000/',
        ws: true,
        secure: false,
        logLevel: 'debug'
      }
    }
  }
}

前端測試WebSockets

在用戶登錄時,連接到WS

在Home.vue里许溅,添加Vuex action

# /src/views/Home.vue
  mounted () {
    if (this.userIsAuthenticated) {
      this.$store.dispatch('messages/getTrips')
      this.$store.dispatch('ws/initWS', 'taxi')
    }
  },
image.png

用戶退出時瓤鼻,關(guān)閉WS
signUserOut 時,調(diào)用ws.js里的mutation

# /src/store/modules/messages.js
  signUserOut ({ commit }) {
    commit('setLoading', true, { root: true })
    messageService.signUserOut()
      .then(messages => {
        commit('ws/closeWS', '', { root: true })
...
      })
  },

Vuex ws.js里闹司,添加一條mutaion娱仔,關(guān)閉websockets連接

const mutations = {
...
  closeWS (state) {
    state.websocket.status = 'CLOSED'
    state.websocket.ws.close()
    state.websocket.ws = null
  }
}
image.png

同時,在Django的服務(wù)器log里游桩,也能看到WebSocket連接/斷開的記錄:

HTTP POST /api/log_in/ 200 [0.16, 127.0.0.1:49968]
HTTP GET /api/trip/ 200 [0.01, 127.0.0.1:49971]
WebSocket HANDSHAKING /ws/taxi/ [127.0.0.1:49973]
WebSocket CONNECT /ws/taxi/ [127.0.0.1:49973]
HTTP POST /api/log_out/ 204 [0.02, 127.0.0.1:50015]
WebSocket DISCONNECT /ws/taxi/ [127.0.0.1:49973]

總結(jié)

后臺添加了Channels牲迫,支持Websockets。
前端也搭建好了Websockets的框架借卧,方便地連接盹憎、發(fā)送、接收和斷開铐刘。

下一篇陪每,會介紹群發(fā)、群收功能
帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(5)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市檩禾,隨后出現(xiàn)的幾起案子挂签,更是在濱河造成了極大的恐慌,老刑警劉巖盼产,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饵婆,死亡現(xiàn)場離奇詭異,居然都是意外死亡戏售,警方通過查閱死者的電腦和手機(jī)侨核,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灌灾,“玉大人搓译,你說我怎么就攤上這事》嫦玻” “怎么了些己?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跑芳。 經(jīng)常有香客問我轴总,道長,這世上最難降的妖魔是什么博个? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任怀樟,我火速辦了婚禮,結(jié)果婚禮上盆佣,老公的妹妹穿的比我還像新娘往堡。我一直安慰自己,他們只是感情好共耍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布虑灰。 她就那樣靜靜地躺著,像睡著了一般痹兜。 火紅的嫁衣襯著肌膚如雪穆咐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天字旭,我揣著相機(jī)與錄音对湃,去河邊找鬼。 笑死遗淳,一個胖子當(dāng)著我的面吹牛拍柒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屈暗,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼拆讯,長吁一口氣:“原來是場噩夢啊……” “哼脂男!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起种呐,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤宰翅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陕贮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堕油,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潘飘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年肮之,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卜录。...
    茶點(diǎn)故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡戈擒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出艰毒,到底是詐尸還是另有隱情筐高,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布丑瞧,位于F島的核電站柑土,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绊汹。R本人自食惡果不足惜稽屏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望西乖。 院中可真熱鬧狐榔,春花似錦、人聲如沸获雕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽届案。三九已至庵楷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間楣颠,已是汗流浹背尽纽。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留球碉,地道東北人蜓斧。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像睁冬,于是被迫代替她去往敵國和親挎春。 傳聞我的和親對象是個殘疾皇子看疙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容