给 PyQt5 注册页,添加注册逻辑代码,实现用户的注册验证

使用 PyQt5(PySide2)+SQLAlchemy 做一个登录注册页(五)

本文将介绍自己用 PyQt5+SQLAlchemy 做的一个登录注册页,使用邮箱接收验证码本文介绍是前后端未分离的实现方式,后续将出一个前后端分离的,你可以将 PyQt5 改为 PySide2 以获得更宽松的开源协议

本文由于涉及到的代码较多,将会是一个系列,会有多篇文章

系列文章索引

  1. 设计登录注册页面
  2. 添加代码运行登录注册页,并为其添加一些样式,和调用资源文件
  3. 使用 SQLAlchemy 实现用户数据库管理
  4. 为登录页,添加登录逻辑代码,实现登录
  5. 给注册页,添加注册逻辑代码,实现用户的注册验证
  6. 给忘记密码页,添加逻辑,实现密码找回
  7. 给登录添加记住用户密码功能,并优化一些内容

必要说明

  • 使用的环境 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步

  1. 添加自定义函数,实现验证生成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))

  1. 添加自定义函数,实现邮箱模板发送 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步

实现 发送邮箱验证码 逻辑

  1. 初始化配置 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))

  1. 添加必填校验方法 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

  1. 添加 邮箱格式验证 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

  1. 添加页面倒计时刷新 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

  1. 发送验证码 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()

  1. 保存验证嘛到数据库,用于后续校验使用 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步

实现注册

  1. 初始化 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)

  1. 添加必填校验方法 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

  1. 检查邮箱与账号是否已被注册 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

  1. 重复密码的验证 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)

  1. 检查验证码 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

  1. 实现注册 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)

GitHub完整代码
本文章的原文地址
GitHub主页

posted @ 2024-02-20 15:50  星尘的博客  阅读(238)  评论(0编辑  收藏  举报