Python aiohttp 异步注册,登录,认证完整客户端服务器应用

知识积累到一定时候,就应该将它们整合起来,融会贯通发挥效力.

这个项目需要实现以下技术

1.PyQt5的Ui界面操作

2.客户端数据采集,通过aiohttp传输给服务端

3.服务端接收数据,通过SQLite ORM 对数据库表进行CRUD操作

4.客户端请求登录,服务端认证返回cookie

5.服务端通过cookie获取用户相关信息

6.客户端通过cookie向服务端发请求获取用户信息并显示

 具体开发过程如下:

 1.先在Qt Designer做三个界面login.ui registe.ui mainwin.ui

2.将界面文件转换为py文件

        login_ui.py registe_ui.py mainwin_ui.py

        PyQt5界面制作过程可参见我的博文:python小技巧大应用--基础实用漂亮界面(无边框,圆角,可拖拽)

3.开发登录界面的执行程序login_do.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a Main using Ui file'

__author__ = 'TianJiang Gui'

import sys
from PyQt5.QtWidgets import QApplication,QDialog,QFileDialog,QMessageBox,QLineEdit
import login_ui
from PyQt5.QtCore import *
from Utils.util_Commons import *
from registe_do import RegisteDo
from mainwin_do import MainwinDo

import asyncio,aiohttp
import Utils.globalvar as gl

class LoginDo(QDialog, login_ui.Ui_Login):
    def __init__(self):
        QDialog.__init__(self)
        login_ui.Ui_Login.__init__(self)
        self.setupUi(self)

       # 无边框
        self.setWindowFlags(Qt.FramelessWindowHint)

        #---gtj 设置窗口透明
        self.setAttribute(Qt.WA_TranslucentBackground)

        #---gtj右下角的小拖拽
        # self.setSizeGripEnabled(True)

        # ---gtj实现引用qss功能
        self.qss()

        self.line_init()
        self.pushbutton_init()

        #---gtj实现最小化,关闭功能
        # self.pushButton_min.clicked.connect(self.showMinimized)
        self.pushButton_quit.clicked.connect(self.close)

        # self.login_button.clicked.connect(self.do_login)

    #---gtj 实现鼠标拖拽功能
    def mousePressEvent(self, event):
        self.pressX = event.x()    #记录鼠标按下的时候的坐标
        self.pressY = event.y()

    def mouseMoveEvent(self, event):
        x = event.x()
        y = event.y()   #获取移动后的坐标
        moveX = x-self.pressX
        moveY = y-self.pressY  #计算移动了多少

        positionX = self.frameGeometry().x() + moveX
        positionY = self.frameGeometry().y() + moveY    #计算移动后主窗口在桌面的位置
        self.move(positionX, positionY)    #移动主窗口
    #---gtj qss文件引用
    def qss(self):
        self.qssfile = "./qss/dialog.qss"
        self.style = UsingQSS.loadqss(self.qssfile)
        self.setStyleSheet(self.style)

    # 单行文本输入框初始化方法
    def line_init(self):
        # 设置提示语
        self.name_line.setPlaceholderText('请输入用户名')
        self.pass_line.setPlaceholderText('请此输入密码')
        # 设置用户密码以掩码显示
        # self.password_line.setEchoMode(QLineEdit.Password)
        # 设置用户在输入时明文显示,控件焦点转移后掩码显示
        self.pass_line.setEchoMode(QLineEdit.PasswordEchoOnEdit)
        # 设置检查单行文本输入框输入状态
        self.name_line.textChanged.connect(self.check_input)
        self.pass_line.textChanged.connect(self.check_input)

    # 按钮初始化方法
    def pushbutton_init(self):
        # 先设置登录按钮为不可点击状态,当用户输入用户名及密码时才变为可点击状态
        self.login_button.setEnabled(False)
        # 登录按钮点击信号绑定槽函数
        self.login_button.clicked.connect(self.login_func)
        # 注册按钮点击信号绑定槽函数
        self.registe_button.clicked.connect(self.do_registe)
        # 退出按钮点击信号绑定槽函数
        self.exit_button.clicked.connect(self.close)



    # 检查文本输入框方法
    def check_input(self):
        # 当用户名及密码输入框均有内容时,设置登录按钮为可点击状态,或者不可点击。
        if self.name_line.text() and self.pass_line.text():
            self.login_button.setEnabled(True)
        else:
            self.login_button.setEnabled(False)

    # 用户注册方法
    def do_registe(self):
        # 向服务端发送注册请求,
        # ----这边也以一个文件读取操作,模拟登录系统----
        register_page = RegisteDo()
        register_page.show()
        # 注册页面的注册成功信号绑定,在登录页面输入注册成功后的用户ID及密码
        register_page.successful_signal.connect(self.successful_func)
        register_page.exec()

    # 注册成功方法
    def successful_func(self, data):
        print(data)
        # 将注册成功数据写入登录页面
        self.name_line.setText(data[0])
        self.pass_line.setText(data[1])
    # 登录方法
    # 在登录方法中,检查用户设置的登录状态
    def login_func(self):
        global cookie
        loop = asyncio.get_event_loop()
        params = {'name': self.name_line.text(), 'pass': self.pass_line.text()}
        url = 'http://127.0.0.1:8100/apiLogin'
        loop.run_until_complete(self.do_login(url, params))
        if len(str(cookie))>1:
            gl._init()
            gl.set_value('cookie',cookie)
            self.close()
            print("open mainwin")
            m = MainwinDo()
            # m.cookie = cookie
            m.show()
            m.exec()
    async def do_login(self,url,params):
        global cookie
        async with aiohttp.ClientSession() as session:
            headers = {'content-type': 'application/json'}
            rj = {}
            async with session.get(url, json=params, headers=headers) as response:
                rj = await response.json(content_type=None)
                print("通过get_json返回的数据type=%s:%s" % (type(rj), rj))
            await session.close()
            if len(rj) > 0:
                code = rj["code"]
                if code > 0:  # 用户登录不成功
                    QMessageBox.warning(self, '警告', rj["mess"])
                else:  # 用户登录成功
                    cookie = rj["cookie"]
                    print("登录成功,cookie:%s" % str(cookie))
                    # self.close()

4.开发注册界面的执行程序registe_do.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a Registe Do Ui file'

__author__ = 'TianJiang Gui'

import sys
from PyQt5.QtWidgets import QApplication,QDialog,QFileDialog,QMessageBox,QLineEdit
from PyQt5.QtCore import Qt,pyqtSignal,QRegExp,QEvent
from PyQt5.QtGui import QFont,QIcon,QRegExpValidator
import registe_ui
import aiohttp
import asyncio

from Utils.util_Commons import *


class RegisteDo(QDialog, registe_ui.Ui_Registe):
    # 自定义注册成功信号,传递列表信息
    successful_signal = pyqtSignal(list)
    def __init__(self):
        QDialog.__init__(self)
        registe_ui.Ui_Registe.__init__(self)

        self.setupUi(self)

       # 无边框
        self.setWindowFlags(Qt.FramelessWindowHint)

        #---gtj 设置窗口透明
        self.setAttribute(Qt.WA_TranslucentBackground)

        #---gtj右下角的小拖拽
        # self.setSizeGripEnabled(True)

        # ---gtj实现引用qss功能
        self.qss()

        # self.repass_line = MyLineEdit(self)
        #---gtj 使用过滤器拦截focusOut事件
        self.repass_line.installEventFilter(self)

        # self.registe_button.installEventFilter(self)

        self.line_init()
        self.pushbutton_init()

        #---gtj实现最小化,关闭功能
        self.pushButton_min.clicked.connect(self.showMinimized)
        self.pushButton_quit.clicked.connect(self.close)


    #---gtj 实现鼠标拖拽功能
    def mousePressEvent(self, event):
        self.pressX = event.x()    #记录鼠标按下的时候的坐标
        self.pressY = event.y()

    def mouseMoveEvent(self, event):
        x = event.x()
        y = event.y()   #获取移动后的坐标
        moveX = x-self.pressX
        moveY = y-self.pressY  #计算移动了多少

        positionX = self.frameGeometry().x() + moveX
        positionY = self.frameGeometry().y() + moveY    #计算移动后主窗口在桌面的位置
        self.move(positionX, positionY)    #移动主窗口
    #---gtj qss文件引用
    def qss(self):
        self.qssfile = "./qss/dialog.qss"
        self.style = UsingQSS.loadqss(self.qssfile)
        self.setStyleSheet(self.style)

    def line_init(self):

        # 单行文本输入框内容变化绑定按钮显示
        self.name_line.textChanged.connect(self.check_input)
        self.pass_line.textChanged.connect(self.check_input)
        self.repass_line.textChanged.connect(self.check_input)
        # 设置密码显示方式
        self.pass_line.setEchoMode(QLineEdit.PasswordEchoOnEdit)
        self.repass_line.setEchoMode(QLineEdit.PasswordEchoOnEdit)
        # 注册提示
        self.name_line.setPlaceholderText('用户名为字母数字,不可为中文或特殊字符')
        self.pass_line.setPlaceholderText('密码为6到10位数字字母,首字母必须为大写')
        self.repass_line.setPlaceholderText('请再次确认密码!')
        # 设置文本框校验器
        # 姓名文本框校验器设置
        # 1、创建正则表达式限定输入内容
        name_RegExp = QRegExp("[0-9A-Za-z]*")
        # 2、创建文本框校验器
        name_validator = QRegExpValidator(name_RegExp)
        # 3、文本输入框绑定创建的校验器
        self.name_line.setValidator(name_validator)
        # 设置密码文本输入框校验器
        password_val = QRegExpValidator(QRegExp("^[A-Z][0-9A-Za-z]{10}$"))
        self.pass_line.setValidator(password_val)
        self.repass_line.setValidator(password_val)
        # 检查密码输入,验证密码输入位数,两次密码输入是否一致。

        # self.repass_line.focus_out.connect(self.check_password)
    #---gtj 事件拦截器
    def eventFilter(self, obj, event):
        if (obj == self.repass_line):
            if (event.type() == QEvent.FocusOut):
                self.check_password()
            else:
                pass
            return False #super(RegisteDo, self).eventFilter(obj, event)
        # if (obj == self.registe_button):
        #     if (event.type() == QEvent.Click):
        #         loop = asyncio.get_event_loop()
        #         loop.run_until_complete(self.register_func())

    # 按钮初始化方法
    def pushbutton_init(self):
        # 设置注册按钮为不可点击状态,绑定槽函数
        self.registe_button.setEnabled(False)
        self.registe_button.clicked.connect(self.register_func)
        # 取消按钮绑定取消注册槽函数
        self.cancel_button.clicked.connect(self.cancel_func)

    # 检查输入方法,只有在三个文本输入框都有文字时,注册按钮才为可点击状态
    def check_input(self):
        if (self.name_line.text() and self.pass_line.text()
                and self.repass_line.text() ):
            self.registe_button.setEnabled(True)
        else:
            self.registe_button.setEnabled(False)

    # 取消注册方法
    # 如果用户在注册界面输入了数据,提示用户是否确认取消注册,如未输入数据则直接退出。
    def cancel_func(self):
        # if (self.name_line.text() or self.pass_line.text()
        #         or self.repass_line.text() ):
        choice = QMessageBox.information(self,'提示','是否取消注册?',
                                QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.close()
        else:
            return
        # else:
        #     self.close()

    # 检查用户输入密码合法性方法
    def check_password(self):
        password_1 = self.pass_line.text()
        password_2 = self.repass_line.text()

        if len(password_1) < 6:
            QMessageBox.warning(self,'警告','密码位数小于6')
            self.pass_line.setText('')
            self.repass_line.setText('')
            # return 0
        else:
            if password_1 == password_2:
                pass
            else:
                QMessageBox.warning(self,'警告','两次密码输入结果不一致!')
                self.pass_line.setText('')
                self.repass_line.setText('')
                # self.pass_line.setFocus()
                # return 0
        # return 1

    # 用户注册方法
    def register_func(self):
        loop = asyncio.get_event_loop()
        params = {'name': self.name_line.text(), 'pass': self.pass_line.text()}
        url = 'http://127.0.0.1:8100/apiRegister'
        loop.run_until_complete(self.doregister(url,params))

    async def doregister(self,url,params):
        async with aiohttp.ClientSession() as session:
            headers = {'content-type': 'application/json'}
            rj={}
            async with session.post(url, json=params, headers=headers) as response:
                rj = await response.json(content_type=None)
                print("通过post_json返回的数据type=%s:%s" % (type(rj),rj))
            await session.close()
            if len(rj)>0:
                code = rj["code"]
                # 如果用户已存在,提示用户已被注册
                if code>0:#用户注册不成功
                    QMessageBox.warning(self,'警告',rj["mess"])
                else:#用户注册成功
                    choice = QMessageBox.information(self,'提示','注册成功,是否登录?',
                                            QMessageBox.Yes | QMessageBox.No)
                    # 如选择是,关闭注册页面,并在登录页面显示注册用户,密码
                    if choice == QMessageBox.Yes:
                        self.successful_signal.emit([self.name_line.text(),
                                                     self.pass_line.text()])
                        self.close()
                    # 如选择否,直接关闭注册页面。
                    else:
                        self.close()

5.开发服务端执行程序server_main.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a Server main use register or login'

__author__ = 'TianJiang Gui'

import asyncio
from aiohttp import web
from models.users import Users

async def apiLogin(request):
    data = await request.json()
    name = data["name"]
    passwd = data["pass"]
    users = Users.findAll('name=?', [name])
    if len(users) > 0:
        for user in users:
            if passwd != user.passwd:
                print('用户登录==>username: %s 密码错误!' % name)
                return web.json_response({'code': 1, 'mess': 'error passwd'})
            else:
                print('用户登录==>username: %s 登录成功!' % name)
                return web.json_response({'code': 0, 'cookie': user.id })
    else:
        print('用户登录==>username: %s 不存在!' % name)
        return web.json_response({'code': 1, 'mess': 'error no username:%s'%name})

async def apiRegister(request):
    # put或者post方式参数获取
    data = await request.json()
    name = data["name"]
    passwd = data["pass"]
    users = Users.findAll('name=?',[name])
    if len(users)>0:
        print('用户注册==>username: %s 已经存在,不能重复注册!' % name)
        return web.json_response({'code': 1, 'mess':  '%s 已经存在,不能重复注册!' % name})
    else:
        u = Users(name=name, passwd=passwd)
        uid = u.save()
        if uid > 0:
            print('插入新纪录uid=%s成功!' % uid)
            # ---gtj 按id查用户信息
            user = Users.find(uid)
            print('新插入记录内容为==>id:', user.id, 'name:', user.name, 'passwd:', user.passwd)

        try:
            return web.json_response({'code': 0, 'data': name})
        except Exception as e:
            return web.json_response({'msg': e.value}, status=500)

async def apiCookieinfo(request):
    data = await request.json()
    name = data["name"]
    print('请求get的信息data为: %s' % str(data))
    try:
        return web.json_response({'code': 0, 'data': name})
    except Exception as e:
        return web.json_response({'msg': e.value}, status=500)


if __name__ == '__main__':
    app = web.Application()
    app.add_routes([web.get('/apiLogin', apiLogin),
                    web.post('/apiRegister', apiRegister),
                    web.get('/apiCookieinfo', apiCookieinfo)])

    web.run_app(app,host='127.0.0.1',port=8100)

       服务端执行重点在于:

        1)使用aiohttp进行异步通信

               

         关于aiohttp异步通信的内容请参考我的博文:

                python小技巧大应用--用aiohttp实现HTTP C/S收发JSON数据

        2)使用util_SQLiteORM.py实现对数据库的ORM CRUD操作,users.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
Models for users.
'''

__author__ = 'TianJiang Gui'

import time, uuid

from Utils.util_SQLiteORM import Model, StringField, BooleanField, FloatField, TextField,IntegerField
#---gtj 数值型随机数
def next_id():
    return int(time.time() * 1000)

class Users(Model):
    __table__ = 'users'

    id = IntegerField(primary_key=True, default=next_id)
    name = StringField(ddl='varchar(20)')
    passwd = StringField(ddl='varchar(100)')

        关于SQLite3 ORM内容请参考我的博文:

        python小技巧大应用--实现自己的SQLite3 ORM(全) 

6.开发客户端主程序client_main.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a Cient Main using Login,Registe Ui file'

__author__ = 'TianJiang Gui'
import sys
from PyQt5.QtWidgets import QApplication
from login_do import LoginDo

cookie =0
if __name__ == '__main__':
    app = QApplication(sys.argv)
    md = LoginDo()
    md.show()
    sys.exit(app.exec_())

至此,所有内容都开发完毕,项目工程目录结构如下:

现在测试一下看看整体运行情况:

1.现将服务运行起来server_main.py

 2.运行客户端主程序client_main.py,在登录页面点击注册进入注册界面

两次密码不一致会报错

 

 注册成功会提示:

服务端显示注册成功:

 查看数据库已有数据:

进入登录页面进行登录:

 

 登录后服务端显示登录成功:

 客户端登录成功后将cookie显示在主页面:

 此处重点注意类间全局变量传递问题:(在这个问题上我用了一天才解决)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' JiDi Common Utils global var'
__author__ = 'TianJiang Gui'

def _init():
    global _global_dict
    _global_dict = {}
def set_value(name,value):
    _global_dict[name] = value

def get_value(name,defValue=None):
    try:
        return _global_dict[name]
    except KeyError:
        return defValue

最后总结:

这个实用落地项目包括了很多知识点,细节在我的其他博文中都有介绍,只是在此文中我将所有技术融汇在一个小项目中实现,建议大家亲自手动实践

1.界面开发,转化及界面代码分离;2.ORM SQLite CRUD操作;3.aiohttp 异步通信;4.全局变量cookie在客户端不同类间传递....

如果想直接使用本人开发的源码工程,也可直接到如下连接下载,不过你要请我一杯咖啡:)


下载吧:

python异步注册登录认证完整客户端服务器版

猜你喜欢

转载自blog.csdn.net/gui818/article/details/123260206