知识积累到一定时候,就应该将它们整合起来,融会贯通发挥效力.
这个项目需要实现以下技术
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异步注册登录认证完整客户端服务器版
|