写在前面
笔者刚刚完成对网络传输代码的理解,现在已经快马加鞭开始做打卡板块的数据传输了,这一部分主要是时间的问题,真正的数据很少。本篇涉及到tcp服务器客户端通信、服务器调度数据库操作、调用系统时钟API。
这一篇博客是笔者重大突破,因为他再一次超出了我的能力范围,我只有一步一步做下去。通过这一篇笔者既希望让读者可以更深入理解上一篇没讲清楚的服务器、客户端、数据库间的联系,又希望让我自己加强这部分的实力。
需求分析
以界面为参考分析要实现的功能:

这个板块需要存储的数据:连续达标总天数、当前日期、当天打卡的每一条数据、当天总时长、当天是否达标
其他功能:
-
调用系统API输出当天日期
-
系统自己判断是否到了第二天,来决定是否清空打卡数据并计算连续天数
所以本篇博客要实现的功能有:
-
新建一条时长数据,并把它存到数据库中
-
修改选中时长数据,并把它存到数据库中
-
删除选中时长数据,并把它存到数据库中
-
显示当天日期
-
存储连续达标总天数
-
根据当前系统时间来判断是否到了第二天,并清空打卡数据且根据是否达标计算连续天数
-
存储是否达标的标记
-
存储当前日期
思路梳理
(1)功能完成方式
先完成显示当天日期:使用一个函数调用当前日期,并展示到输出框
注意要把图中几个输出框全部改成只读模式
接着实现存储,连续达标天数的存储与其他的不太一样,所以暂时不讨论;打卡数据与当天总时长是绑定关系,所以这两个要同时动;是否达标的标记只需要修改时存储
接着就是如何通过系统时间判断到了第二天,来决定连续打卡天数,这一部分学长给了一个巧妙的方法:数据库中存当前日期,然后搜当前日期-1的日期,能搜到再看是否完成的标记,搜不到直接清零
差不多了,接下来就是实现顺序
(2)实现顺序
由于还没开数据库中对应的表,所以需要开表。开表经过我们的讨论,最终确定的版本是:开一个存事件相关的表,存当天的事件及起始时间;再开一个时间相关的表,存当天日期、是否达标、当天打卡总时长,这两个表都需要用打卡日期和用户名来关联。计算连续时间就是再开一个触发器,触发时机是向时间相关的表插入新纪录之前,会查询用户前一天的记录,然后判断连续时长
数据库开好了之后就再做客户端部分,先把输出框只读模式设置好,然后调用系统时钟API来输出当天日期
实现数据传输需要考虑多个方面和节点:修改一天内的事件及其起止时间、显示当前连续打卡天数、导出当天打卡内容、显示和修改当天总时长,这一部分放到"具体实现"里讨论
具体实现
(1)开数据库的表
sql
use tick_task;
create table clock_date(
-- 某一天的打卡
Account VARCHAR(30) NOT NULL,
CDate DATE NOT NULL,
IsStandard BOOLEAN DEFAULT 0,
CFullTime TIME,
RunningDays INT NOT NULL,
FOREIGN KEY(Account) REFERENCES Account(Account),
CONSTRAINT unique_account_cdate UNIQUE (Account, CDate)
);
create table clock_event(
-- 某一天打卡里的所有事件
Account VARCHAR(30) NOT NULL,
CDate DATE NOT NULL,
EName VARCHAR(40) NOT NULL,
EStartTime Time NOT NULL,
EEndTime Time NOT NULL,
FOREIGN KEY(Account,CDate) REFERENCES clock_date(Account,CDate) ON DELETE CASCADE,
CONSTRAINT unique_account_cdate_ename UNIQUE (Account, CDate, Ename)
);
DELIMITER //
CREATE TRIGGER check_running_days
BEFORE INSERT ON clock_date
FOR EACH ROW
BEGIN
DECLARE prev_running_days INT;
-- 查询前一天的记录(如果有)
SELECT RunningDays INTO prev_running_days
FROM clock_date
WHERE Account = NEW.Account
AND CDate = DATE_SUB(NEW.CDate, INTERVAL 1 DAY)
AND IsStandard = TRUE
LIMIT 1;
-- 如果查到前一天记录且达标,则继承 RunningDays + 1
-- 否则 RunningDays = 0
IF prev_running_days IS NOT NULL THEN
SET NEW.RunningDays = prev_running_days + 1;
ELSE
SET NEW.RunningDays = 0;
END IF;
END;
//
DELIMITER ;
这段代码是我们讨论之后确定的最终版:clock_date表存打卡日期、用户、当天是否完成、连续打卡天数;colck_event表存用户、打卡日期、任务名、起止时间;check_running_days触发器用于判断连续打卡天数
存储逻辑是:用户存一条打卡之后,把这条打卡存到clock_event里,然后更新clock_date里的当天总时长;修改删除同理;更新天数的话,我的想法是:先判断当天日期,然后在数据库查,能查到就改这条,查不到就开新的,注意这一步要放在刚刚登陆成功之后,因为用户打开打卡板块就应该看到连续打卡时长的更新,而不是在更新了打卡信息之后才跟着更新连续打卡天数,而且还要通过当天是不是第二天来决定是否展示打卡信息
(2)实现当前日期输出
先在构造函数里设置输出框的只读模式
cpp
//设置输出框的只读模式
ui->daysTitleEdit->setReadOnly(true);
ui->dataYearShowEdit->setReadOnly(true);
ui->dataMonthShowEdit->setReadOnly(true);
ui->dataDayShowEdit->setReadOnly(true);
ui->hourShowEdit->setReadOnly(true);
ui->minuteShowEdit->setReadOnly(true);
然后实现日期输出框的内容
cpp
//这个函数的作用是展示当天日期
void Clock::showCurrentDate()
{
QDateTime currentDateTime = QDateTime::currentDateTime();
QDate currentDate = currentDateTime.date();
int currentYear=currentDate.year();
int currentMonth=currentDate.month();
int currentDay=currentDate.day();
ui->dataYearShowEdit->setText(QString::number(currentYear));
ui->dataMonthShowEdit->setText(QString::number(currentMonth));
ui->dataDayShowEdit->setText(QString::number(currentDay));
}
注意这个函数一定要在构造函数中调用
(3)实现根据日期选择处理方式
这一步骤主要着眼于clock_date表,实现顺序与逻辑是:
在程序运行一开始,通过函数查找表中是否存在当天日期,若存在就把这天的数据导出,包括clock_date表存的连续天数、当天总时长、是否达标标记 、当天打卡信息
如果不存在,那就向clock_date表中存一行,触发器自动算连续时长,这个不需要我们做,当天总时长存为零,然后展示空白内容就可以了
当然查找过程是很复杂的,不可能通过一个函数就实现,这需要各个类间和服务器方面的协同
前期有一个准备工作:
cpp
typedef struct _event_
{
QString EventName;
QTime StartTime;
QTime EndTime;
}Event;
typedef struct _clock_
{
QString account;
QDate CurrentDate;
bool IsStandard;
QTime FullTime;
int RunningDays;
QVector<Event> allevents;
}ClockClass;
ClockClass currentClock;
这是声明在Clock类里的两个结构体,Event结构体存的是一个一个子任务,这个结构体起到的是临时存储的作用,所以只需要在用的时候声明就可以;ClockClass结构体存的是当天的数据,由于后续需要借助这个结构体进行客户端服务器的数据交互,所以就给它开一个全局变量,其中的数组存的是Event开的临时变量,不过这里不需要考虑临时变量被销毁数组就寄了的问题,因为数组存的是对象副本。
然后就可以干正事了,开一组函数,作用是查找当前日期在数据库中有没有储存
先在打卡类里写一个监测函数,这个函数的作用只有获取当天日期和向客户端类发送一个信号,然后在客户端类里设置一个函数来捕获这个信号,再把获取到的日期传给服务器,客户端发送信号方面就做完了
此为Clock里的函数:
cpp
//这个函数的作用是判断当前日期在数据库中有没有存下
void Clock::hasDateInDatabase()
{
QDateTime currentDateTime = QDateTime::currentDateTime();
QDate currentDate = currentDateTime.date();
//信号被TcpClient类的GetClockClient函数接收
emit decideCurDate(currentDate);
}
这个函数应当在收到登录成功信号之后被调用
此为TcpClient里的函数:
cpp
//负责连接主界面与客户端函数的函数-----------------------------------------------具体功能还没连接完成
void TcpClient::GetClockClient(Clock *Cparameter)
{
m_ClockClient = Cparameter;
if(m_ClockClient)
{
connect(m_ClockClient,&Clock::decideCurDate,this,&TcpClient::DecideNewDate);
}
}
//这个函数的作用是将获取的当天时间送到服务器端
void TcpClient::DecideNewDate(QDate curDate)
{
SendCurDate(curDate);
}
//这个函数的作用是将获取的当天时间送到服务器端
void TcpClient::SendCurDate(QDate curDate)
{
// 将QDate转换为字符串格式
QString dateStr = curDate.toString("yyyy-MM-dd");
// 构造JSON数据,包含账号和日期
QJsonObject jsonData;
jsonData["account"] = currentuser;
jsonData["date"] = dateStr;
// 将JSON转换为字节数组
QByteArray data = QJsonDocument(jsonData).toJson(QJsonDocument::Compact);
// 构造TransPack数据包
unsigned char istoken = CLIENT_DATE_CHECK;
quint32 packLength = sizeof(istoken) + sizeof(quint32) + sizeof(quint32) + data.size();
quint32 checkNum = 0; // 根据需求实现校验逻辑
// 逐字段序列化到QByteArray
QByteArray sendData;
QDataStream stream(&sendData, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian); // 网络字节序
stream << istoken; // 写入IsToken
stream << packLength; // 写入packLength
stream << checkNum; // 写入checkNum
stream.writeRawData(data.constData(), data.size()); // 写入packData
m_socket->write(sendData);
}
模块函数还是个半成品,但是后两个函数已经实现了将当天日期发送给服务器,现在做服务器的接收方面
服务器关键点在于捕获客户端发送的数据以及调用数据库查找,再把结果返回给客户端
cpp
//这个函数的作用是判断当前日期是否在数据库中存在,最终发送检查结果给客户端
void ClientWorker::IsDateRepeat(QByteArray packdata)
{
qDebug() << "注意这里是服务器的调试信息,表示服务器接收到客户端传来的时间消息";
// 解析JSON数据
QJsonDocument jsonDoc = QJsonDocument::fromJson(packdata);
if (jsonDoc.isNull() || !jsonDoc.isObject()) {
qDebug() << "Invalid JSON format in date check request";
sendErrorResponse("Invalid JSON format");
return;
}
QJsonObject jsonObj = jsonDoc.object();
QString account = jsonObj["account"].toString();
QString dateStr = jsonObj["date"].toString();
QDate curDate = QDate::fromString(dateStr, "yyyy-MM-dd");
if (account.isEmpty() || !curDate.isValid()) {
qDebug() << "Invalid account or date format";
sendErrorResponse("Invalid account or date format");
return;
}
// 先使用 CheckDateExist 判断是否存在
ClockDbWorker clockWorker;
bool isDateExist = clockWorker.CheckDateExist(account, curDate);
clockWorker.isDateRepeat(account,curDate,isDateExist);
// 构造响应
QJsonObject responseJson;
responseJson["account"] = account;
responseJson["date"] = dateStr;
responseJson["exists"] = isDateExist;
if (isDateExist) {
// 仅在确认存在后才获取完整数据
ClockClass clockData;
if (clockWorker.GetClockData(account, curDate, &clockData)) {
responseJson["isStandard"] = clockData.IsStandard;
responseJson["fullTime"] = clockData.FullTime.toString("HH:mm:ss");
responseJson["runningDays"] = clockData.RunningDays;
// 添加事件列表
QJsonArray eventArray;
for (const Event& event : clockData.allevents) {
QJsonObject eventObj;
eventObj["eventName"] = event.EventName;
eventObj["startTime"] = event.StartTime.toString("HH:mm");
eventObj["endTime"] = event.EndTime.toString("HH:mm");
eventArray.append(eventObj);
}
responseJson["events"] = eventArray;
} else {
// 理论上不会执行,因为已经通过 CheckDateExist 确认存在
qWarning() << "日期存在但无法获取数据,可能数据库状态不一致";
}
}
unsigned char istoken = isDateExist ?
CLIENT_DATE_CHECK_REPEAT : CLIENT_DATE_CHECK_SUCCESSFUL;
QByteArray responseData = QJsonDocument(responseJson).toJson(QJsonDocument::Compact);
QByteArray sendData;
PreSendData(sendData, responseData, istoken);
sendResponse(sendData);
qDebug()<<"这是服务器端的调试信息,表示已经返回当前日期是否重复的结果";
emit DateChecked(account, curDate, isDateExist);
}
void ClientWorker::sendErrorResponse(const QString& message)
{
QJsonObject errorJson;
errorJson["error"] = message;
QByteArray responseData = QJsonDocument(errorJson).toJson(QJsonDocument::Compact);
QByteArray sendData;
PreSendData(sendData, responseData, CLIENT_DATE_CHECK_FAILED);
sendResponse(sendData);
}
这两个函数加调用学长早就写好的工具函数可以实现功能
cpp
//这个函数的作用是检查当天日期是否出现过
bool ClockDbWorker::CheckDateExist(QString account,QDate curDate)
{
qDebug()<<"注意这里是数据库函数的调试信息,表示服务器正在从数据库中查找信息";
qDebug() << "[DateDBWorker] Checking if date exists:" << curDate.toString("yyyy-MM-dd");
QSqlDatabase db = DbConnectionPool::instance().getConnection();
QString connName = db.connectionName();
QSqlQuery query(db);
query.prepare("SELECT COUNT(*) FROM clock_date WHERE Account = :account AND CDate = :date");
query.bindValue(":account", account); // 添加账户参数
query.bindValue(":date", curDate.toString("yyyy-MM-dd"));
if (!query.exec()) {
qWarning() << "[DateDBWorker] Query execution failed:" << query.lastError().text();
DbConnectionPool::instance().releaseConnection(connName);
return false;
}
if (query.next()) {
int count = query.value(0).toInt();
DbConnectionPool::instance().releaseConnection(connName);
return count > 0; // 返回是否存在
}
DbConnectionPool::instance().releaseConnection(connName);
return false;
}
//这个函数的作用是判断是否有重复日期,没有的话就在clock_date中新开一行
void ClockDbWorker::isDateRepeat(const QString& account, QDate curDate, bool isDateRepeat)
{
if (!isDateRepeat) { // 仅在日期不存在时执行插入操作
qDebug() << "[ClockDbWorker] Inserting new clock date record for:" << account << curDate;
QSqlDatabase db = DbConnectionPool::instance().getConnection();
QString connName = db.connectionName();
qDebug() << "Connection name:" << connName;
QSqlQuery query(db);
query.prepare(R"(
INSERT INTO clock_date (Account, CDate, IsStandard, CFullTime, RunningDays)
VALUES (:account, :cdate, :isStandard, :fullTime, :runningDays)
)");
// 绑定基本字段
query.bindValue(":account", account);
query.bindValue(":cdate", curDate);
query.bindValue(":isStandard", false); // 默认不达标
query.bindValue(":fullTime", QTime(0, 0, 0)); // 默认00:00:00
// RunningDays由触发器自动计算,这里可以给默认值(会被触发器覆盖)
query.bindValue(":runningDays", 0);
if (!query.exec()) {
qWarning() << "[ClockDbWorker] Failed to insert clock date:"
<< query.lastError().text();
} else {
qDebug() << "[ClockDbWorker] Successfully inserted new clock date record";
}
DbConnectionPool::instance().releaseConnection(connName);
qDebug()<<"没有查到有当前日期,所以数据库中新建了一行数据";
}
}
这个函数是具体实现从数据库中检索的
cpp
//这个函数的作用是获取当天的信息
bool ClockDbWorker::GetClockData(const QString& account, const QDate& date, ClockClass* clockData)
{
QSqlDatabase db = DbConnectionPool::instance().getConnection();
QString connName = db.connectionName();
bool result = false;
// 先查询clock_date表
QSqlQuery query(db);
query.prepare("SELECT * FROM clock_date WHERE Account = :account AND CDate = :date");
query.bindValue(":account", account);
query.bindValue(":date", date.toString("yyyy-MM-dd"));
if (query.exec() && query.next()) {
clockData->account = query.value("Account").toString();
clockData->CurrentDate = date;
clockData->IsStandard = query.value("IsStandard").toBool();
clockData->FullTime = QTime::fromString(query.value("CFullTime").toString(), "HH:mm:ss");
clockData->RunningDays = query.value("RunningDays").toInt();
// 查询clock_event表获取所有事件
QSqlQuery eventQuery(db);
eventQuery.prepare("SELECT * FROM clock_event WHERE Account = :account AND CDate = :date");
eventQuery.bindValue(":account", account);
eventQuery.bindValue(":date", date.toString("yyyy-MM-dd"));
if (eventQuery.exec()) {
while (eventQuery.next()) {
Event event;
event.EventName = eventQuery.value("EName").toString();
event.StartTime = QTime::fromString(eventQuery.value("EStartTime").toString(), "HH:mm:ss");
event.EndTime = QTime::fromString(eventQuery.value("EEndTime").toString(), "HH:mm:ss");
clockData->allevents.append(event);
}
}
result = true;
}
DbConnectionPool::instance().releaseConnection(connName);
return result;
}
这个函数的作用是从数据库中找到指定日期的数据并把它存到结构体中
代码具体实现方法我也讲不明白,一方面是我还没学,另一方面这是我把整个工程喂给豆包之后让它模仿着写的,所以正确性我也不确定,只能待会儿测试来看
现在再做客户端的接收,只要收到重复的令牌,就展示当天的数据,如果收到不重复的令牌,只需要关注连续打卡天数,需要传回连续打卡天数,还要在服务器方面开一行新的数据
cpp
void TcpClient::getCurDate(const QByteArray &data)
{
// 日期存在:解析数据并发送带数据的信号
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
if (!jsonDoc.isNull() && jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
ClockClass clockData;
clockData.account = jsonObj["account"].toString();
clockData.CurrentDate = QDate::fromString(jsonObj["date"].toString(), "yyyy-MM-dd");
clockData.IsStandard = jsonObj["isStandard"].toBool();
clockData.FullTime = QTime::fromString(jsonObj["fullTime"].toString(), "HH:mm:ss");
clockData.RunningDays = jsonObj["runningDays"].toInt();
QJsonArray eventArray = jsonObj["events"].toArray();
for (const auto& eventValue : eventArray) {
QJsonObject eventObj = eventValue.toObject();
Event event;
event.EventName = eventObj["eventName"].toString();
event.StartTime = QTime::fromString(eventObj["startTime"].toString(), "HH:mm");
event.EndTime = QTime::fromString(eventObj["endTime"].toString(), "HH:mm");
clockData.allevents.append(event);
}
emit RepeatSaveSuccess(clockData); // 发送带数据的信号
} else {
emit RepeatSaveFailed(); // 解析失败
}
}
这是客户端类的分支处理函数,得到了当天的数据,再把数据用信号带着传给打卡板块类
cpp
//这个函数的作用是在当天日期已经存在的情况下展示当天的数据
void Clock::showCurData(ClockClass clockdata)
{
int hour=clockdata.FullTime.hour();
int minute=clockdata.FullTime.minute();
ui->daysTitleEdit->setText(QString::number(clockdata.RunningDays));
ui->hourShowEdit->setText(QString::number(hour));
ui->minuteShowEdit->setText(QString::number(minute));
if(clockdata.IsStandard==false)
{
ui->clock_isSuccessChoice->setCurrentIndex(0);
}
else
{
ui->clock_isSuccessChoice->setCurrentIndex(1);
}
for (const Event& event : clockdata.allevents) { // 遍历所有事件节点
// 获取事件数据
QTime startTime = event.StartTime;
QTime endTime = event.EndTime;
QString eventName = event.EventName;
// 插入新行到模型(若模型为空,先初始化行计数)
int currentRow = model->rowCount();
model->insertRow(currentRow);
// 创建表格项并设置数据
// 第 0 列:开始时间
QStandardItem* startItem = new QStandardItem(startTime.toString("HH:mm"));
model->setItem(currentRow, 0, startItem);
// 第 1 列:结束时间
QStandardItem* endItem = new QStandardItem(endTime.toString("HH:mm"));
model->setItem(currentRow, 1, endItem);
// 第 2 列:事件名称
QStandardItem* nameItem = new QStandardItem(eventName);
model->setItem(currentRow, 2, nameItem);
}
}
这个函数是得到有重复信号之后的函数,展示获取到的信息
cpp
//这个函数的作用是收到日期不存在的信号之后只把连续天数传给打卡
void TcpClient::getRunningsDay(const QByteArray &data)
{
qDebug()<<"我们已经成功接收了服务器的板块,是的,当前日期是不重复的";
// 日期不存在:解析前一天数据(如果有)并发送带预期连续天数的信号
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
if (!jsonDoc.isNull() && jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
QString account = jsonObj["account"].toString();
QDate curDate = QDate::fromString(jsonObj["date"].toString(), "yyyy-MM-dd");
// 检查是否有前一天数据
if (jsonObj.contains("prevDate") && jsonObj.contains("prevIsStandard") &&
jsonObj.contains("expectedRunningDays")) {
QDate prevDate = QDate::fromString(jsonObj["prevDate"].toString(), "yyyy-MM-dd");
bool prevIsStandard = jsonObj["prevIsStandard"].toBool();
int expectedRunningDays = jsonObj["expectedRunningDays"].toInt();
// 构造前一天的简略打卡数据
ClockClass prevClockData;
prevClockData.account = account;
prevClockData.CurrentDate = prevDate;
prevClockData.IsStandard = prevIsStandard;
prevClockData.RunningDays = expectedRunningDays - 1; // 前一天的实际连续天数
// 发送预期连续天数信号
emit RunningDaysCalculated(account, curDate, prevClockData, expectedRunningDays);
} else {
// 没有前一天数据(可能是首次打卡)
emit RunningDaysCalculated(account, curDate, ClockClass(), 0);
}
} else {
emit RunningDaysCalculationFailed(); // 解析失败
}
}
这是TcpClient类里的函数,把连续天数获取并发出信号
cpp
//这个函数的作用是收到日期不重复信号之后展示连续天数的函数
void Clock::showRunningsDay(const QString& account, const QDate& curDate, const ClockClass& prevClockData, int expectedRunningDays)
{
ui->daysTitleEdit->setText(QString::number(expectedRunningDays));
}
打卡类的函数收到信号之后,在界面展示连续时长
这里插入一点更改内容,笔者在做完新建修改功能之后才想起这个问题,因为新建修改传的当天总时长是依靠int型的hour和minute,所以在读取当天信息的时候,应该把这两个数据也相应赋值
(4)实现打卡时间条的新建和修改
在梳理清楚逻辑之后,再做这个就很简单了,况且还有待办板块的模板可以直接抄,只不过要注意,这里更新了打卡的数据信息时,当天总时长也会跟着更新,还有当天是否达标也涉及到修改,需要实时传递信息
首先,做当天是否达标的修改,核心逻辑就是:修改触发加个槽函数,这个槽函数发送一个带布尔、账号、日期的信号,然后把这个信号传给TcpClient函数,TcpClient接收之后打包信息发给服务器,服务器接收之后通过查找clock_date表来修改这个数据。这部分很简单,代码上比刚才还简单,所以就不附了,反正也是AI写的
接着,再做新建时间段时的操作,核心逻辑是:在原来写前端的槽函数里加个信号,信号传时间段数据、当前日期、当天总时长,然后被TcpClient捕获,然后把时间段数据、当前日期、当天总时长、令牌传给服务器,服务器方面收到消息后调用数据库函数往其中添加一行,代码同样不附了
最后做修改操作,这部分和新建差不多,只是数据库方面改一下,令牌类型改一下就好
(5)补充服务器界面的消息提示
先确定提醒消息类型,参考学长写完的登录、注册、待办,可以确定需要提醒的有:打卡数据已被读取,打卡数据修改,打卡数据新建,其他的用不上
然后之前写代码的时候我就释放信号了,一直没用,就在这个地方起作用了
cpp
connect(m_TcpServer, &TCPServer::ClockDataReadSuccess, this, [=](QString account){
logWithTimestamp(tr("用户 '%1' 打卡数据读取成功!").arg(account));
});
connect(m_TcpServer, &TCPServer::ClockDataModifySuccess, this, [=](QString account){
logWithTimestamp(tr("用户 '%1' 打卡数据修改成功!").arg(account));
});
connect(m_TcpServer, &TCPServer::ClockDataCreateSuccess, this, [=](QString account){
logWithTimestamp(tr("用户 '%1' 打卡数据新建成功!").arg(account));
});
我说一下这里的逻辑:TcpServer是真正实现多线程基础的地方,它和服务器界面类联动,发出信号被界面捕获然后输出内容,它又接着ClientWorker类发出的同名信号(同名是为了方便)
实现逻辑就是:真正干事的ClientWorker类函数做完发出信号给TcpServer,TcpServer收到信号再发一个同名信号给服务器界面类,界面类输出信息
篇末总结
首先先感慨一下,AI简直是本世纪最伟大的发明,它大大降低了计算机做项目的门槛,提高了开发效率。可是这不代表AI会取代程序员,因为现在的AI局限性仍很大,程序员拥抱AI会过得更好,摒弃AI只有被使用AI的人淘汰掉,在AI的帮助下,加上已经有了的函数模板,只要搞清楚逻辑,写数据传输也是一件很简单的事情,难的只是从无到有
然后补充上一篇的一个疏漏:信号传递中"令牌"起到了非常重要的作用。它在我的理解中就是"服务器和客户端间的信号",与Qt的信号与槽实现功能形式很类似
这一点很重要:晚上睡觉突然顿悟了,理清楚了程序运行的真正逻辑,之后写代码就丝滑多了:
板块发信号给tcpclient
->tcpclient打包数据带着请求令牌发给服务器
->服务器收到数据和请求令牌调用tcpclient的函数
->tcpclient的函数再调用clockdbwoker里操作数据库的函数,然后返回结果令牌
->服务器打包结果令牌和数据传给客户端
->客户端tcpclient解析数据根据结果令牌类型再发信号给板块类
->板块类收到信号再操作ui
通过学长的代码我又明白了一些代码结构上的事情:函数以功能分类,通过信号和槽、直接调用、令牌互传来调用函数,本质思想是通过条件互传来实现连接的