[add]添加远程平台数据同步服务
This commit is contained in:
parent
65f83b476d
commit
a8cb3c3b1f
@ -8,7 +8,8 @@ SUBDIRS += \
|
||||
sys_file_sync \
|
||||
sys_startup \
|
||||
sys_svn_file_sync_api \
|
||||
sys_svn_file_sync
|
||||
sys_svn_file_sync \
|
||||
sys_data_sync_server
|
||||
|
||||
sys_file_sync.depends = sys_file_sync_api
|
||||
sys_svn_file_sync.depends = sys_svn_file_sync_api
|
||||
|
||||
742
product/src/sys/sys_data_sync_server/datasyncserver.cpp
Normal file
742
product/src/sys/sys_data_sync_server/datasyncserver.cpp
Normal file
@ -0,0 +1,742 @@
|
||||
#include "datasyncserver.h"
|
||||
|
||||
DataSyncServer::DataSyncServer(QObject *parent) : QTcpServer(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理新连接
|
||||
* @param handle 新连接的socket描述符
|
||||
*
|
||||
* 创建QTcpSocket对象,初始化请求缓冲区,并连接相关信号槽
|
||||
*/
|
||||
void DataSyncServer::incomingConnection(qintptr handle)
|
||||
{
|
||||
QTcpSocket *socket = new QTcpSocket(this);
|
||||
socket->setSocketDescriptor(handle);
|
||||
|
||||
requestBuffers[socket] = QByteArray();
|
||||
|
||||
connect(socket, &QTcpSocket::readyRead, this, &DataSyncServer::onSocketReadyRead);
|
||||
connect(socket, &QTcpSocket::disconnected, this, &DataSyncServer::onSocketDisconnected);
|
||||
connect(socket, &QTcpSocket::bytesWritten, this, &DataSyncServer::onSocketBytesWritten);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Socket数据就绪槽函数
|
||||
*
|
||||
* 累积接收数据到缓冲区,尝试解析HTTP请求
|
||||
* 如果请求完整则处理,否则继续等待数据,限制最大请求大小为50MB
|
||||
*/
|
||||
void DataSyncServer::onSocketReadyRead()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket || socket->state() != QAbstractSocket::ConnectedState) return;
|
||||
|
||||
requestBuffers[socket].append(socket->readAll());
|
||||
|
||||
QString method;
|
||||
QString path;
|
||||
QByteArray body;
|
||||
int contentLength = -1;
|
||||
|
||||
//尝试解析HTTP请求
|
||||
if (!parseHttpRequest(requestBuffers[socket], method, path, body, contentLength)) {
|
||||
//请求尚未完整接收,等待更多数据
|
||||
if (requestBuffers[socket].size() > 1024 * 1024 *50) { //限制最大请求大小为50MB
|
||||
qDebug()<<"请求过大,关闭连接";
|
||||
sendErrorResponse(socket, 413, "请求实体body过大!最大限制为50M");
|
||||
socket->disconnectFromHost();
|
||||
//清理缓冲区
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//请求解析成功,处理请求(注意:此时不清除缓冲区,因为可能需要异步处理)
|
||||
handleRequest(socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Socket数据写入完成槽函数
|
||||
* @param bytes 已写入的字节数
|
||||
*
|
||||
* 预留接口,目前为空实现,可用于拓展写入完成后的处理逻辑
|
||||
*/
|
||||
void DataSyncServer::onSocketBytesWritten(qint64 bytes)
|
||||
{
|
||||
//可以在这里添加写入完成的回调处理,目前为空实现
|
||||
Q_UNUSED(bytes);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Socket断开连接槽函数
|
||||
*
|
||||
* 清理该Socket相关的所有数据:请求缓冲区、临时文件映射、操作类型标记等。
|
||||
*/
|
||||
void DataSyncServer::onSocketDisconnected()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket) return;
|
||||
|
||||
//清理该 socket 的所有相关数据
|
||||
requestBuffers.remove(socket);
|
||||
socketTempFiles.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
socket->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析HTTP请求
|
||||
* @param data 完整的HTTP请求数据
|
||||
* @param method 输出参数:HTTP方法
|
||||
* @param path 输出参数: 请求路径
|
||||
* @param body 输出参数:请求体内容
|
||||
* @param contentLength 输出参数:Content-Length值
|
||||
* @return 如果请求完整返回true,否则返回false
|
||||
*
|
||||
* 解析HTTP请求行、请求头和请求体。处理查询参数,根据Content-Length判断body是否完整
|
||||
*/
|
||||
bool DataSyncServer::parseHttpRequest(const QByteArray &data, QString &method, QString &path, QByteArray &body, int &contentLength)
|
||||
{
|
||||
//查找请求头和请求体的分隔符
|
||||
int headerEnd = data.indexOf("\r\n\r\n");
|
||||
if (headerEnd < 0) {
|
||||
return false; //头部尚未完整接收
|
||||
}
|
||||
|
||||
//解析请求头
|
||||
QByteArray headerData = data.left(headerEnd);
|
||||
QList<QByteArray> headerLines = headerData.split('\n');
|
||||
|
||||
if (headerLines.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//解析请求行;格式为"METHOD /path HTTP/1.x"
|
||||
QByteArray requestLine = headerLines[0].trimmed();
|
||||
QList<QByteArray> requestParts = requestLine.split(' ');
|
||||
if (requestParts.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
method = QString::fromLatin1(requestParts[0]);
|
||||
path = QString::fromLatin1(requestParts[1]);
|
||||
|
||||
//提取查询数据(如果有)
|
||||
int queryIndex = path.indexOf('?');
|
||||
if (queryIndex >= 0) {
|
||||
path = path.left(queryIndex);
|
||||
}
|
||||
|
||||
//查找 Content-Length
|
||||
contentLength = -1;
|
||||
for (int i = 1; i < headerLines.size(); ++i) {
|
||||
QByteArray line = headerLines[i].trimmed();
|
||||
if (QString(line).startsWith("Content-Length:", Qt::CaseInsensitive)) {
|
||||
bool ok;
|
||||
contentLength = line.mid(15).trimmed().toInt(&ok);
|
||||
if (!ok) contentLength = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//提取请求体
|
||||
int bodyStart = headerEnd + 4;
|
||||
QByteArray receivedBody = data.mid(bodyStart);
|
||||
|
||||
if (method.toUpper() == "POST" || method.toUpper() == "PUT") {
|
||||
if (contentLength > 0) {
|
||||
//需要接收完整的body
|
||||
if (receivedBody.size() < contentLength) {
|
||||
return false; //body 尚未完整接收
|
||||
}
|
||||
body = receivedBody.left(contentLength);
|
||||
}
|
||||
else {
|
||||
// 没有Content-Length, 假设所有剩余数据都是body
|
||||
body = receivedBody;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理HTTP请求
|
||||
* @param socket 客户端socket指针
|
||||
*
|
||||
* 解析HTTP请求并根据方法和路径分发到相应的处理函数
|
||||
* 支持的路径:
|
||||
* - GET /backup -> 备份操作
|
||||
* - POST /restore -> 恢复操作
|
||||
* - 其他 -> 404错误
|
||||
*/
|
||||
void DataSyncServer::handleRequest(QTcpSocket *socket)
|
||||
{
|
||||
QByteArray fullRequest = requestBuffers[socket];
|
||||
|
||||
QString method;
|
||||
QString path;
|
||||
QByteArray body;
|
||||
int contentLength = -1;
|
||||
|
||||
if (!parseHttpRequest(fullRequest, method, path, body, contentLength)) {
|
||||
sendErrorResponse(socket, 400, "错误请求!");
|
||||
socket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug()<<"收到请求:"<<method<<path;
|
||||
|
||||
//处理不同的路径
|
||||
if (method.toUpper() == "GET" && path == "/backup") {
|
||||
socketOperationType[socket] = true; //标记为备份操作
|
||||
handleBackup(socket);
|
||||
}
|
||||
else if (method.toUpper() == "POST" && path == "/restore") {
|
||||
socketOperationType[socket] = false; //标记为恢复操作
|
||||
handleRestore(socket, body);
|
||||
}
|
||||
else {
|
||||
sendErrorResponse(socket, 404, "Not Found");
|
||||
socket->disconnectFromHost();
|
||||
// 请求处理完成,清理缓冲区
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理备份请求
|
||||
* @param socket 客户端socket指针
|
||||
*
|
||||
* 执行数据备份操作:
|
||||
* 1. 检查数据目录是否存在
|
||||
* 2. 生成唯一的临时文件名避免并发冲突
|
||||
* 3. 使用tar命令异步打包data目录
|
||||
* 4. 打包完成后在onTarProcessFinished中发送文件给客户端
|
||||
*/
|
||||
void DataSyncServer::handleBackup(QTcpSocket *socket)
|
||||
{
|
||||
//检查socket是否仍然连接
|
||||
if (!socket || socket->state() != QAbstractSocket::ConnectedState) {
|
||||
qDebug() << "Socket已断开,取消备份操作";
|
||||
if (socket) {
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//确保目录存在
|
||||
QString appDir = QCoreApplication::applicationDirPath() + "/../..";
|
||||
QString tmpDir = QDir(appDir).filePath("tmp");
|
||||
QString dataDir = QDir(appDir).filePath("data");
|
||||
|
||||
if (!ensureDirectoryExists(tmpDir)) {
|
||||
qDebug() << "无法创建临时目录:" << tmpDir;
|
||||
sendErrorResponse(socket, 500, "无法创建临时目录tmp");
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
//检查数据目录是否存在
|
||||
if (!QDir(dataDir).exists()) {
|
||||
qDebug() << "数据目录不存在:" << dataDir;
|
||||
sendErrorResponse(socket, 500, "data目录不存在!");
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
//生成唯一的临时文件名,避免并发冲突
|
||||
QString uniqueId = QUuid::createUuid().toString().remove('{').remove('}');
|
||||
QString outputFile = tmpDir + QDir::separator() + "data_" + uniqueId + ".zip";
|
||||
|
||||
// 使用QProcess 异步执行tar命令,避免阻塞
|
||||
QProcess *process = new QProcess(this);
|
||||
processSockets[process] = socket;
|
||||
|
||||
// 设置tar命令参数(使用setProgram/setArguments避免路径问题)
|
||||
process->setProgram("zip");
|
||||
process->setWorkingDirectory(appDir);
|
||||
QStringList arguments;
|
||||
arguments << "-r" << outputFile << "data";
|
||||
process->setArguments(arguments);
|
||||
|
||||
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &DataSyncServer::onTarProcessFinished);
|
||||
connect(process, &QProcess::errorOccurred, this, &DataSyncServer::onTarProcessError);
|
||||
|
||||
qDebug()<< "start backup command:tar" << arguments.join(" ");
|
||||
|
||||
// 保存输出文件路径,供后续使用
|
||||
socketTempFiles[socket] = outputFile;
|
||||
|
||||
process->start();
|
||||
|
||||
if (!process->waitForStarted(3000)) {
|
||||
qDebug() << "tar 进程启动失败:" << process->errorString();
|
||||
sendErrorResponse(socket, 500, "无法启动tar进程!");
|
||||
processSockets.remove(process);
|
||||
socketTempFiles.remove(socket);
|
||||
process->deleteLater();
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理恢复请求
|
||||
* @param socket 客户端socket指针
|
||||
* @param body HTTP请求体,包含tar文件数据
|
||||
*
|
||||
* 执行数据恢复操作:
|
||||
* 1. 验证请求体不为空
|
||||
* 2. 将接收到的tar数据保存到临时文件
|
||||
* 3. 删除旧的data目录
|
||||
* 4. 使用tar命令异步解包到data目录
|
||||
* 5. 解包完成后发送成功相应
|
||||
*/
|
||||
void DataSyncServer::handleRestore(QTcpSocket *socket, const QByteArray &body)
|
||||
{
|
||||
// 检查socket是否仍然连接
|
||||
if (!socket || socket->state() != QAbstractSocket::ConnectedState) {
|
||||
qDebug() << "Socket已断开,取消恢复操作";
|
||||
if (socket) {
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.isEmpty()) {
|
||||
qDebug() << "恢复请求的 body 为空";
|
||||
sendErrorResponse(socket, 400, "错误请求!请求实体body为空!");
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
//确保目录存在
|
||||
QString appDir = QCoreApplication::applicationDirPath() + "/../..";
|
||||
QString tmpDir = QDir(appDir).filePath("tmp");
|
||||
QString restoreDir = QDir(appDir).filePath("data");
|
||||
|
||||
if (!ensureDirectoryExists(tmpDir)) {
|
||||
qDebug() << "无法创建临时目录:" << tmpDir;
|
||||
sendErrorResponse(socket, 500, "无法创建临时目录tmp!");
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
//生成唯一的临时文件名,避免并发冲突
|
||||
QString uniqueId = QUuid::createUuid().toString().remove('{').remove('}');
|
||||
QString saveFile = tmpDir + QDir::separator() + "data_" + uniqueId + ".tar";
|
||||
|
||||
//保存上传的文件
|
||||
QFile file(saveFile);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "无法创建临时文件:" << saveFile << file.errorString();
|
||||
sendErrorResponse(socket, 500, "无法创建临时文件!");
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 written = file.write(body);
|
||||
file.close();
|
||||
|
||||
if (written != body.size()) {
|
||||
qDebug() << "文件写入不完整,期望:" << body.size() << "实际:" <<written;
|
||||
sendErrorResponse(socket, 500, "临时文件写入失败!");
|
||||
//清理不完整的临时文件
|
||||
if (QFile::exists(saveFile)) {
|
||||
QFile::remove(saveFile);
|
||||
}
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "已保存上传文件:" << saveFile << "大小:" << written;
|
||||
|
||||
// 删除旧目录(如果存在)
|
||||
if (QDir(restoreDir).exists()) {
|
||||
qDebug() << "删除旧目录:" << restoreDir;
|
||||
if (!QDir(restoreDir).removeRecursively()) {
|
||||
qDebug() << "警告:无法完全删除旧目录:" << restoreDir;
|
||||
}
|
||||
}
|
||||
|
||||
QProcess *process = new QProcess(this);
|
||||
processSockets[process] = socket;
|
||||
|
||||
process->setProgram("tar");
|
||||
QStringList arguments;
|
||||
arguments << "xf" << saveFile << "-C" << appDir;
|
||||
process->setArguments(arguments);
|
||||
|
||||
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &DataSyncServer::onTarProcessFinished);
|
||||
connect(process, &QProcess::errorOccurred, this, &DataSyncServer::onTarProcessError);
|
||||
|
||||
qDebug() << "开始执行恢复命令:tar" << arguments.join(" ");
|
||||
|
||||
//保存临时文件路径,成功后删除
|
||||
socketTempFiles[socket] = saveFile;
|
||||
|
||||
process->start();
|
||||
|
||||
if (!process->waitForStarted(3000)) {
|
||||
qDebug() << "tar 进程启动失败:" << process->errorString();
|
||||
sendErrorResponse(socket, 500, "无法启动tar进程!");
|
||||
processSockets.remove(process);
|
||||
socketTempFiles.remove(socket);
|
||||
//清理临时文件
|
||||
if (QFile::exists(saveFile)) {
|
||||
QFile::remove(saveFile);
|
||||
}
|
||||
process->deleteLater();
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief tar进程完成槽函数
|
||||
* @param exitCode 进程退出码,0代表成功
|
||||
* @param exitStatus 进程退出状态,NormalExit表示正常退出
|
||||
*
|
||||
* 当tar命令执行完成时调用:
|
||||
* - 如果失败:发送错误响应并清理资源
|
||||
* - 如果成功且是备份操作:分块读取tar文件并发送给客户端
|
||||
* - 如果成功且是恢复操作:发送成功响应消息
|
||||
*/
|
||||
void DataSyncServer::onTarProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||
if (!process) return;
|
||||
|
||||
QTcpSocket *socket = processSockets.value(process);
|
||||
if (!socket) {
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查socket是否仍然连接
|
||||
if (socket->state() != QAbstractSocket::ConnectedState) {
|
||||
qDebug() << "Socket已断开,清理资源";
|
||||
QString tempFile = socketTempFiles.value(socket);
|
||||
if (!tempFile.isEmpty() && QFile::exists(tempFile)) {
|
||||
QFile::remove(tempFile);
|
||||
}
|
||||
processSockets.remove(process);
|
||||
socketTempFiles.remove(socket);
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
processSockets.remove(process);
|
||||
QString tempFile = socketTempFiles.value(socket);
|
||||
bool isBackup = socketOperationType.value(socket, false);
|
||||
|
||||
if (exitStatus != QProcess::NormalExit || exitCode != 0) {
|
||||
QByteArray errorOutput = process->readAllStandardError();
|
||||
qDebug() << "tar 命令执行失败,退出码:" << exitCode << "错误:" << errorOutput;
|
||||
sendErrorResponse(socket, 500, "tar进程执行失败!");
|
||||
|
||||
//清理临时文件
|
||||
if (!tempFile.isEmpty() && QFile::exists(tempFile)) {
|
||||
QFile::remove(tempFile);
|
||||
}
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
socketTempFiles.remove(socket);
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// tar 命令执行成功
|
||||
QByteArray standardOutput = process->readAllStandardOutput();
|
||||
QByteArray errorOutput = process->readAllStandardError();
|
||||
|
||||
if (!standardOutput.isEmpty()) {
|
||||
qDebug() << "tar 标准输出:" << standardOutput;
|
||||
}
|
||||
if (!errorOutput.isEmpty()) {
|
||||
qDebug() << "tar 标准错误:" << errorOutput;
|
||||
}
|
||||
|
||||
//根据操作类型处理
|
||||
if (isBackup && !tempFile.isEmpty() && QFile::exists(tempFile)) {
|
||||
//备份操作;分块发送文件,避免大文件占用过多内存
|
||||
QFile file(tempFile);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "无法打开备份文件:" << tempFile;
|
||||
sendErrorResponse(socket, 500, "无法打开临时文件!");
|
||||
QFile::remove(tempFile);
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
socketTempFiles.remove(socket);
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 fileSize = file.size();
|
||||
if (fileSize == 0) {
|
||||
qDebug() << "备份文件为空";
|
||||
sendErrorResponse(socket, 500, "临时文件为空!");
|
||||
file.close();
|
||||
QFile::remove(tempFile);
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
socketTempFiles.remove(socket);
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "send backup file,size:" << fileSize;
|
||||
|
||||
// 发送HTTP响应头
|
||||
QByteArray header = "HTTP/1.1 200 OK\r\n";
|
||||
header += "Content-Type: application/octet-stream\r\n";
|
||||
header += "Content-Length: " + QByteArray::number(fileSize) + "\r\n";
|
||||
header += "Connection: close\r\n";
|
||||
header += "Date: " + QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'").toLatin1() + "\r\n";
|
||||
header += "Server: DataSyncServer/1.0\r\n";
|
||||
header += "\r\n";
|
||||
|
||||
socket->write(header);
|
||||
|
||||
// 分块读取并发送文件
|
||||
const qint64 chunkSize = 64 * 1024; // 64KB 块
|
||||
while (!file.atEnd()) {
|
||||
QByteArray chunk = file.read(chunkSize);
|
||||
if (chunk.isEmpty() && !file.atEnd()) {
|
||||
qDebug() << "文件读取错误";
|
||||
file.close();
|
||||
QFile::remove(tempFile);
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
socketTempFiles.remove(socket);
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
socket->write(chunk);
|
||||
socket->waitForBytesWritten(30000);
|
||||
}
|
||||
file.close();
|
||||
|
||||
// 清理临时文件
|
||||
QFile::remove(tempFile);
|
||||
}
|
||||
else {
|
||||
// 恢复操作成功
|
||||
qDebug() << "恢复操作成功完成";
|
||||
sendSuccessResponse(socket, QByteArray("文件接收并解压成功!"), "text/plain; charset=utf-8");
|
||||
|
||||
// 清理临时文件
|
||||
if (!tempFile.isEmpty() && QFile::exists(tempFile)) {
|
||||
QFile::remove(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
socket->disconnectFromHost();
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
socketTempFiles.remove(socket);
|
||||
process->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief tar进程错误槽函数
|
||||
* @param error 进程错误类型
|
||||
*
|
||||
* 当tar命令执行出错时被调用,根据错误类型生成相应的错误消息
|
||||
* 发送错误响应给客户端并清理所有相关资源(临时文件、缓冲区等)。
|
||||
*/
|
||||
void DataSyncServer::onTarProcessError(QProcess::ProcessError error)
|
||||
{
|
||||
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||
if (!process) return;
|
||||
|
||||
QTcpSocket *socket = processSockets.value(process);
|
||||
if (!socket) {
|
||||
process->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QString errorString;
|
||||
switch (error) {
|
||||
case QProcess::FailedToStart:
|
||||
errorString = "tar command failed to start";
|
||||
break;
|
||||
case QProcess::Crashed:
|
||||
errorString = "tar command crashed";
|
||||
break;
|
||||
case QProcess::Timedout:
|
||||
errorString = "tar command timed out";
|
||||
break;
|
||||
case QProcess::WriteError:
|
||||
errorString = "tar command write error";
|
||||
break;
|
||||
case QProcess::ReadError:
|
||||
errorString = "tar command read error";
|
||||
break;
|
||||
default:
|
||||
errorString = "tar command unknown error";
|
||||
break;
|
||||
}
|
||||
|
||||
qDebug() << "tar 进程错误:" << errorString;
|
||||
|
||||
QString tempFile = socketTempFiles.value(socket);
|
||||
|
||||
// 清理临时文件
|
||||
if (!tempFile.isEmpty() && QFile::exists(tempFile)) {
|
||||
QFile::remove(tempFile);
|
||||
}
|
||||
|
||||
if (socket && socket->state() == QAbstractSocket::ConnectedState) {
|
||||
sendErrorResponse(socket, 500, errorString.toLatin1());
|
||||
socket->disconnectFromHost();
|
||||
}
|
||||
|
||||
requestBuffers.remove(socket);
|
||||
socketOperationType.remove(socket);
|
||||
process->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送HTTP错误响应
|
||||
* @param socket 客户端socket指针
|
||||
* @param statusCode HTTP状态码(400,404,413,500等)
|
||||
* @param message 错误消息内容
|
||||
*
|
||||
* 构造并发送完整的HTTP错误响应,包含状态行、响应头和错误消息体。
|
||||
* 自动添加Data和Server等标准HTTP响应头字段
|
||||
*/
|
||||
void DataSyncServer::sendErrorResponse(QTcpSocket *socket, int statusCode, const QByteArray &message)
|
||||
{
|
||||
if (!socket || socket->state() != QAbstractSocket::ConnectedState) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray response = "HTTP/1.1 " + QByteArray::number(statusCode) + " ";
|
||||
|
||||
switch (statusCode) {
|
||||
case 400: response += "Bad Request"; break;
|
||||
case 404: response += "Not Found"; break;
|
||||
case 413: response += "Request Entity Too Large"; break;
|
||||
case 500: response += "Internal Server Error"; break;
|
||||
default: response += "Error"; break;
|
||||
}
|
||||
|
||||
response += "\r\n";
|
||||
response += "Content-Type: text/plain; charset=utf-8\r\n";
|
||||
response += "Content-Length: " + QByteArray::number(message.size()) + "\r\n";
|
||||
response += "Connection: close\r\n";
|
||||
response += "Date: " + QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'").toLatin1() + "\r\n";
|
||||
response += "Server: DataSyncServer/1.0\r\n";
|
||||
response += "\r\n";
|
||||
response += message;
|
||||
|
||||
socket->write(response);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送HTTP成功响应
|
||||
* @param socket 客户端socket指针
|
||||
* @param content 响应内容
|
||||
* @param contentType Content-Type响应头,默认为"application/octet-stream"
|
||||
*
|
||||
* 构造并发送完整的HTTP 200成功响应,包含状态行、响应头和响应体
|
||||
* 自动添加Data和Server等标准HTTP响应头字段。
|
||||
*/
|
||||
void DataSyncServer::sendSuccessResponse(QTcpSocket *socket, const QByteArray &content, const QByteArray &contentType)
|
||||
{
|
||||
if (!socket || socket->state() != QAbstractSocket::ConnectedState) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray response = "HTTP/1.1 200 OK\r\n";
|
||||
response += "Content-Type: " + contentType + "\r\n";
|
||||
response += "Content-Length: " + QByteArray::number(content.size()) + "\r\n";
|
||||
response += "Connection: close\r\n";
|
||||
response += "Date: " + QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'").toLatin1() + "\r\n";
|
||||
response += "Server: DataSyncServer/1.0\r\n";
|
||||
response += "\r\n";
|
||||
response += content;
|
||||
|
||||
socket->write(response);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确保目录存在
|
||||
* @param path 目录路径
|
||||
* @return 如果目录存在或创建成功返回true,否则返回false
|
||||
*
|
||||
* 检查指定目录是否存在,如果不存在则递归创建该目录及其所有父目录
|
||||
*/
|
||||
bool DataSyncServer::ensureDirectoryExists(const QString &path)
|
||||
{
|
||||
QDir dir;
|
||||
if (!dir.exists(path)) {
|
||||
return dir.mkpath(path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清理路径字符串
|
||||
* @param path 原始路径字符串
|
||||
* @return 清理后的路径字符串
|
||||
*
|
||||
* 移除路径中的潜在危险字符,防止路径遍历攻击
|
||||
* - 移除".."路径遍历符号
|
||||
* - 移除重复的路径分隔符
|
||||
*
|
||||
* 注意:在当前实现中可能不需要,但保留此接口用于未来拓展
|
||||
*/
|
||||
QString DataSyncServer::sanitizePath(const QString &path)
|
||||
{
|
||||
// 移除潜在的路径遍历攻击(虽然在这个实现中可能不需要,但保留接口)
|
||||
QString sanitized = path;
|
||||
sanitized.replace("..", "");
|
||||
QString sep = QDir::separator();
|
||||
sanitized.replace(sep + sep, sep);
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
53
product/src/sys/sys_data_sync_server/datasyncserver.h
Normal file
53
product/src/sys/sys_data_sync_server/datasyncserver.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef DATASYNCSERVER_H
|
||||
#define DATASYNCSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QUuid>
|
||||
#include <QDateTime>
|
||||
|
||||
class DataSyncServer : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DataSyncServer(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr handle);
|
||||
|
||||
private slots:
|
||||
void onSocketReadyRead();
|
||||
void onSocketBytesWritten(qint64 bytes);
|
||||
void onSocketDisconnected();
|
||||
void onTarProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void onTarProcessError(QProcess::ProcessError error);
|
||||
|
||||
private:
|
||||
// 为每个socket 维护请求缓冲区,处理分包问题
|
||||
QHash<QTcpSocket*, QByteArray> requestBuffers;
|
||||
QHash<QProcess*, QTcpSocket*> processSockets;
|
||||
QHash<QTcpSocket*, QString> socketTempFiles;
|
||||
//标记每个socket的操作类型;true=备份,false=恢复
|
||||
QHash<QTcpSocket*, bool> socketOperationType;
|
||||
|
||||
|
||||
bool parseHttpRequest(const QByteArray &data, QString &method, QString &path, QByteArray &body, int &contentLength);
|
||||
void handleRequest(QTcpSocket *socket);
|
||||
void handleBackup(QTcpSocket *socket);
|
||||
void handleRestore(QTcpSocket *socket, const QByteArray &body);
|
||||
void sendErrorResponse(QTcpSocket *socket, int statusCode, const QByteArray &message);
|
||||
void sendSuccessResponse(QTcpSocket *socket, const QByteArray &content, const QByteArray &contentType);
|
||||
bool ensureDirectoryExists(const QString &path);
|
||||
QString sanitizePath(const QString &path);
|
||||
};
|
||||
|
||||
#endif // DATASYNCSERVER_H
|
||||
17
product/src/sys/sys_data_sync_server/main.cpp
Normal file
17
product/src/sys/sys_data_sync_server/main.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "datasyncserver.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
DataSyncServer data_sync_server;
|
||||
if (!data_sync_server.listen(QHostAddress::Any, 8080)) {
|
||||
qFatal("Listen failed");
|
||||
}
|
||||
|
||||
qDebug("data_sync_server started at 8080");
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
REM 固定用户名
|
||||
set username=admin
|
||||
|
||||
echo 请输入远程主机IP地址:
|
||||
set /p ip=
|
||||
|
||||
if "%ip%"=="" (
|
||||
echo IP地址不能为空!
|
||||
pause
|
||||
exit
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 请选择操作:
|
||||
echo 1 - 启动系统
|
||||
echo 2 - 停止系统
|
||||
set /p choice=请输入 1 或 2:
|
||||
|
||||
if "%choice%"=="1" (
|
||||
set command=/opt/EnergyHub/platform/oe2203_aarch64_release/sys_ctrl
|
||||
) else if "%choice%"=="2" (
|
||||
set command=/opt/EnergyHub/platform/oe2203_aarch64_release/sys_ctrl -s
|
||||
) else (
|
||||
echo 无效选择!
|
||||
pause
|
||||
exit
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 正在连接 %ip% ...
|
||||
echo 如果需要密码,请根据提示输入。
|
||||
echo.
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL %username%@%ip% %command%
|
||||
|
||||
echo.
|
||||
echo 执行完成。
|
||||
pause
|
||||
@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 固定用户名
|
||||
username="admin"
|
||||
|
||||
echo "请输入远程主机IP地址:"
|
||||
read ip
|
||||
|
||||
if [ -z "$ip" ]; then
|
||||
echo "IP地址不能为空!"
|
||||
read -p "按回车键退出..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "请选择操作:"
|
||||
echo "1 - 启动系统"
|
||||
echo "2 - 停止系统"
|
||||
read -p "请输入 1 或 2:" choice
|
||||
|
||||
if [ "$choice" = "1" ]; then
|
||||
command="/opt/EnergyHub/platform/oe2203_aarch64_release/sys_ctrl"
|
||||
elif [ "$choice" = "2" ]; then
|
||||
command="/opt/EnergyHub/platform/oe2203_aarch64_release/sys_ctrl -s"
|
||||
else
|
||||
echo "无效选择!"
|
||||
read -p "按回车键退出..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "正在连接 $ip ..."
|
||||
echo "如果需要密码,请根据提示输入。"
|
||||
echo
|
||||
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${username}@${ip} "$command"
|
||||
|
||||
echo
|
||||
echo "执行完成。"
|
||||
read -p "按回车键退出..."
|
||||
@ -0,0 +1,53 @@
|
||||
QT -= gui
|
||||
|
||||
QT += network
|
||||
|
||||
CONFIG += c++11 console
|
||||
CONFIG -= app_bundle
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which as been marked deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
# You can also make your code fail to compile if you use deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += main.cpp \
|
||||
datasyncserver.cpp
|
||||
|
||||
HEADERS += \
|
||||
datasyncserver.h
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
COMMON_PRI=$$PWD/../../common.pri
|
||||
exists($$COMMON_PRI) {
|
||||
include($$COMMON_PRI)
|
||||
}else {
|
||||
error("FATAL error: can not find common.pri")
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 脚本目录
|
||||
SCRIPT_DIR = $$PWD/scripts
|
||||
|
||||
win32 {
|
||||
SCRIPT_SRC = $$shell_path($$SCRIPT_DIR/remote_sys_ctrl.bat)
|
||||
SCRIPT_DST = $$shell_path($$DESTDIR/remote_sys_ctrl.bat)
|
||||
|
||||
generate_script.commands = $$QMAKE_COPY "$$SCRIPT_SRC" "$$SCRIPT_DST"
|
||||
}
|
||||
|
||||
unix:!macx { #Linux
|
||||
SCRIPT_SRC = $$SCRIPT_DIR/remote_sys_ctrl.sh
|
||||
SCRIPT_DST = $$DESTDIR/remote_sys_ctrl.sh
|
||||
|
||||
generate_script.commands = cp "$$SCRIPT_SRC" "$$SCRIPT_DST" && chmod +x "$$SCRIPT_DST"
|
||||
}
|
||||
|
||||
QMAKE_EXTRA_TARGETS += generate_script
|
||||
POST_TARGETDEPS += generate_script
|
||||
# ------------------------------------------------------------------
|
||||
Loading…
x
Reference in New Issue
Block a user