给 PyQt5 注册页,添加注册逻辑代码,实现用户的注册验证
使用 PyQt5(PySide2)+SQLAlchemy 做一个登录注册页(五)
本文将介绍自己用 PyQt5+SQLAlchemy 做的一个登录注册页,使用邮箱接收验证码,本文介绍是前后端未分离的实现方式,后续将出一个前后端分离的,你可以将 PyQt5 改为 PySide2 以获得更宽松的开源协议
本文由于涉及到的代码较多,将会是一个系列,会有多篇文章
系列文章索引
- 设计登录注册页面
- 添加代码运行登录注册页,并为其添加一些样式,和调用资源文件
- 使用 SQLAlchemy 实现用户数据库管理
- 为登录页,添加登录逻辑代码,实现登录
- 给注册页,添加注册逻辑代码,实现用户的注册验证
- 给忘记密码页,添加逻辑,实现密码找回
- 给登录添加记住用户密码功能,并优化一些内容
必要说明
- 使用的环境
requirements.txt
# Python3.8.10 x32
# Windows10 x64
PyQt5
pyqt5-tools
PyMySQL~=1.1.0
sqlalchemy~=2.0.25
bcrypt~=4.1.2
email-validator # new add
- 项目结构(显示变化的部分)
--- QtLoginRegistration
|--- db
|--- schemas.py # 数据模型
|--- lib
|--- __init__.py
|--- lib.py # 库
|--- send_email.py # 发送邮件
|--- requirements.txt # 添加 pydantic,yagmail
给注册页,添加注册逻辑代码,实现用户的注册验证(本篇涉及的内容较多)
第1步
修改db/models.py
,添加一个新映射表active
from sqlalchemy import SmallInteger
class ConfirmString(Base):
"""验证码验证激活"""
__tablename__ = 'active' # 指定表名
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
email = Column(String(255), nullable=False, comment='邮箱')
activeCode = Column(String(255), nullable=False, comment='验证码')
activeValidityPeriod = Column(DateTime, nullable=False, comment='验证码有效期')
createTime: str = Column(DateTime, nullable=False, comment='创建时间')
deleted = Column(SmallInteger, nullable=False, server_default='0', comment='删除:删除1')
def __repr__(self):
return '<active %r,%r>' % (self.id, self.email)
第2步
添加db/schemas.py
,实现2个Pydantic
数据模型
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ Project : QtLoginRegistration
@ File : schemas.py
@ Author : yqbao
@ Version : V1.0.0
@ Description : 创建初始 Pydantic模型,用于关联ORM
"""
from datetime import datetime
from typing import Union
from pydantic import BaseModel, ConfigDict
# ConfirmString Pydantic模型
class ConfirmString(BaseModel):
email: str
activeCode: str
activeValidityPeriod: datetime
createTime: datetime
deleted: int = 0
model_config = ConfigDict(from_attributes=True, )
# UserRegister Pydantic模型
class UserRegister(BaseModel):
"""注册"""
username: str
password: str
email: str
name: Union[str, None] = None
sex: Union[int, None] = 1
disabled: int = 1
createTime: datetime
deleted: int = 0
model_config = ConfigDict(from_attributes=True, )
第3步
修改crud/crud.py
,添加新的数据库操作
# 导入 schemas
from db import schemas
class CRUDUser(CRUD):
def create(self, db: Session, user: schemas.UserRegister):
"""用户创建"""
db_user = models.User(**user.model_dump())
db.add(db_user)
return self._commit(db, db_user)
class CRUDConfirmString(CRUD):
@staticmethod
def get_confirm_string_by_email(db: Session, email: str) -> Union[models.ConfirmString, None]:
"""根据email获取验证码"""
return db.query(models.ConfirmString).filter(
and_(models.ConfirmString.email == email, models.ConfirmString.deleted == 0)).first()
def create(self, db: Session, confirm_string: schemas.ConfirmString):
"""创建验证码保存"""
db_confirm_string = models.ConfirmString(**confirm_string.model_dump())
db.add(db_confirm_string)
return self._commit(db, db_confirm_string)
def update(self, db: Session, email: str, update_data: dict):
"""更新"""
db_confirm_string = db.query(models.ConfirmString).filter(
and_(models.ConfirmString.email == email, models.ConfirmString.deleted == 0)).update(update_data)
return self._commit(db, db_confirm_string)
第4步
- 添加自定义函数,实现验证生成
lib/lib.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ Project : QtLoginRegistration
@ File : lib.py
@ Author : yqbao
@ Version : V1.0.0
@ Description :
"""
import string
from random import randint, choices
def check_code() -> str:
"""六位验证码数字加大写字母"""
code: str = ''
for i in range(6):
current = randint(0, 6)
temp = chr(randint(65, 90)) if current != i else randint(0, 9)
code += str(temp)
return code
def check_code2() -> str:
"""六位验证码数字加大写字母"""
return ''.join(choices(string.ascii_uppercase + string.digits, k=6))
- 添加自定义函数,实现邮箱模板发送
lib/send_email.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@ Project : QtLoginRegistration
@ File : send_email.py
@ Author : yqbao
@ Version : V1.0.0
@ Description :
"""
import smtplib
import yagmail
from setting import CONFIG
class BasicEmail(object):
def __init__(self, user, password, host='smtp.qq.com'):
"""使用的 QQ邮箱服务"""
self.yag = yagmail.SMTP(user=user, password=password, host=host)
def send(self, subject, contents: list, to: list, attachments: list = None):
"""发送"""
self.yag.send(subject=subject, contents=contents, to=to, attachments=attachments)
def close(self):
"""关闭"""
self.yag.close()
class SendEmail(BasicEmail):
def __init__(self):
user = CONFIG.EMAIL_USERNAME
password = CONFIG.EMAIL_PASSWORD
super().__init__(user, password)
def send_active_email(self, captcha, to: str):
"""发送激活邮件"""
subject = 'QtLoginRegistration 邮箱验证码:{}'.format(captcha)
contents = [
'''<div style="width:500px;margin: auto;">
您好!<br>
欢迎使用并注册 QtLoginRegistration<br>
这是你的邮箱激活验证码,有效期为 <b>5</b> 分钟,且仅可使用一次,过期或使用后均将失效。<br>
如你同意激活,请将下方的六位数验证码,输入到 QtLoginRegistration 邮箱确认框中<br>
<P><b style="font-size:20px">{}</b><P>
若你没有注册 QtLoginRegistration 且收到了此邮件,则请忽略,因为本邮件并不会有任何的安全隐患!<br>
祝福您<br>
QtLoginRegistration 开发团队<br>
</div>'''.format(captcha)
]
to = [to, ]
try:
self.send(subject=subject, contents=contents, to=to)
except smtplib.SMTPDataError as e:
print(e)
finally:
self.close()
第5步
实现 发送邮箱验证码 逻辑
- 初始化配置 GitHub完整代码
# 导入
import datetime
from threading import Thread
from PyQt5.QtCore import QTimer
from db import models
from crud.crud import CRUDConfirmString
from lib.lib import check_code
from lib.send_email import SendEmail
# 在 构造方法 __init__ 中实例化类
self.confirm_string = CRUDConfirmString()
self.send_email = SendEmail()
# 定义新的类方法,初始化一个QTimer,用于实现验证码发送60秒倒计时
def init_time(self):
self.count = 60
self.time = QTimer(self)
self.time.setInterval(1000)
# 将 init_time 在 init_ui 中初始化调用
self.init_time()
# 在 init_ui 中绑定注册页发送验证码按钮信号
self.pushButtonSend.clicked.connect(lambda: self.send_captcha(self.lineEdit_2, self.pushButtonSend))
- 添加必填校验方法 GitHub完整代码
def send_captcha_required(self, email_line_edit):
self.email = email_line_edit.text()
if not self.email.strip():
self.basic_function.info_message("邮箱地址不能为空")
return False
return True
- 添加 邮箱格式验证 GitHub完整代码
def check_email_format(self):
"""邮箱格式校验"""
try:
info = validate_email(self.email, check_deliverability=False)
self.email = info.normalized
return True
except EmailNotValidError:
self.basic_function.info_message("邮箱格式不正确,请重新输入")
return
- 添加页面倒计时刷新 GitHub完整代码
def refresh_time(self, captcha_push_button):
if self.count > 0:
captcha_push_button.setText(str(self.count) + '秒后重发')
self.count -= 1
else:
self.time.stop()
captcha_push_button.setEnabled(True)
captcha_push_button.setText('发送')
self.count = 60
- 发送验证码 GitHub完整代码
def send_captcha(self, email_line_edit, captcha_push_button):
"""发送验证码"""
if not self.send_captcha_required(email_line_edit) or not self.check_email_format(): # 数据校验
return
self.time.timeout.connect(lambda: self.refresh_time(captcha_push_button))
if captcha_push_button.isEnabled():
self.time.start()
captcha_push_button.setEnabled(False)
captcha = check_code()
self.save_captcha(captcha)
# 发送邮件是一个耗时操作,为了避免阻塞,于是定义一个线程来实现
thread = Thread(
target=self.send_email.send_active_email,
args=(captcha, self.email,)
)
thread.start()
- 保存验证嘛到数据库,用于后续校验使用 GitHub完整代码
def save_captcha(self, captcha):
"""更新数据库验证码"""
active_valid_time = (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime("%Y-%m-%d %H:%M:%S")
confirm_string = schemas.ConfirmString(
email=self.email, activeCode=captcha, activeValidityPeriod=active_valid_time,
createTime=datetime.datetime.now())
with session_factory() as db:
# 每点击一次发送,就将旧的删除
self.confirm_string.update(db, self.email, {models.ConfirmString.deleted: 1})
# 保存激活信息
self.confirm_string.create(db, confirm_string)
第6步
实现注册
- 初始化 GitHub完整代码
# 导入
from db import models, schemas
# 在 init_ui 中绑定注册页发送验证码按钮信号
self.lineEdit_3.textEdited.connect(lambda: self.check_password(self.lineEdit_3, self.lineEdit_4, self.label_9, self.pushButtonRegister))
self.lineEdit_4.textEdited.connect(lambda: self.check_password(self.lineEdit_3, self.lineEdit_4, self.label_9, self.pushButtonRegister))
self.pushButtonRegister.clicked.connect(self.register)
- 添加必填校验方法 GitHub完整代码
def register_required(self):
"""注册必填校验"""
self.username = self.lineEdit.text()
self.captcha = self.lineEdit_5.text()
self.email = self.lineEdit_2.text()
self.password = self.lineEdit_3.text()
if not self.username.strip():
self.basic_function.info_message("账号不能为空")
return False
elif not self.email.strip():
self.basic_function.info_message("邮箱地址不能为空")
return False
elif not self.password.strip():
self.basic_function.info_message("用户密码不能为空")
return False
elif not self.repeat_password.strip():
self.basic_function.info_message("重复密码不能为空")
return False
elif not self.captcha.strip():
self.basic_function.info_message("邮箱验证码不能为空")
return False
return True
- 检查邮箱与账号是否已被注册 GitHub完整代码
def check_email_username_unique(self):
"""检查邮箱与账号是否已被注册"""
with session_factory() as db:
get_email = self.user.get_user_by_email(db, self.email)
get_username = self.user.get_user_by_username(db, self.username)
if get_email:
self.basic_function.info_message("邮箱地址已存在")
return
if get_username:
self.basic_function.info_message("账号已存在")
return
return True
- 重复密码的验证 GitHub完整代码
def check_password(self, password_line_edit, old_password_line_edit, label, push_button):
"""重复密码的验证"""
self.password = password_line_edit.text()
self.repeat_password = old_password_line_edit.text()
if self.password != self.repeat_password:
label.setStyleSheet("color: rgb(255, 0, 0);")
label.setText("两次密码输入不一致,重新输入!")
push_button.setEnabled(False)
return
label.setText("")
push_button.setEnabled(True)
- 检查验证码 GitHub完整代码
def check_captcha(self):
"""检查验证码"""
with session_factory() as db:
get_active_code = self.confirm_string.get_confirm_string_by_email(db, self.email)
if not get_active_code:
self.basic_function.info_message("验证码已失效,请重新发送邮件获取")
return
time_now = datetime.datetime.now()
time_diff = get_active_code.activeValidityPeriod - time_now
if not time_diff.seconds <= 5 * 60 or time_diff.days < 0:
self.basic_function.info_message("验证码已过期,请重新发送邮件获取")
self.confirm_string.update(db, self.email, {models.ConfirmString.deleted: '1'})
return
if self.captcha != get_active_code.activeCode:
self.basic_function.info_message("验证码不正确,请重新输入")
return
self.confirm_string.update(db, self.email, {models.ConfirmString.deleted: '1'})
return True
- 实现注册 GitHub完整代码
def register(self):
"""注册动作"""
if (not self.register_required() or
not self.check_email_format() or
not self.check_email_username_unique() or
not self.check_captcha()): # 数据校验
return
bytes_my_password = bytes(self.password, encoding="utf-8")
bytes_my_hash_password = bcrypt.hashpw(bytes_my_password, bcrypt.gensalt(rounds=13))
str_my_hash_password = bytes.decode(bytes_my_hash_password)
user = schemas.UserRegister(
username=self.username, password=str_my_hash_password, email=self.email, createTime=datetime.datetime.now())
with session_factory() as db:
self.user.create(db, user)
# 注册成功后,判断是否选中直接登录,若未选中,则切换会登录页
if self.checkBox_2.isChecked():
self.accept()
else:
self.stackedWidget.setCurrentIndex(0)
本文来自博客园作者:星尘的博客,转载请注明出处:https://www.cnblogs.com/yqbaowo/p/18021669