項目演示地址:https://www.szyfd.xyz/app/threewall/3dwall
新建django 項目,如剛接觸微信請移步微信開發入門篇第一章:https://www.cnblogs.com/wangcongxing/p/11546780.html
1.新建app threewall
python manage.py startapp threewall
2.新建static 文件夾,配置靜態資源
配置settings.py
STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), )
3.安裝 channels channels_redis 用於處理web socket,pyCryptodome 處理二維碼對稱加密
pip install -U channels==2.0.2 channels_redis==2.1.1
pip install pyCryptodome
pip install django-simpleui
4.安裝完成后配置如下 settings.py
INSTALLED_APPS = [ 'simpleui', 'import_export', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app', 'threewall', 'channels', ]
5.threewall是我們准備建立的簽到牆應用,接着就建立我們的 asgi 應用,並指定其要使用的路由。在 settings 同級目錄下新建一個 routing.py 的文件:
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import threewall.routing application = ProtocolTypeRouter({ # (http->django views is added by default) # 普通的HTTP請求不需要我們手動在這里添加,框架會自動加載過來 'websocket': AuthMiddlewareStack( URLRouter( threewall.routing.websocket_urlpatterns ) ), })
6.threewall.routing 以及 threewall.routing.websocket_urlpatterns 是我們后面會自己建立的模塊。
緊接着,我們需要在 Django 的配置文件中繼續配置 Channels 的 asgi 應用和通道層的信息:
#WSGI_APPLICATION = 'wechatDemo.wsgi.application' ASGI_APPLICATION = "wechatDemo.routing.application" # 上面新建的 asgi 應用 CHANNEL_LAYERS = { 'default': { # 這里用到了 channels_redis 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { 'hosts': [('127.0.0.1', 6379)], # 配置你自己的 redis 服務信息 }, } }
7.啟動redis
windows 安裝redis地址:https://www.jianshu.com/p/e16d23e358c0
8.設計簽到表:threewall>models.py
from django.contrib.auth.models import User from django.db import models from django.utils.crypto import random # Create your models here. from django.utils.html import format_html # Create your models here. def rename(newname): def decorator(fn): fn.__name__ = newname return fn return decorator # 簽到表 class checkin(models.Model): headimgurl = models.URLField(max_length=256, default="", null=True, blank=True) openid = models.CharField(max_length=225, verbose_name="openid", blank=True, default="") nickname = models.CharField(max_length=225, verbose_name="昵稱", blank=True, default="") sex = models.CharField(max_length=225, verbose_name="性別", blank=True, default="") language = models.CharField(max_length=225, verbose_name="語言", blank=True, default="") city = models.CharField(max_length=225, verbose_name="城市", blank=True, default="") createTime = models.DateTimeField(auto_now_add=True, verbose_name="簽到時間") lastTime = models.DateTimeField(auto_now=True, verbose_name="修改時間") class Meta: verbose_name_plural = "簽到表" @rename("模板頭像") def showheadimgurl(self): return format_html("<img src='{}' style='width:50px'/>", self.headimgurl) def __str__(self): return self.nickname
9.threewall>modes.py
from django.contrib import admin from threewall import models # Register your models here. @admin.register(models.checkin) class orderAdmin(admin.ModelAdmin): list_display = ("showheadimgurl", "openid", "nickname", "sex", "language", "city", "createTime", "lastTime") list_display_links = ("openid", "nickname") search_fields = ('nickname', "openid") list_per_page = 50
10.threewall>views.py
from django.shortcuts import render from wechatpy.oauth import WeChatOAuth from django.shortcuts import render, redirect from django.http import JsonResponse, HttpResponse, HttpResponseRedirect import time import datetime from django.conf import settings from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.shortcuts import render import uuid from wechatpy import WeChatClient import os import json from wechatpy import WeChatPay from threewall import models from wechatpy.pay import dict_to_xml import base64 from Crypto.Cipher import AES # Create your views here. # 公眾號id AppID = "xxx" # 公眾號AppSecret AppSecret = "xxx" # 密鑰 key = "xxxx" # 服務號 client = WeChatClient(AppID, AppSecret) # 消息通道 from channels.layers import get_channel_layer channel_layer = get_channel_layer() from asgiref.sync import async_to_sync # Create your views here. def dwall(request): checkins = models.checkin.objects.values("headimgurl")[0:200] print(checkins.query) checkUserInfo = [] CurPersonNum = checkins.count() for item in checkins: checkUserInfo.append({"headimgurl": item["headimgurl"]}) if checkUserInfo.__len__() < 199: index = 0 while index < (199 - checkUserInfo.__len__()): index += 1 checkUserInfo.append({"headimgurl": "/static/3dwall/img/a.png"}) aes = AES.new(add_to_16(key), AES.MODE_ECB) # 先進行aes加密 ticks = str(time.time()) encrypt_aes = aes.encrypt(add_to_16(ticks)) # 用base64轉成字符串形式 encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') # 執行加密並轉碼返回bytes return render(request, "3dwall.html", {"checkUserInfo": checkUserInfo, "CurPersonNum": CurPersonNum, "encrypted_text": encrypted_text}) # 定義授權裝飾器 def getWeChatOAuth(redirect_url): return WeChatOAuth(AppID, AppSecret, redirect_url, 'snsapi_userinfo') def oauth(method): def warpper(request): if request.session.get('user_info', None) is None: code = request.GET.get('code', None) wechat_oauth = getWeChatOAuth(request.get_raw_uri()) url = wechat_oauth.authorize_url print(url) if code: try: wechat_oauth.fetch_access_token(code) user_info = wechat_oauth.get_user_info() print(user_info) except Exception as e: print(str(e)) # 這里需要處理請求里包含的 code 無效的情況 # abort(403) else: # 建議存儲在用戶表 request.session['user_info'] = user_info else: return redirect(url) return method(request) return warpper @oauth def checkin(request): signature = request.GET.get("signature", None) if signature is None: return render(request, "checkerror.html") try: aes = AES.new(add_to_16(key), AES.MODE_ECB) # 優先逆向解密base64成bytes base64_decrypted = base64.decodebytes(signature.replace(' ', '+').encode(encoding='utf-8')) # 執行解密密並轉碼返回str decrypted_text = str(aes.decrypt(base64_decrypted), encoding='utf-8').replace('\0', '') except Exception as e: print("signature="+signature) return render(request, "expired.html") # 二維碼已過期 print(decrypted_text) ltime = time.localtime(float(decrypted_text)) qrTime = time.strftime("%Y-%m-%d %H:%M:%S", ltime) # 沒有必要再次轉換為時間格式 # 獲得系統當前時間-10秒,用於判斷二維碼是否過期 d = datetime.datetime.now() + datetime.timedelta(seconds=-10) t = d.timetuple() timeStamp = int(time.mktime(t)) timeStamp = float(str(timeStamp) + str("%06d" % d.microsecond)) / 1000000 if float(decrypted_text) < timeStamp: return render(request, "expired.html") # 二維碼已過期 print(qrTime) user_info = request.session.get('user_info') user_info = client.user.get(user_info["openid"]) print(user_info) subscribe = user_info["subscribe"] if subscribe == 0: return render(request, "isfollow.html") headimgurl = user_info["headimgurl"] nickname = user_info["nickname"] ischeck = models.checkin.objects.filter(openid=user_info["openid"]).first() if ischeck is None: models.checkin.objects.create(headimgurl=headimgurl, openid=user_info["openid"], nickname=nickname, sex=user_info["sex"], language=user_info["language"], city=user_info["country"] + "-" + user_info["province"] + "-" + user_info["city"]) else: ischeck.save() async_to_sync(channel_layer.group_send)("chat_roomName", {'type': 'chat_message', 'message': {"type": "message", "headimgurl": headimgurl, "nickname": nickname}}) return render(request, "checkin.html") # pip install pyCryptodome # str不是16的倍數那就補足為16的倍數 def add_to_16(value): while len(value) % 16 != 0: value += '\0' return str.encode(value) # 返回bytes @csrf_exempt def signatureCheck(request): while True: time.sleep(5) # 初始化加密器 aes = AES.new(add_to_16(key), AES.MODE_ECB) # 先進行aes加密 ticks = str(time.time()) encrypt_aes = aes.encrypt(add_to_16(ticks)) # 用base64轉成字符串形式 encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') # 執行加密並轉碼返回bytes #print(encrypted_text) async_to_sync(channel_layer.group_send)("chat_roomName", {'type': 'chat_message', 'message': {"type": "signatureCheck", "signatureCheck": encrypted_text}})
11.threewall>consumers.py
import json,uuid import datetime import time from asgiref.sync import async_to_sync import multiprocessing from channels.generic.websocket import WebsocketConsumer class ChatConsumer(WebsocketConsumer): def connect(self): # 當 websocket 一鏈接上以后觸發該函數 self.room_name = self.scope['url_route']['kwargs']['room_name'] self.room_group_name = 'chat_%s' % self.room_name print(self.room_group_name) # 注意 `group_add` 只支持異步調用,所以這里需要使用`async_to_sync`轉換為同步調用 async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name ) # 接受該鏈接 self.accept() def disconnect(self, close_code): # 斷開鏈接是觸發該函數 # 將該鏈接移出聊天室 async_to_sync(self.channel_layer.group_discard)( self.room_group_name, self.channel_name ) def receive(self, text_data): # 前端發送來消息時,通過這個接口傳遞 text_data_json = json.loads(text_data) message = text_data_json['message'] async_to_sync(self.channel_layer.group_send)( self.room_group_name, { # 這里的type要在當前類中實現一個相應的函數, # 下划線或者'.'的都會被Channels轉換為下划線處理, # 所以這里寫 'chat.message'也沒問題 'type': 'chat_message', 'message': message + str(datetime.datetime.today()) } ) # 從聊天室拿到消息,后直接將消息返回回去 def chat_message(self, event): message = event['message'] # Send message to WebSocket self.send(text_data=json.dumps({ 'message': message }))
12.threewall>routing.py
from django.urls import path from threewall import consumers websocket_urlpatterns = [ # 路由,指定 websocket 鏈接對應的 consumer path('ws/chat/<str:room_name>/', consumers.ChatConsumer), ]
13.wechatDemo>routing.py
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import threewall.routing application = ProtocolTypeRouter({ # (http->django views is added by default) # 普通的HTTP請求不需要我們手動在這里添加,框架會自動加載過來 'websocket': AuthMiddlewareStack( URLRouter( threewall.routing.websocket_urlpatterns ) ), })
14.后台統計
15.項目完整結構
注:需要靜態資源,請私下聯系