WebQQの実装: Djangoを使用した小規模プロジェクト

HTTPプロトコルの特徴

HTTPプロトコルは短時間接続と無状態性を特徴としています。

短時間接続: サーバーがリクエストに応答すると、その接続は閉じられます。これは大量の同時アクセスに対応するためで、長時間開いたままの接続は資源を浪費します。

無状態: サーバーはクライアントの状態を記憶しません。各リクエストには必要な情報が含まれます。

WebQQの通信実装

WebQQでは、ユーザー間のメッセージ送受信にはサーバーが必要です。通常、C1からC2へのメッセージはサーバーを通じて転送されます。

しかし、サーバーがC2に直接メッセージを送ることはできません。なぜなら、サーバーはリクエストを待つだけだからです。

そのため、ポーリングという手法が使われます。

ポーリング方式

  • ショートポーリング: C2が定期的にサーバーにリクエストを送ります。
  • ロングポーリング: C2がサーバーにリクエストを送ると、サーバーにデータがない場合でもレスポンスを返さずに待機し続けます。新しいデータが到着したらすぐに返します。

WebQQのデータベース設計

ユーザーの友達リストは多対多の関係を持ちます。

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    name = models.CharField(max_length=32)
    groups = models.ManyToManyField("UserGroup")
    friends = models.ManyToManyField('self', related_name='my_friends')

さらに、チャットグループ用のモデルも作成します。

class QQGroup(models.Model):
    name = models.CharField(max_length=64, unique=True)
    description = models.CharField(max_length=255, default="The Admin is so lazy, The Noting to show you ....")
    members = models.ManyToManyField(UserProfile, blank=True)
    admins = models.ManyToManyField(UserProfile, blank=True, related_name='group_admins')
    max_member_nums = models.IntegerField(default=200)

    def __unicode__(self):
        return self.name

URLの設定

プロジェクト全体のURL設定:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^web/', include(web_urls)),
    url(r'^chat/', include(chat_urls)),
    url(r'', views.index, name='index'),
]

webアプリケーションのURL設定:

urlpatterns = [
    url(r'category/(\d+)/$', views.category, name='category'),
    url(r'article_detaill/(\d+)/$', views.article_detaill, name='article_detaill'),
    url(r'article/new/$', views.new_article, name='new_article'),
    url(r'account/logout$', views.acount_logout, name='logout'),
    url(r'account/login', views.acount_login, name='login'),
]

web_chatアプリケーションのURL設定:

urlpatterns = [
    url(r'^dashboard/$', views.dashboard, name='web_chat'),
]

Djangoの認証モジュールを使用してログイン状態を確認

@login_required
def dashboard(request):
    return render(request, 'web_chat/dashboard.html')

settings.pyで未ログイン時のリダイレクト先を設定します。

LOGIN_URL = '/web/account/login/'

イベントチェーン

$(document).ready(function () {
    $("body").delegate("textarea", "keydown", function (e) {
        if (e.which == 13) {
            var msg_text = $("textarea").val();
            if ($.trim(msg_text).length > 0) {
                AddSentMsgIntoBox(msg_text);
                $("textarea").val('');
            }
        }
    });
});

チャット内容の自動展開とスクロール

.chat_contener {
    width: 100%;
    height: 490px;
    background-color: black;
    opacity: 0.6;
    overflow: auto;
}
function AddSentMsgIntoBox(msg_text) {
    var msg_ele = "<div class='clearfix' style='padding-top:10px'>" + "<div class='arrow'>" + "</div>" +
                    "<div class='content_send'>" + "<div style='margin-top: 10px;margin-left: 5px;'>" +
                    msg_text + "</div>" + "</div>";
    $(".chat_contener").append(msg_ele);
    $('.chat_contener').animate({
        scrollTop: $('.chat_contener')[0].scrollHeight
    }, 500);
}

AJAXリクエスト

$.ajax({
    url: '/save_hostinfo/',
    type: 'POST',
    data: {data: JSON.stringify(change_info)},
    success: function (arg) {
        var callback_dict = $.parseJSON(arg);
        if (callback_dict) {
            setTimeout("hide()", 5000);
            var change_infos = '修改了' + callback_dict['change_count'] + '条数据';
            $('#handle_status').text(change_infos).removeClass('hide');
        } else {
            alert(callback_dict.error);
        }
    }
});

AJAX POSTデータのCSRF問題

function GetCsrfToken() {
    return $("input[name='csrfmiddlewaretoken']").val();
}

function SendMsg(msg_text) {
    var contact_id = $('#chat_hander h2').attr("contact_id");
    var contact_type = $('#chat_hander h2').attr("contact_type");
    var msg_dic = {
        'contact_type': contact_type,
        'to': contact_id,
        'from': "{{ request.user.userprofile.id }}",
        'from_name': "{{ request.user.userprofile.name }}",
        'msg': msg_text
    };
    $.post("{% url 'send_msg' %}", {'data': JSON.stringify(msg_dic), 'csrfmiddlewaretoken': GetCsrfToken()}, function (callback) {
        console.log(callback);
    });
}

WebQQのメッセージ保存方法

import Queue

GLOBAL_MQ = {}

def new_msg(request):
    if request.method == 'POST':
        data = json.loads(request.POST.get('data'))
        send_to = data['to']
        if send_to not in GLOBAL_MQ:
            GLOBAL_MQ[send_to] = Queue.Queue()
        data['timestamp'] = time.strftime("%Y-%m-%d %X", time.localtime())
        GLOBAL_MQ[send_to].put(data)
        return HttpResponse(GLOBAL_MQ[send_to].qsize())
    else:
        request_user = str(request.user.userprofile.id)
        msg_lists = []
        if request_user in GLOBAL_MQ:
            stored_msg_nums = GLOBAL_MQ[request_user].qsize()
            try:
                if stored_msg_nums == 0:
                    print "\033[41;1m没有消息等待,15秒.....\033[0m"
                    msg_lists.append(GLOBAL_MQ[request_user].get(timeout=15))
            except Exception as e:
                print ('error:', e)
                print "\033[43;1等待已超时......15秒.....\033[0m"
            for i in range(stored_msg_nums):
                msg_lists.append(GLOBAL_MQ[request_user].get())
        else:
            GLOBAL_MQ[str(request.user.userprofile.id)] = Queue.Queue()
        return HttpResponse(json.dumps(msg_lists))

ページ内のチャットボックスの内容切り替え

var GLOBAL_SESSION_CACHE = {
    'single_contact': {},
    'group_contact': {},
};

function OpenDialogBox(ele) {
    var contact_id = $(ele).attr("contact_id");
    var contact_name = $(ele).attr("chat_to");
    var contact_type = $(ele).attr("contact_type");
    DumpSession();
    var chat_to_info = "<h2 style='color:whitesmoke;text-align:center;' contact_type='" + contact_type + "' contact_id='" + contact_id + "'>" + contact_name + "</h2>";
    $('#chat_hander').html(chat_to_info);
    $('.chat_contener').html(LoadSession(contact_id, contact_type));
    var unread_msg_num_ele = $(ele).find('span')[0];
    $(unread_msg_num_ele).text(0);
    $(unread_msg_num_ele).addClass('hide');
}

function DumpSession2(contact_id, contact_type, content) {
    if (contact_id) {
        GLOBAL_SESSION_CACHE[contact_type][contact_id] = content;
    }
}

function LoadSession(current_contact_id, current_contact_type) {
    if (GLOBAL_SESSION_CACHE[current_contact_type].hasOwnProperty(current_contact_id)) {
        var session_html = GLOBAL_SESSION_CACHE[current_contact_type][current_contact_id];
    } else {
        var session_html = '';
    }
    return session_html;
}

タグ: Django Python Web Development HTTP AJAX

5月16日 23:44 投稿