客户端-01

处理 RSA 加密和解密的工具类 :SslTool

  • SslTool 类用于执行 RSA 加密和解密操作。
  • 包含 RSA 公钥和私钥的指针。
  • 提供了 rsaEncodersaDecode 方法,用于加密和解密数据。

处理大数运算的辅助类 BigNum

  • BigNum 类封装了 OpenSSL 的 BIGNUM 类型,提供了构造、析构、赋值和转换操作。
  • 提供了从 Base64、十六进制和二进制字符串构造 BIGNUM 的方法。
  • 提供了将 BIGNUM 转换为二进制字符串和 Base64 编码的静态方法。
  • rsaEncodersaDecode 方法将使用 OpenSSL 的 RSA 功能进行数据加密和解密。
  • rsaEncode 方法将输入的 QByteArray 进行 RSA 加密,返回加密后的结果。
  • rsaDecode 方法将输入的 QByteArray 进行 RSA 解密,返回解密后的结果。

常量定义

const char* PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n...-----END PRIVATE KEY-----\n";
const char* PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n...-----END PUBLIC KEY-----\n";
const char* SERVER_PRIVATE = "-----BEGIN PRIVATE KEY-----\n...-----END PRIVATE KEY-----\n";

定义了 RSA 的私钥和公钥,通常用于加密和解密操作。

读取配置信息类:RecordFile

类成员变量

    QJsonObject m_config;  // 存储配置信息的 JSON 对象
   QString m_path;        // 配置文件的路径
   SslTool tool;         // 用于加密和解密的工具

构造函数

  1. 打开文件:
    • 使用 QFile 类打开指定路径的文件。如果打开失败,则直接跳出。
  2. 读取文件内容:
    • 读取文件的所有内容到 QByteArray 中。如果文件为空,则跳出。
  3. 解密数据:
    • 使用 tool.rsaDecode(data) 解密读取到的数据。
  4. 处理数据:
    • 使用循环去掉不必要的字符,确保数据的有效性。if((int)data[i] >= (int)0x7F || (int)data[i] < (int)0x0A)检查字节的值是否大于等于 127(十六进制的 0x7F),这是 ASCII 字符集中不可打印的字符的起始值。检查字节的值是否小于 10(十六进制的 0x0A),这通常表示换行符(\n)之前的字符。这两个条件结合起来,意味着只保留 ASCII 可打印字符(通常是 10 到 126 的范围)如果条件成立,表示找到了一个不可打印的字符,使用 data.resize(i)data 的大小调整为 i,以保留从开头到当前索引 i 的所有字节(有效部分),并丢弃后面的所有字节(无效部分)。
  5. 解析 JSON 数据:
    • 使用 QJsonDocument::fromJson 解析 JSON 数据。如果解析成功并且是一个 JSON 对象,则将其存储到 m_config 中。
  6. 设置默认值:
    • 如果读取或解析失败,则设置默认的配置项,例如用户名、密码、自动登录、记住密码和日期。

save

bool RecordFile::save()
{
   QFile file(m_path);
   if(file.open(QIODevice::WriteOnly | QIODevice::Truncate) == false)
  {
       return false; // 打开文件失败
  }
   
   QJsonDocument document = QJsonDocument(m_config);
   file.write(tool.rsaEncode(document.toJson())); // 加密后写入文件
   file.close();
   return true; // 保存成功
}
  1. 打开文件:
    • 使用 QFile 打开指定路径的文件,以写入模式和截断模式(即清空文件内容)。
  2. 加密和写入数据:
    • 创建一个 QJsonDocument 对象,将 m_config 转换为 JSON 格式,并使用 tool.rsaEncode 进行加密后写入文件。
  3. 关闭文件:
    • 关闭文件并返回 true,表示保存成功。

自定义登录窗口类:LoginForm

包含头文件

包含了主要用于创建窗口部件(QWidget),网络请求(QNetworkAccessManager和QNetworkReply),处理JSON数据(QJsonObject,QJsonParseError,QJsonDocument)和桌面服务(QDesktopServices)

功能函数

    explicit LoginForm(QWidget* parent = nullptr);
   ~LoginForm();
   virtual bool eventFilter(QObject* watched, QEvent* event);
   virtual void timerEvent(QTimerEvent* event);

eventFilter用于处理特定的事件,timerEvent用于处理定时器事件

槽功能函数

    void on_logoButton_released();
   void on_remberPwd_stateChanged(int state);
   void slots_autoLoginCheck_stateChange(int state);
   void slots_login_request_finshed(QNetworkReply* reply);

响应按钮释放事件,状态变化事件和网络请求完成事件

信号部分

signals:
   void login(const QString& nick, const QByteArray& head);

定义信号 login,用于在用户登录成功时发射。

成员变量

    Ui::LoginForm* ui;
   QNetworkAccessManager* net;
   InfoForm info;
   RecordFile* record;
   int auto_login_id;
   QPoint position;

MD5加密

const char* MD5_KEY = "*&^%$#@b.v+h-b*g/h@n!h#n$d^ssx,.kl<kl";
const char* HOST = "http://127.0.0.1:19527";
bool LOGIN_STATUS = false;

定义常量 MD5_KEY 用于加密,HOST 是服务器的地址,LOGIN_STATUS 是登录状态的标志。

构造函数

初始化基类QWidget,创建ui和record对象,设置窗口无边框,初始化UI元素的占位符文本,安装事件过滤器以及处理输入框的焦点事件,创建网络访问管理器并连接信号与槽,设置info窗口的属性,并调用load_config加载配置

事件过滤器

事件过滤器是Qt中用于处理事件的一个强大的机制,允许在事件到达之前拦截并处理这些事件

QObject* watched是被监控的对象,可能是一个文本框(ui->pwdEdit或ui->nameEdit)

通过 if(ui->pwdEdit == watched)else if(ui->nameEdit == watched),函数首先检查当前事件是否来自于密码输入框或用户名输入框。

处理 FocusIn 事件:

  • 当输入框获得焦点时(即用户点击或选中该输入框),会触发 QEvent::FocusIn 事件。
  • 在这种情况下,代码会改变输入框的样式,使文字颜色变为白色,并将背景设置为透明:ui->pwdEdit->setStyleSheet(“color: rgb(251, 251, 251);background-color: transparent;”);

处理 FocusOut 事件:

  • 当输入框失去焦点时(即用户点击其他地方或者切换到其他输入框),会触发 QEvent::FocusOut 事件。
  • 在这种情况下,代码会检查输入框中是否还有文本。如果没有文本(即用户没有输入任何内容),则将文字颜色恢复为深灰色,并将背景设置为透明:if(ui->pwdEdit->text().size() == 0)
    {
       ui->pwdEdit->setStyleSheet(“color: rgb(71, 75, 94);background-color: transparent;”);
    }

最后,函数调用 QWidget::eventFilter(watched, event),将未处理的事件传递给父类的事件过滤器。这是一个好习惯,确保其他事件处理程序也能正常工作。

登录检查

  1. 创建一个MD5哈希对象QCrytograhicHash md5(QCryptographicHash::Md5);
  2. 创建网络请求对象QNetworkRequest request;该对象用于发送HTTP请求
  3. 构造请求URLQString url = QString(HOST) +”/login?”;
  4. 生成随机的盐值QString salt = QString::number(QRandomeGenerato::global()->bounded(10000,99999));这个盐值是一个随机的四位数
  5. 获取当前的时间QString time = getTime();
  6. 生成签名md5.addData((time + MD5_KEY +pwd + salt).toUtf8);QString sign = md5.result().toHex();将时间戳、MD5 密钥、用户输入的密码和盐值拼接在一起,并计算它们的 MD5 哈希值,生成一个签名(sign)。这个签名用于验证请求的合法性。
  7. 设置请求的URLurl += “time=” + time + “&”;
    url += “salt=” + salt + “&”;
    url += “user=” + user + “&”;
    url += “sign=” + sign;将时间、盐值、用户名和签名附加到 URL 中,形成完整的请求 URL。
  8. 记录用户输入的用户名和密码record->config()[“password”] = ui->pwdEdit->text();
    record->config()[“user”] = ui->nameEdit->text();将用户输入的用户名和密码存储到配置中,以便后续使用(例如记住密码功能)。
  9. 禁用窗口
  10. 发送Get请求 通过网络访问管理器发送 GET 请求,向服务器请求登录验证
  11. 返回

加载配置

  1. 打开配置文件:QFile configFile(“config.json”);
    if (!configFile.open(QIODevice::ReadOnly)) {
       qWarning(“Could not open config file”);
       return;
    }
    • 使用 QFile 类打开名为 config.json 的配置文件。如果文件无法打开,则输出警告信息并返回。
  2. 读取文件内容:QByteArray configData = configFile.readAll();
    configFile.close();
    • 读取整个文件的内容到 QByteArray 中,并关闭文件。
  3. 解析 JSON 数据:QJsonDocument jsonDoc = QJsonDocument::fromJson(configData);
    if (jsonDoc.isNull() || !jsonDoc.isObject()) {
       qWarning(“Invalid JSON format”);
       return;
    }
    • 使用 QJsonDocument 类从读取的字节数组中解析 JSON 数据。如果解析失败,输出警告信息并返回。
  4. 获取 JSON 对象:QJsonObject jsonObj = jsonDoc.object();
    • 获取 JSON 文档中的对象,这样可以方便地访问各个配置项。
  5. 加载配置项:if (jsonObj.contains(“user”)) {
       ui->nameEdit->setText(jsonObj[“user”].toString());
    }
    if (jsonObj.contains(“password”)) {
       ui->pwdEdit->setText(jsonObj[“password”].toString());
    }
    • 检查 JSON 对象中是否包含 userpassword 字段。如果存在,就将其值设置到相应的输入框中。

鼠标事件处理

void LoginForm::mouseMoveEvent(QMouseEvent* event)
{
   move(event->globalPos() - position);
}

void LoginForm::mousePressEvent(QMouseEvent* event)
{
   position = event->globalPos() - this->pos();
}

void LoginForm::mouseReleaseEvent(QMouseEvent* /*event*/)
{
}

这些方法实现了窗口的拖动功能,记录鼠标按下时的位置,并在移动时更新窗口的位置。

class InfoForm : public QWidget
{
   Q_OBJECT

public:
   explicit InfoForm(QWidget* parent = nullptr);
   ~InfoForm();
public:
   //更新文字 每次弹出信息窗口之前,可以通过此接口来更新窗口信息内容
   InfoForm& set_text(const QString& text, const QString& button);
protected:
   virtual void mouseMoveEvent(QMouseEvent* event);
   virtual void mousePressEvent(QMouseEvent* event);
   virtual void mouseReleaseEvent(QMouseEvent* event);
signals:
   //按钮按下了(如果是关闭的窗口,自带了close信号)
   void button_clicked();
   void closed();
protected slots:
   void on_connectButton_released();
   void on_closeButton_released();
private:
   Ui::InfoForm* ui;
   QPoint position;
};