Django Channels 入門

原文:https://realpython.com/getting-started-with-django-channels/

本文中洼冻,我們將使用 Django Channels來(lái)構(gòu)建一個(gè)實(shí)時(shí)應(yīng)用程序:當(dāng)客戶端上線或下線時(shí)租漂,實(shí)時(shí)更新用戶列表數(shù)據(jù)搬卒。使用 WebSockets (通過(guò) Django Channels) 技術(shù)進(jìn)行客戶端和服務(wù)器之間的通信,當(dāng)有客戶端上線胡诗,服務(wù)器會(huì)向所有連接的客戶端發(fā)送一個(gè)廣播,并自動(dòng)更新客戶端屏幕顯示而不用刷新頁(yè)面。

理解本文需要的知識(shí)儲(chǔ)備:

  • Django 開發(fā)經(jīng)驗(yàn)

  • WebSocket 概念

項(xiàng)目任務(wù):

  • 為 Django 項(xiàng)目添加 WebSocket 的支持(通過(guò) Django Channels)

  • Django 使用 Redis学辱,建立簡(jiǎn)單的連接

  • 實(shí)現(xiàn)基本的用戶身份驗(yàn)證

  • 使用 Django 信號(hào)(Django Signals)機(jī)制來(lái)操作用戶上下線的動(dòng)作

將要用到的工具包:

  • Python (v3.6.0)

  • Django (v1.10.5)

  • Django Channels (v1.0.3)

  • Redis (v3.2.8)

開始

首先創(chuàng)建一個(gè)新的虛擬環(huán)境來(lái)隔離我們項(xiàng)目的依賴包的安裝

$ mkdir django-example-channels 
$ cd django-example-channels 
$ python3.6 -m venv env 
$  source env/bin/activate 
(env)$

安裝 Django, Django Channels, and ASGI Redis,創(chuàng)建一個(gè)新的 Django 項(xiàng)目和 app

(ENV)$ PIP安裝django的== 1個(gè) .10.5 通道== 1 .0.2 asgi_redis == 1 .0.0
(ENV)$ django-admin.py startproject命令example_channels
(ENV)$  CD example_channels
(ENV)$蟒manage.py的startApp example
(env)$ python manage.py migrate

下載安裝 Redis

啟動(dòng) Redis 服務(wù)默認(rèn)使用 6379 端口环形,Django 將使用該端口連接 Redis 服務(wù)策泣。

更新項(xiàng)目配置文件 settings.py 中的 INSTALLED_APPS 項(xiàng)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'example',
]

配置 CHANNEL_LAYERS:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgi_redis.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('localhost', 6379)],
        },
        'ROUTING': 'example_channels.routing.channel_routing',
    }
}

WebSocket 101

通常 Django 使用HTTP在客戶機(jī)和服務(wù)器之間通信:

  • 客戶端發(fā)送 HTTP 請(qǐng)求

  • Django 解析請(qǐng)求,提取 URL 然后將它匹配到一個(gè)視圖函數(shù)進(jìn)行處理

  • 視圖處理請(qǐng)求并返回 HTTP 響應(yīng)

HTTP不同的是WebSocket協(xié)議允許雙向通信抬吟,這意味著服務(wù)器可以將數(shù)據(jù)推送到客戶端萨咕,而無(wú)需用戶請(qǐng)求。HTTP中只有客戶端請(qǐng)求然后得到響應(yīng)火本,而WebSocket協(xié)議中危队,服務(wù)器可以同時(shí)與多個(gè)客戶端進(jìn)行通信,下面我們將要演示的使用ws://前綴钙畔,而不是http://

有什么不清楚的請(qǐng)自行查閱相關(guān)CHANNEL文檔

Consumers and Groups

新建一個(gè)文件 example_channels/example/consumers.py茫陆,創(chuàng)建首個(gè) consumer,它負(fù)責(zé)處理客戶端和服務(wù)器的基礎(chǔ)連接擎析。

from channels import Group


def ws_connect(message):
    Group('users').add(message.reply_channel)


def ws_disconnect(message):
    Group('users').discard(message.reply_channel)   

Consumer 對(duì)應(yīng)到Django的視圖簿盅,任何連接到服務(wù)器的客戶端用戶將被添加到“users”群組,可以接收到服務(wù)器發(fā)送的信息。當(dāng)客戶端離線時(shí)桨醋,該用戶通道(channel)將會(huì)被移除出群組中棚瘟,用戶無(wú)法接收到信息。

接下來(lái)喜最,進(jìn)行路由的設(shè)置偎蘸,它的工作方式與Django URL配置幾乎相同,將以下代碼添加到 example_channels/routing.py 這個(gè)新文件中:

from channels.routing import route
from example.consumers import ws_connect, ws_disconnect


channel_routing = [
    route('websocket.connect', ws_connect),
    route('websocket.disconnect', ws_disconnect),
]

上面我們通過(guò)定義一個(gè) channel_routing 替換 urlpatterns 瞬内,用 route() 替換掉 url()迷雪。將我們的 consumer 處理函數(shù)匹配到 WebSockets。

模板

編寫可以進(jìn)行 WebSockets的Html 代碼虫蝶,構(gòu)建項(xiàng)目模板文件夾 example_channels/example/templates/example章咧,新建:

a _base.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link  rel="stylesheet">
  <title>Example Channels</title>
</head>
<body>
  <div class="container">
    <br>
    {% block content %}{% endblock content %}
  </div>
  <script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
  {% block script %}{% endblock script %}
</body>
</html>

user_list.html

{% extends 'example/_base.html' %}

{% block content %}{% endblock content %}

{% block script %}
  <script>
    var socket = new WebSocket('ws://' + window.location.host + '/users/');

    socket.onopen = function open() {
      console.log('WebSockets connection created.');
    };

    if (socket.readyState == WebSocket.OPEN) {
      socket.onopen();
    }
  </script>
{% endblock script %}

現(xiàn)在,當(dāng)我們的客戶端成功使用 WebSocket 建立和服務(wù)器的連接時(shí)秉扑,我們可以在后臺(tái)的命令行看到相應(yīng)信息慧邮。

視圖

example_channels/example/views.py文件中,創(chuàng)建支持Django視圖的模板渲染的代碼:

from django.shortcuts import render


def user_list(request):
    return render(request, 'example/user_list.html')

將URL添加到 example_channels/example/urls.py中:

from django.conf.urls import url
from example.views import user_list


urlpatterns = [
    url(r'^$', user_list, name='user_list'),
]

將 example_channels/example_channels/urls.py中的地址舟陆,更新到項(xiàng)目的 URL 中:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('example.urls', namespace='example')),
]

測(cè)試

啟動(dòng)項(xiàng)目進(jìn)行測(cè)試:

(env)$ python manage.py runserver

您也可以在兩個(gè)不同的終端上運(yùn)行 pythonManage.py runserver-noWorkerpythonManage.py runWorker误澳,以作為兩個(gè)獨(dú)立的進(jìn)程測(cè)試接口服務(wù)器和工作服務(wù)器。兩種方法都有效秦躯!

現(xiàn)在你訪問(wèn) http://localhost:8000/ 在后臺(tái)的命令行終端應(yīng)該可以看到類似以下的信息:

[2018/02/19 23:24:57] HTTP GET / 200 [0.02, 127.0.0.1:52757]

[2018/02/19 23:24:58] WebSocket HANDSHAKING /users/ [127.0.0.1:52789]

[2018/02/19 23:25:03] WebSocket DISCONNECT /users/ [127.0.0.1:52789]

用戶身份驗(yàn)證

接下來(lái)我們需要做的就是處理用戶的身份驗(yàn)證忆谓,我們目標(biāo)是用戶登錄到系統(tǒng)后,能夠看到本組中其他成員的列表踱承。首先構(gòu)建用戶創(chuàng)建賬號(hào)和登錄的方式倡缠,新建一個(gè)簡(jiǎn)單的登錄頁(yè)面,用戶可以通過(guò)賬號(hào)和密碼進(jìn)行登錄茎活。

example_channels/example/templates/example/log_in.html:

{% extends 'example/_base.html' %}

{% block content %}
  <form action="{% url 'example:log_in' %}" method="post">
    {% csrf_token %}
    {% for field in form %}
      <div>
        {{ field.label_tag }}
        {{ field }}
      </div>
    {% endfor %}
    <button type="submit">Log in</button>
  </form>
  <p>Don't have an account? <a href="{% url 'example:sign_up' %}">Sign up!</a></p>
{% endblock content %}

接下來(lái)更新 example_channels/example/views.py:

from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect


def user_list(request):
    return render(request, 'example/user_list.html')


def log_in(request):
    form = AuthenticationForm()
    if request.method == 'POST':
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            return redirect(reverse('example:user_list'))
        else:
            print(form.errors)
    return render(request, 'example/log_in.html', {'form': form})


def log_out(request):
    logout(request)
    return redirect(reverse('example:log_in'))

Django 本身自帶通用身份驗(yàn)證表單功能昙沦,我們可以用它來(lái)提供用戶的登錄驗(yàn)證。表單檢驗(yàn)用戶的賬號(hào)和密碼是否匹配载荔,驗(yàn)證通過(guò)后返回一個(gè) User對(duì)象盾饮。用戶登錄后將重定向到項(xiàng)目的主頁(yè)。用戶也應(yīng)該可以進(jìn)行注銷的操作懒熙,所以我們繼續(xù)創(chuàng)建一個(gè)注銷視圖丘损,用戶注銷后將轉(zhuǎn)回登錄頁(yè)面。

更新 example_channels/example/urls.py:

from django.conf.urls import url
from example.views import log_in, log_out, user_list


urlpatterns = [
    url(r'^log_in/$', log_in, name='log_in'),
    url(r'^log_out/$', log_out, name='log_out'),
    url(r'^$', user_list, name='user_list')
]

我們還需要一個(gè)注冊(cè)頁(yè)面來(lái)提供新用戶注冊(cè)工扎,example_channels/example/templates/example/sign_up.html

{% extends 'example/_base.html' %}

{% block content %}
  <form action="{% url 'example:sign_up' %}" method="post">
    {% csrf_token %}
    {% for field in form %}
      <div>
        {{ field.label_tag }}
        {{ field }}
      </div>
    {% endfor %}
    <button type="submit">Sign up</button>
    <p>Already have an account? <a href="{% url 'example:log_in' %}">Log in!</a></p>
  </form>
{% endblock content %}

登錄和注冊(cè)頁(yè)面類似并相互鏈接徘钥。然后在視圖中加入函數(shù):

def sign_up(request):
    form = UserCreationForm()
    if request.method == 'POST':
        form = UserCreationForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('example:log_in'))
        else:
            print(form.errors)
    return render(request, 'example/sign_up.html', {'form': form})

同樣我們使用自帶的表單來(lái)提供用戶注冊(cè)處理,注冊(cè)成功后將定向到登錄頁(yè)面肢娘。要記得在代碼中導(dǎo)入表單模塊:

from  django.contrib.auth.forms  import  AuthenticationForm,  UserCreationForm

再次更新 example_channels/example/urls.py:

from django.conf.urls import url
from example.views import log_in, log_out, sign_up, user_list


urlpatterns = [
    url(r'^log_in/$', log_in, name='log_in'),
    url(r'^log_out/$', log_out, name='log_out'),
    url(r'^sign_up/$', sign_up, name='sign_up'),
    url(r'^$', user_list, name='user_list')
]

到此呈础,我們重新打開瀏覽器訪問(wèn) http://localhost:8000/sign_up/ 舆驶,填好注冊(cè)信息就創(chuàng)建我們第一個(gè)注冊(cè)用戶。(默認(rèn)用戶是michael猪落,密碼 johnson123)Sign_up視圖將我們重定向到log_in視圖贞远,我們可以對(duì)新創(chuàng)建的用戶進(jìn)行身份驗(yàn)證畴博。登錄后笨忌,我們可以測(cè)試新的身份驗(yàn)證視圖。然后使用“注冊(cè)”表單創(chuàng)建幾個(gè)新用戶俱病,為下一節(jié)做準(zhǔn)備官疲。

登錄提醒

我們已經(jīng)構(gòu)建了基本的登錄驗(yàn)證功能,但還沒(méi)有完成用戶列表的顯示亮隙,還要實(shí)現(xiàn)當(dāng)用戶登錄下線時(shí)服務(wù)器自動(dòng)更新這個(gè)列表途凫。接下來(lái),我們將更新消費(fèi)者函數(shù)溢吻,以便當(dāng)用戶登錄或退出時(shí)發(fā)送通知消息维费。該消息包括用戶名和連接狀態(tài)信息。

example_channels/example/consumers.py:

import json
from channels import Group
from channels.auth import channel_session_user, channel_session_user_from_http


@channel_session_user_from_http
def ws_connect(message):
    Group('users').add(message.reply_channel)
    Group('users').send({
        'text': json.dumps({
            'username': message.user.username,
            'is_logged_in': True
        })
    })


@channel_session_user
def ws_disconnect(message):
    Group('users').send({
        'text': json.dumps({
            'username': message.user.username,
            'is_logged_in': False
        })
    })
    Group('users').discard(message.reply_channel)

example_channels/example/templates/example/user_list.html:

{% extends 'example/_base.html' %}

{% block content %}
  <a href="{% url 'example:log_out' %}">Log out</a>
  <br>
  <ul>
    {% for user in users %}
      <!-- NOTE: We escape HTML to prevent XSS attacks. -->
      <li data-username="{{ user.username|escape }}">
        {{ user.username|escape }}: {{ user.status|default:'Offline' }}
      </li>
    {% endfor %}
  </ul>
{% endblock content %}

{% block script %}
  <script>
    var socket = new WebSocket('ws://' + window.location.host + '/users/');

    socket.onopen = function open() {
      console.log('WebSockets connection created.');
    };

    socket.onmessage = function message(event) {
      var data = JSON.parse(event.data);
      // NOTE: We escape JavaScript to prevent XSS attacks.
      var username = encodeURI(data['username']);
      var user = $('li').filter(function () {
        return $(this).data('username') == username;
      });

      if (data['is_logged_in']) {
        user.html(username + ': Online');
      }
      else {
        user.html(username + ': Offline');
      }
    };

    if (socket.readyState == WebSocket.OPEN) {
      socket.onopen();
    }
  </script>
{% endblock script %}

在主頁(yè)上促王,我們擴(kuò)展用戶列表用來(lái)顯示用戶數(shù)據(jù)犀盟,將每個(gè)用戶名存儲(chǔ)為一個(gè)數(shù)據(jù)屬性,方便在DOM中搜索到該數(shù)據(jù)項(xiàng)蝇狼。向WebSocket添加一個(gè)事件監(jiān)聽器阅畴,用來(lái)處理服務(wù)器的消息。當(dāng)收到消息時(shí)迅耘,解析JSON數(shù)據(jù)贱枣,定位到該用戶的<li>元素,更新該用戶狀態(tài)颤专。

Django不會(huì)跟蹤用戶是否登錄纽哥,因此我們還要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的模型來(lái)實(shí)現(xiàn)這個(gè)功能。創(chuàng)建一個(gè)LoggedInUser模型栖秕,與用戶模型進(jìn)行一對(duì)一的連接春塌。

example_channels/example/models.py:

from django.conf import settings
from django.db import models


class LoggedInUser(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, related_name='logged_in_user')

當(dāng)用戶登錄時(shí)應(yīng)用將創(chuàng)建一個(gè) LoggedInUser 實(shí)例,當(dāng)用戶退出時(shí)這個(gè)實(shí)例將被刪除累魔。更新我們的數(shù)據(jù)庫(kù):

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

更新用戶列表視圖函數(shù)摔笤,提供檢索需要渲染的用戶列表:

example_channels/example/views.py

from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect


User = get_user_model()


@login_required(login_url='/log_in/')
def user_list(request):
    """
    NOTE: This is fine for demonstration purposes, but this should be
    refactored before we deploy this app to production.
    Imagine how 100,000 users logging in and out of our app would affect
    the performance of this code!
    """
    users = User.objects.select_related('logged_in_user')
    for user in users:
        user.status = 'Online' if hasattr(user, 'logged_in_user') else 'Offline'
    return render(request, 'example/user_list.html', {'users': users})


def log_in(request):
    form = AuthenticationForm()
    if request.method == 'POST':
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            return redirect(reverse('example:user_list'))
        else:
            print(form.errors)
    return render(request, 'example/log_in.html', {'form': form})


@login_required(login_url='/log_in/')
def log_out(request):
    logout(request)
    return redirect(reverse('example:log_in'))


def sign_up(request):
    form = UserCreationForm()
    if request.method == 'POST':
        form = UserCreationForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('example:log_in'))
        else:
            print(form.errors)
    return render(request, 'example/sign_up.html', {'form': form})

如果該用戶有對(duì)應(yīng)的 LoggedInUser 則標(biāo)記為在線,否則標(biāo)記為離線垦写。添加了一個(gè)@login_required裝飾器吕世,用來(lái)限制僅僅對(duì)注冊(cè)用戶的訪問(wèn)。

添加以下導(dǎo)入包

from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required

現(xiàn)在梯投,用戶可以登錄命辖,注銷况毅,這些會(huì)觸發(fā)服務(wù)器向客戶端發(fā)送消息。

但是當(dāng)用戶第一次登錄時(shí)尔艇,我們無(wú)法知道哪些用戶已經(jīng)登錄尔许。用戶只在其他用戶的狀態(tài)更改時(shí)才看到更新。這就是LoggedInUser發(fā)揮作用的地方终娃,但我們需要一種方法味廊,在用戶登錄時(shí)創(chuàng)建LoggedInUser實(shí)例,然后在該用戶注銷時(shí)將其刪除棠耕。

但我們現(xiàn)在還沒(méi)有辦法知道當(dāng)用戶第一個(gè)登錄時(shí)是哪一位余佛。只用當(dāng)其他用戶登錄狀態(tài)更新是我們才能看到。

Django庫(kù)包含一個(gè)稱為Signals的特性窍荧,當(dāng)發(fā)生某些操作時(shí)辉巡,它會(huì)廣播通知。應(yīng)用程序可以監(jiān)聽這些通知蕊退,然后對(duì)它們采取行動(dòng)郊楣。我們可以利用兩個(gè)有用的內(nèi)置信號(hào)(user_login和user_logout)來(lái)處理LoggedInUser行為。

example_channels/example/signals.py****:

from django.contrib.auth import user_logged_in, user_logged_out
from django.dispatch import receiver
from example.models import LoggedInUser


@receiver(user_logged_in)
def on_user_login(sender, **kwargs):
    LoggedInUser.objects.get_or_create(user=kwargs.get('user'))


@receiver(user_logged_out)
def on_user_logout(sender, **kwargs):
    LoggedInUser.objects.filter(user=kwargs.get('user')).delete()

example_channels/example/apps.py

from django.apps import AppConfig


class ExampleConfig(AppConfig):
    name = 'example'

    def ready(self):
        import example.signals

example_channels/example/init.py

default_app_config  =  'example.apps.ExampleConfig'

完整性檢查

到此瓤荔,代碼部分已經(jīng)完成净蚤,我們用多個(gè)用戶賬號(hào)連接到服務(wù)器來(lái)測(cè)試一下我們的應(yīng)用。啟動(dòng) Django 服務(wù)茉贡,登錄系統(tǒng)塞栅,訪問(wèn)項(xiàng)目主頁(yè)。我們應(yīng)該能夠看到所有的用戶列表腔丧,此時(shí)用戶的狀態(tài)都是“離線”放椰。打開新的瀏覽器匿名窗口,用另一個(gè)賬號(hào)登錄愉粤,這時(shí)各個(gè)窗口的用戶列表會(huì)自動(dòng)更新到“在線”狀態(tài)砾医。你可以通過(guò)不同的瀏覽器、設(shè)備來(lái)測(cè)試登錄登出衣厘。

查看客戶端瀏覽器上的開發(fā)人員控制臺(tái)和終端中的服務(wù)器活動(dòng)如蚜,你可以觀察到:當(dāng)用戶登錄時(shí),WebSocket 連接被創(chuàng)建影暴,當(dāng)用戶注銷時(shí)错邦,WebSocket 連接被銷毀。

總結(jié)

本文我們討論了:

  • Django Channels

  • WebSockets

  • 用戶身份驗(yàn)證

  • Django 信號(hào)

  • 部分前端開發(fā)技術(shù)

重要的是 Django Channels 擴(kuò)展了 Django 框架的傳統(tǒng)功能型宙,通過(guò) WebSockets 我們可以將消息從服務(wù)器直接發(fā)送到客戶端撬呢。這個(gè)功能可以讓我們進(jìn)一步做出很多有意思的東西,比如聊天室妆兑、多人在線游戲魂拦、能夠?qū)崟r(shí)通信的協(xié)作應(yīng)用毛仪。一般的應(yīng)用使用 WebSockets ,在服務(wù)器完成任務(wù)后向客戶端發(fā)送狀態(tài)更新來(lái)代替?zhèn)鹘y(tǒng)的定期輪詢服務(wù)器芯勘,從而得到性能改進(jìn)箱靴。

本文只是簡(jiǎn)單介紹了 Django Channels 的基本使用,感興趣的童鞋可以閱讀 Django Channels 項(xiàng)目的文檔荷愕,看看你還可以用它來(lái)實(shí)現(xiàn)什么有趣的東東衡怀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市路翻,隨后出現(xiàn)的幾起案子狈癞,更是在濱河造成了極大的恐慌茄靠,老刑警劉巖茂契,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異慨绳,居然都是意外死亡掉冶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門脐雪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厌小,“玉大人,你說(shuō)我怎么就攤上這事战秋¤笛牵” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵脂信,是天一觀的道長(zhǎng)癣蟋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狰闪,這世上最難降的妖魔是什么疯搅? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮埋泵,結(jié)果婚禮上幔欧,老公的妹妹穿的比我還像新娘。我一直安慰自己丽声,他們只是感情好礁蔗,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雁社,像睡著了一般浴井。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上歧胁,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天滋饲,我揣著相機(jī)與錄音厉碟,去河邊找鬼。 笑死屠缭,一個(gè)胖子當(dāng)著我的面吹牛箍鼓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呵曹,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼款咖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了奄喂?” 一聲冷哼從身側(cè)響起铐殃,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎跨新,沒(méi)想到半個(gè)月后富腊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡域帐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年赘被,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肖揣。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡民假,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出龙优,到底是詐尸還是另有隱情羊异,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布彤断,位于F島的核電站野舶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瓦糟。R本人自食惡果不足惜筒愚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菩浙。 院中可真熱鬧巢掺,春花似錦、人聲如沸劲蜻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)先嬉。三九已至轧苫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背含懊。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工身冬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岔乔。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓酥筝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親雏门。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘿歌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • 版權(quán): https://github.com/haiiiiiyun/awesome-django-cn Aweso...
    若與閱讀 23,017評(píng)論 3 241
  • 在這個(gè)例子中,我們將使用Django Channels來(lái)創(chuàng)建一個(gè)實(shí)時(shí)在線應(yīng)用茁影,當(dāng)用戶登錄或下線時(shí)宙帝,這個(gè)應(yīng)用可以自動(dòng)...
    Ccccolin_aha閱讀 13,935評(píng)論 1 12
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,311評(píng)論 1 92
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理募闲,服務(wù)發(fā)現(xiàn)步脓,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 模塊間聯(lián)系越多蝇更,其耦合性越強(qiáng)沪编,同時(shí)表明其獨(dú)立性越差( 降低耦合性,可以提高其獨(dú)立性)年扩。軟件設(shè)計(jì)中通常用耦合度和內(nèi)聚...
    riverstation閱讀 2,061評(píng)論 0 8