[add]添加文件同步服务&&安装脚本添加自定义文件服务安装
This commit is contained in:
parent
79f03f12c2
commit
cd5f931fb6
@ -539,55 +539,9 @@ chmod 644 /usr/share/applications/${app_name}.desktop
|
|||||||
update-desktop-database
|
update-desktop-database
|
||||||
#为所有用户创建应用菜单快捷方式 end
|
#为所有用户创建应用菜单快捷方式 end
|
||||||
|
|
||||||
#注册 sys_data_sync_server 到 systemd
|
|
||||||
#if [ "oe2203_aarch64" = "$OS_DEFINE" ]; then
|
|
||||||
SERVICE_NAME="sys_data_sync_server"
|
|
||||||
EXEC_PATH="$(dirname "$script_path")/product/$BIN_DIR_VER/sys_data_sync_server"
|
|
||||||
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
||||||
|
|
||||||
if [ ! -f "$EXEC_PATH" ]; then
|
|
||||||
echo "ERROR: 未找到可执行文件 $EXEC_PATH"
|
|
||||||
else
|
|
||||||
echo "INFO: 开始注册 ${SERVICE_NAME} 到 systemd..."
|
|
||||||
|
|
||||||
chmod +x "$EXEC_PATH"
|
|
||||||
|
|
||||||
cat > "$SERVICE_FILE" <<EOF
|
|
||||||
[Unit]
|
|
||||||
Description=Sys Data Sync Server
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=$EXEC_PATH
|
|
||||||
WorkingDirectory=$(dirname "$EXEC_PATH")
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
LimitNOFILE=65535
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 重新加载 systemd
|
|
||||||
systemctl daemon-reload
|
|
||||||
|
|
||||||
# 设置开机自启动
|
|
||||||
systemctl enable ${SERVICE_NAME}.service
|
|
||||||
|
|
||||||
# 启动服务
|
|
||||||
systemctl start ${SERVICE_NAME}.service
|
|
||||||
|
|
||||||
# 检查状态
|
|
||||||
if systemctl is-active --quiet "${SERVICE_NAME}"; then
|
|
||||||
echo "INFO:${SERVICE_NAME} 服务已成功启动并设置为开机自启"
|
|
||||||
else
|
|
||||||
echo "ERROR:${SERVICE_NAME} 启动失败,请执行:"
|
|
||||||
echo "journalctl -u ${SERVICE_NAME} -f"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
#fi
|
|
||||||
|
|
||||||
|
#注册 sys_data_sync_server 服务
|
||||||
|
bash $(dirname "$script_path")/installer/others/sys_file_service_manager.sh install "$BIN_DIR_VER"
|
||||||
|
|
||||||
|
|
||||||
if [ "oe2203_aarch64" = "$OS_DEFINE" ]; then
|
if [ "oe2203_aarch64" = "$OS_DEFINE" ]; then
|
||||||
|
|||||||
96
installer/others/sys_file_service_manager.sh
Normal file
96
installer/others/sys_file_service_manager.sh
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
#注册 sys_file_service 到 systemd
|
||||||
|
SERVICE_NAME="sys_file_service"
|
||||||
|
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||||
|
script_path="/opt/EnergyHub/installer"
|
||||||
|
|
||||||
|
install_service() {
|
||||||
|
echo "INFO:开始注册 ${SERVICE_NAME} 到 systemd..."
|
||||||
|
|
||||||
|
EXEC_PATH="$(dirname "$script_path")/product/$BIN_DIR_VER/sys_file_service"
|
||||||
|
WORK_DIR="$(dirname "$EXEC_PATH")"
|
||||||
|
|
||||||
|
if [ ! -f "$EXEC_PATH" ]; then
|
||||||
|
echo "ERROR: 未找到可执行文件 ${EXEC_PATH}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$EXEC_PATH"
|
||||||
|
|
||||||
|
cat > "$SERVICE_FILE" <<EOL
|
||||||
|
[Unit]
|
||||||
|
Description=Sys Data Sync Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=$EXEC_PATH
|
||||||
|
WorkingDirectory=$WORK_DIR
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
LimitNOFILE=65536
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOL
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable ${SERVICE_NAME}.service
|
||||||
|
systemctl restart ${SERVICE_NAME}.service
|
||||||
|
|
||||||
|
if systemctl is-active --quiet ${SERVICE_NAME}.service; then
|
||||||
|
echo "INFO:${SERVICE_NAME} 服务已经成功启动并设置为开机自启!"
|
||||||
|
else
|
||||||
|
echo "ERROR: ${SERVICE_NAME} 服务启动失败!查看日志请执行:"
|
||||||
|
echo "journalctl -u ${SERVICE_NAME}.service -f"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall_service() {
|
||||||
|
echo "INFO:正在卸载 ${SERVICE_NAME} 服务..."
|
||||||
|
|
||||||
|
if systemctl is-active --quiet ${SERVICE_NAME}.service; then
|
||||||
|
systemctl stop ${SERVICE_NAME}.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl disable ${SERVICE_NAME}.service || true
|
||||||
|
|
||||||
|
if [ -f "$SERVICE_FILE" ]; then
|
||||||
|
rm -f "$SERVICE_FILE"
|
||||||
|
systemctl daemon-reload
|
||||||
|
echo "INFO:${SERVICE_NAME} 服务已成功卸载!"
|
||||||
|
else
|
||||||
|
echo "WARNING: ${SERVICE_NAME} 服务文件未找到,可能已经被删除。"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "用法:$0 {install|uninstall} [BIN_DIR_VER]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ACTION="$1"
|
||||||
|
BIN_DIR_VER="$2"
|
||||||
|
|
||||||
|
# install 时 BIN_DIR_VER 必须提供
|
||||||
|
if [ "$ACTION" = "install" ] && [ -z "$BIN_DIR_VER" ]; then
|
||||||
|
echo "ERROR: 安装时必须提供 BIN_DIR_VER 参数!"
|
||||||
|
echo "用法:$0 install BIN_DIR_VER"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
install)
|
||||||
|
install_service
|
||||||
|
;;
|
||||||
|
uninstall)
|
||||||
|
uninstall_service
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "用法:$0 {install|uninstall} [BIN_DIR_VER]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -9,7 +9,7 @@ SUBDIRS += \
|
|||||||
sys_startup \
|
sys_startup \
|
||||||
sys_svn_file_sync_api \
|
sys_svn_file_sync_api \
|
||||||
sys_svn_file_sync \
|
sys_svn_file_sync \
|
||||||
sys_data_sync_server
|
sys_file_service
|
||||||
|
|
||||||
sys_file_sync.depends = sys_file_sync_api
|
sys_file_sync.depends = sys_file_sync_api
|
||||||
sys_svn_file_sync.depends = sys_svn_file_sync_api
|
sys_svn_file_sync.depends = sys_svn_file_sync_api
|
||||||
|
|||||||
33
product/src/sys/sys_file_service/AppComponent.hpp
Normal file
33
product/src/sys/sys_file_service/AppComponent.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef APPCOMPONENT_HPP
|
||||||
|
#define APPCOMPONENT_HPP
|
||||||
|
|
||||||
|
#include "oatpp/web/server/HttpConnectionHandler.hpp"
|
||||||
|
#include "oatpp/network/tcp/server/ConnectionProvider.hpp"
|
||||||
|
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
||||||
|
#include "oatpp/web/server/HttpRouter.hpp"
|
||||||
|
#include "oatpp/core/macro/component.hpp"
|
||||||
|
|
||||||
|
class AppComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([]{
|
||||||
|
return oatpp::network::tcp::server::ConnectionProvider::createShared({"0.0.0.0", 8080, oatpp::network::Address::IP_4});
|
||||||
|
}());
|
||||||
|
|
||||||
|
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, httpRouter)([]{
|
||||||
|
return oatpp::web::server::HttpRouter::createShared();
|
||||||
|
}());
|
||||||
|
|
||||||
|
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([]{
|
||||||
|
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);
|
||||||
|
return oatpp::web::server::HttpConnectionHandler::createShared(router);
|
||||||
|
}());
|
||||||
|
|
||||||
|
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::parser::json::mapping::ObjectMapper>, apiObjectMapper)([]{
|
||||||
|
return oatpp::parser::json::mapping::ObjectMapper::createShared();
|
||||||
|
}());
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPCOMPONENT_HPP
|
||||||
321
product/src/sys/sys_file_service/FileController.hpp
Normal file
321
product/src/sys/sys_file_service/FileController.hpp
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
#ifndef FILECONTROLLER_HPP
|
||||||
|
#define FILECONTROLLER_HPP
|
||||||
|
|
||||||
|
#include "oatpp/web/server/api/ApiController.hpp"
|
||||||
|
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||||
|
#include "oatpp/web/mime/multipart/PartReader.hpp"
|
||||||
|
#include "oatpp/web/mime/multipart/PartList.hpp"
|
||||||
|
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
||||||
|
#include "oatpp/core/macro/codegen.hpp"
|
||||||
|
#include "oatpp/core/macro/component.hpp"
|
||||||
|
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||||
|
#include "oatpp/web/mime/multipart/TemporaryFileProvider.hpp"
|
||||||
|
#include "oatpp/web/mime/multipart/InMemoryDataProvider.hpp"
|
||||||
|
#include "oatpp/core/data/resource/TemporaryFile.hpp"
|
||||||
|
#include "oatpp/core/utils/ConversionUtils.hpp"
|
||||||
|
#include "oatpp/web/protocol/http/outgoing/BufferBody.hpp"
|
||||||
|
#include "oatpp/web/protocol/http/outgoing/StreamingBody.hpp"
|
||||||
|
#include "oatpp/network/Url.hpp"
|
||||||
|
#include "miniz.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
|
||||||
|
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||||
|
|
||||||
|
class FileController : public oatpp::web::server::api::ApiController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FileController(OATPP_COMPONENT(std::shared_ptr<oatpp::parser::json::mapping::ObjectMapper>, objectMapper))
|
||||||
|
: oatpp::web::server::api::ApiController(objectMapper){}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string ensureDir(const std::string& dir) {
|
||||||
|
const QString qdir = QString::fromUtf8(dir.c_str());
|
||||||
|
if (QDir().mkpath(qdir)) return dir;
|
||||||
|
throw std::runtime_error("create_directories failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string joinPath(const std::string& a, const std::string& b) {
|
||||||
|
const QString qa = QString::fromUtf8(a.c_str());
|
||||||
|
const QString qb = QString::fromUtf8(b.c_str());
|
||||||
|
return QDir(qa).filePath(qb).toUtf8().constData();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string sanitizeFileName(const oatpp::String& in) {
|
||||||
|
if (!in || in->empty()) return "upload.bin";
|
||||||
|
const QString raw = QString::fromUtf8(in->c_str());
|
||||||
|
QString name = QFileInfo(raw).fileName();
|
||||||
|
if (name.isEmpty() || name == "." || name =="..") return "upload.bin";
|
||||||
|
|
||||||
|
static const QString illegal = "\\/:*?\"<>|";
|
||||||
|
for (int i = 0; i < name.size(); ++i) {
|
||||||
|
if (illegal.contains(name.at(i))) {
|
||||||
|
name[i] = QChar('_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name.toUtf8().constData();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool endsWithZip(const std::string& path) {
|
||||||
|
return QString::fromUtf8(path.c_str()).endsWith(".zip", Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString zipExtractDirFor(const std::string& zipPath, const std::string& baseDir) {
|
||||||
|
const QString qZip = QString::fromUtf8(zipPath.c_str());
|
||||||
|
const QString qBase = QString::fromUtf8(baseDir.c_str());
|
||||||
|
const QString stem = QFileInfo(qZip).completeBaseName();
|
||||||
|
return QDir(qBase).filePath(stem);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string pathStem(const std::string& file) {
|
||||||
|
return QFileInfo(QString::fromUtf8(file.c_str())).completeBaseName().toUtf8().constData();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extractZip(const std::string& zipPath, const std::string& targetDir, std::string& outError) {
|
||||||
|
mz_zip_archive zip_archive;
|
||||||
|
memset(&zip_archive, 0, sizeof(zip_archive));
|
||||||
|
|
||||||
|
if (!mz_zip_reader_init_file(&zip_archive, zipPath.c_str(), 0)) {
|
||||||
|
outError = "Could not initialize zip reader";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_uint num_files = mz_zip_reader_get_num_files(&zip_archive);
|
||||||
|
for (mz_uint i = 0; i < num_files; i++) {
|
||||||
|
mz_zip_archive_file_stat file_stat;
|
||||||
|
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过目录条目(miniz 会在解压文件时自动处理路径)
|
||||||
|
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防御 Zip Slip 攻击:检查文件名是否包含 ".."
|
||||||
|
QString fileName = QString::fromUtf8(file_stat.m_filename);
|
||||||
|
if (fileName.contains("..")) continue;
|
||||||
|
|
||||||
|
// 构建完整输出路径
|
||||||
|
std::string fullPath = joinPath(targetDir, file_stat.m_filename);
|
||||||
|
|
||||||
|
// 确保子目录存在
|
||||||
|
QFileInfo fi(QString::fromUtf8(fullPath.c_str()));
|
||||||
|
QDir().mkpath(fi.absolutePath());
|
||||||
|
|
||||||
|
// 解压文件
|
||||||
|
if (!mz_zip_reader_extract_to_file(&zip_archive, i, fullPath.c_str(), 0)) {
|
||||||
|
outError = "Failed to extract: " + std::string(file_stat.m_filename);
|
||||||
|
mz_zip_reader_end(&zip_archive);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_reader_end(&zip_archive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool zipDirectoryToFile(const std::string& srcDir, const std::string& zipFile, std::string* err) {
|
||||||
|
mz_zip_archive zip;
|
||||||
|
std::memset(&zip, 0, sizeof(zip));
|
||||||
|
if(!mz_zip_writer_init_file(&zip, zipFile.c_str(), 0)) {
|
||||||
|
if(err) *err = "create zip failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString root = QDir(QString::fromUtf8(srcDir.c_str())).absolutePath();
|
||||||
|
QDirIterator it(root, QDir::Files, QDirIterator::Subdirectories);
|
||||||
|
while(it.hasNext()) {
|
||||||
|
const QString abs = it.next();
|
||||||
|
const QString rel = QDir(root).relativeFilePath(abs);
|
||||||
|
const std::string zipEntry = QDir::fromNativeSeparators(rel).toUtf8().constData();
|
||||||
|
if(!mz_zip_writer_add_file(&zip,
|
||||||
|
zipEntry.c_str(),
|
||||||
|
abs.toUtf8().constData(),
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
MZ_BEST_COMPRESSION)) {
|
||||||
|
if(err) *err = "zip add failed: " + zipEntry;
|
||||||
|
mz_zip_writer_end(&zip);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mz_zip_writer_finalize_archive(&zip)) {
|
||||||
|
if(err) *err = "zip finalize failed";
|
||||||
|
mz_zip_writer_end(&zip);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mz_zip_writer_end(&zip);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanupOldTempZips(const std::string& dir, qint64 olderThanSeconds) {
|
||||||
|
QDir qdir(QString::fromUtf8(dir.c_str()));
|
||||||
|
if(!qdir.exists()) return;
|
||||||
|
const QDateTime now = QDateTime::currentDateTimeUtc();
|
||||||
|
const QFileInfoList list = qdir.entryInfoList(QStringList() << "download_*.zip", QDir::Files, QDir::Time);
|
||||||
|
for(int i = 0; i < list.size(); ++i) {
|
||||||
|
const QFileInfo fi = list.at(i);
|
||||||
|
const qint64 age = fi.lastModified().toUTC().secsTo(now);
|
||||||
|
if(age > olderThanSeconds) {
|
||||||
|
QFile::remove(fi.absoluteFilePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string urlDecodeToStdString(const oatpp::String& encoded) {
|
||||||
|
if (!encoded) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const std::string raw = *encoded;
|
||||||
|
QByteArray bytes(raw.data(), static_cast<int>(raw.size()));
|
||||||
|
QString decoded = QUrl::fromPercentEncoding(bytes);
|
||||||
|
QByteArray utf8 = decoded.toUtf8();
|
||||||
|
return std::string(utf8.constData(), static_cast<size_t>(utf8.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
ENDPOINT("GET", "/hello", hello)
|
||||||
|
{
|
||||||
|
auto response = createResponse(Status::CODE_200, u8"hello");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ENDPOINT("POST", "/upload", upload,
|
||||||
|
REQUEST(std::shared_ptr<IncomingRequest>, request),
|
||||||
|
QUERY(oatpp::String, dir, "dir", "uploads"),
|
||||||
|
QUERY(oatpp::String, field, "field", "file"))
|
||||||
|
{
|
||||||
|
auto contentType = request->getHeader("Content-Type");
|
||||||
|
if (!contentType || contentType->find("multipart/form-data") == std::string::npos) {
|
||||||
|
auto response = createResponse(Status::CODE_400, u8"Invalid Content-Type");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string baseDir = ensureDir(dir ? urlDecodeToStdString(dir) : std::string("uploads"));
|
||||||
|
const std::string tmpDir = ensureDir(joinPath(baseDir, ".tmp"));
|
||||||
|
|
||||||
|
auto multipart = std::make_shared<oatpp::web::mime::multipart::PartList>(request->getHeaders());
|
||||||
|
oatpp::web::mime::multipart::Reader reader(multipart.get());
|
||||||
|
|
||||||
|
const auto fieldName = std::string(field ? *field : "file");
|
||||||
|
|
||||||
|
// 只把目标字段写入临时文件;其余字段限定在小内存中解析。
|
||||||
|
reader.setPartReader(fieldName.c_str(), oatpp::web::mime::multipart::createTemporaryFilePartReader(tmpDir.c_str()));
|
||||||
|
reader.setDefaultPartReader(oatpp::web::mime::multipart::createInMemoryPartReader(16 * 1024));
|
||||||
|
|
||||||
|
request->transferBody(&reader);
|
||||||
|
|
||||||
|
auto filePart = multipart->getNamedPart(oatpp::String(fieldName.c_str()));
|
||||||
|
if(!filePart) {
|
||||||
|
auto response = createResponse(Status::CODE_400, u8"未找到文件部分 (field=" + (field ? std::string(*field) : std::string("file")) + ")");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto filename = sanitizeFileName(filePart->getFilename());
|
||||||
|
const std::string outPath = joinPath(baseDir, filename);
|
||||||
|
|
||||||
|
auto tf = std::dynamic_pointer_cast<oatpp::data::resource::TemporaryFile>(filePart->getPayload());
|
||||||
|
if (!tf) {
|
||||||
|
auto response = createResponse(Status::CODE_400, u8"没有临时文件数据包");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tf->moveFile(oatpp::String(outPath.c_str()))) {
|
||||||
|
auto response = createResponse(Status::CODE_500, u8"保存失败: TemporaryFile::moveFile 函数执行失败");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endsWithZip(outPath)) {
|
||||||
|
const QString qDestDir = zipExtractDirFor(outPath, baseDir);
|
||||||
|
std::string destDir = qDestDir.toUtf8().constData();
|
||||||
|
|
||||||
|
std::string errStr;
|
||||||
|
if (extractZip(outPath, destDir, errStr)) {
|
||||||
|
QFile::remove(QString::fromUtf8(outPath.c_str()));
|
||||||
|
auto response = createResponse(Status::CODE_200, u8"压缩文件接收并提取至: " + destDir);
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto response = createResponse(Status::CODE_500, u8"压缩文件提取失败:" + errStr);
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto response = createResponse(Status::CODE_200, u8"接收并保存至:" + outPath);
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
ENDPOINT("GET", "/download", download,
|
||||||
|
QUERY(oatpp::String, dir, "dir"))
|
||||||
|
{
|
||||||
|
if (!dir || dir->empty()) {
|
||||||
|
auto response = createResponse(Status::CODE_400, u8"缺少dir字段");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string sourceDir = urlDecodeToStdString(dir);
|
||||||
|
QFileInfo info(QString::fromUtf8(sourceDir.c_str()));
|
||||||
|
if (!info.exists() || !info.isDir()) {
|
||||||
|
auto response = createResponse(Status::CODE_404, u8"未找到该目录: " + sourceDir);
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string tmpRoot = ensureDir(".tmp");
|
||||||
|
cleanupOldTempZips(tmpRoot, 3600);
|
||||||
|
const std::string zipName = "download_" + std::to_string(oatpp::base::Environment::getMicroTickCount()) + ".zip";
|
||||||
|
const std::string zipPath = joinPath(tmpRoot, zipName);
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
if(!zipDirectoryToFile(sourceDir, zipPath, &err)) {
|
||||||
|
QFile::remove(QString::fromUtf8(zipPath.c_str()));
|
||||||
|
auto response = createResponse(Status::CODE_500, u8"创建压缩文件失败: " + err);
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string downloadName = pathStem(sanitizeFileName(oatpp::String(sourceDir.c_str()))) + ".zip";
|
||||||
|
|
||||||
|
auto fileStream = std::make_shared<oatpp::data::stream::FileInputStream>(zipPath.c_str());
|
||||||
|
if(fileStream->getFile() == nullptr) {
|
||||||
|
QFile::remove(QString::fromUtf8(zipPath.c_str()));
|
||||||
|
auto response = createResponse(Status::CODE_500, u8"打开压缩文件并流传输失败");
|
||||||
|
response->putHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto streamingBody =
|
||||||
|
std::make_shared<oatpp::web::protocol::http::outgoing::StreamingBody>(fileStream);
|
||||||
|
|
||||||
|
auto response =
|
||||||
|
oatpp::web::protocol::http::outgoing::Response::createShared(Status::CODE_200, streamingBody);
|
||||||
|
|
||||||
|
response->putHeader("Content-Type", "application/zip");
|
||||||
|
response->putHeader("Content-Disposition", "attachment; filename=\"" + downloadName + "\"");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#include OATPP_CODEGEN_END(ApiController)
|
||||||
|
|
||||||
|
#endif // FILECONTROLLER_HPP
|
||||||
34
product/src/sys/sys_file_service/main.cpp
Normal file
34
product/src/sys/sys_file_service/main.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "oatpp/core/base/Environment.hpp"
|
||||||
|
#include "oatpp/network/Server.hpp"
|
||||||
|
|
||||||
|
#include "AppComponent.hpp"
|
||||||
|
#include "FileController.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QCoreApplication a(argc, argv);
|
||||||
|
|
||||||
|
oatpp::base::Environment::init();
|
||||||
|
|
||||||
|
qDebug() << "Oat++ Version:" << OATPP_VERSION;
|
||||||
|
qDebug() << "Oat++ is running with Qt!";
|
||||||
|
|
||||||
|
AppComponent components;
|
||||||
|
|
||||||
|
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);
|
||||||
|
OATPP_COMPONENT(std::shared_ptr<oatpp::parser::json::mapping::ObjectMapper>, mapper);
|
||||||
|
router->addController(std::make_shared<FileController>(mapper));
|
||||||
|
|
||||||
|
OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, connectionHandler);
|
||||||
|
OATPP_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, connectionProvider);
|
||||||
|
|
||||||
|
oatpp::network::Server server(connectionProvider, connectionHandler);
|
||||||
|
|
||||||
|
server.run();
|
||||||
|
|
||||||
|
oatpp::base::Environment::destroy();
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
||||||
7909
product/src/sys/sys_file_service/miniz.c
Normal file
7909
product/src/sys/sys_file_service/miniz.c
Normal file
File diff suppressed because it is too large
Load Diff
1510
product/src/sys/sys_file_service/miniz.h
Normal file
1510
product/src/sys/sys_file_service/miniz.h
Normal file
File diff suppressed because it is too large
Load Diff
41
product/src/sys/sys_file_service/scripts/remote_sys_ctrl.bat
Normal file
41
product/src/sys/sys_file_service/scripts/remote_sys_ctrl.bat
Normal file
@ -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
|
||||||
64
product/src/sys/sys_file_service/scripts/remote_sys_ctrl.sh
Normal file
64
product/src/sys/sys_file_service/scripts/remote_sys_ctrl.sh
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 终端检测与自动弹出
|
||||||
|
if [ ! -t 0 ]; then
|
||||||
|
# 读取系统发行版本信息
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
source /etc/os-release
|
||||||
|
|
||||||
|
# 麒麟使用 mate-terminal,其他的使用 gnome-terminal
|
||||||
|
if [[ "$ID" == "kylin" ]]; then
|
||||||
|
TERM_CMD="mate-terminal"
|
||||||
|
else
|
||||||
|
TERM_CMD="gnome-terminal"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行命令并退出当前无窗口进程
|
||||||
|
if command -v "$TERM_CMD" > /dev/null 2>&1; then
|
||||||
|
exec "$TERM_CMD" -- bash "$0" "$@"
|
||||||
|
else
|
||||||
|
# 最后的兜底,防止某些精简版系统没装对应的终端
|
||||||
|
xterm -e bash "$0" "$@" || echo "找不到合适的终端程序"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 固定用户名
|
||||||
|
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 "按回车键退出..."
|
||||||
56
product/src/sys/sys_file_service/sys_file_service.pro
Normal file
56
product/src/sys/sys_file_service/sys_file_service.pro
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
QT -= gui
|
||||||
|
|
||||||
|
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 \
|
||||||
|
miniz.c
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
AppComponent.hpp \
|
||||||
|
miniz.h \
|
||||||
|
FileController.hpp
|
||||||
|
|
||||||
|
|
||||||
|
LIBS += -loatpp
|
||||||
|
|
||||||
|
|
||||||
|
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