refactor: 加载强类型连接配置

This commit is contained in:
Chenx Dust
2026-07-27 16:23:39 +08:00
parent 5ebd222af2
commit a160d383e9
7 changed files with 267 additions and 157 deletions

View File

@@ -37,6 +37,7 @@ set(SOURCE_FILES
zjuconnectmode.cpp
core/corecommandbuilder.cpp
core/coreoutputparser.cpp
infrastructure/settingsprofileloader.cpp
zjuconnectcontroller/zjuconnectcontroller.cpp
extrasettingwindow/extrasettingwindow.cpp
settingwindow/settingwindow.cpp
@@ -127,4 +128,12 @@ if(BUILD_TESTING)
target_include_directories(coreoutputparser_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(coreoutputparser_test Qt::Core)
add_test(NAME coreoutputparser_test COMMAND coreoutputparser_test)
add_executable(settingsprofileloader_test
tests/settingsprofileloader_test.cpp
infrastructure/settingsprofileloader.cpp
)
target_include_directories(settingsprofileloader_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(settingsprofileloader_test Qt::Core)
add_test(NAME settingsprofileloader_test COMMAND settingsprofileloader_test)
endif()

View File

@@ -0,0 +1,80 @@
#include "settingsprofileloader.h"
ConnectionProfile SettingsProfileLoader::load(
const QSettings &settings,
const QString &profileId,
const QString &username,
const QString &password
)
{
ConnectionProfile profile;
profile.profileId = profileId;
profile.credentials = {
username,
password,
settings.value("Credential/TOTPSecret").toString(),
settings.value("Credential/CertFile", "").toString(),
QByteArray::fromBase64(settings.value("Credential/CertPassword", "").toByteArray())
};
const QString countryCode = settings.value("ZJUConnect/PhoneCountryCode").toString();
const QString phoneNumber = settings.value("ZJUConnect/PhoneNumber").toString();
const QString phone = !countryCode.isEmpty() && !phoneNumber.isEmpty()
? countryCode + "-" + phoneNumber
: QString();
profile.endpoint = {
settings.value("ZJUConnect/Protocol").toString(),
settings.value("ZJUConnect/AuthType").toString(),
settings.value("ZJUConnect/LoginDomain").toString(),
phone,
settings.value("ZJUConnect/ServerAddress").toString(),
settings.value("ZJUConnect/ServerPort").toInt()
};
profile.dns = {
settings.value("ZJUConnect/DNS").toString(),
settings.value("ZJUConnect/DNSAuto").toBool(),
settings.value("ZJUConnect/SecondaryDNS").toString(),
settings.value("ZJUConnect/DNSTTL").toInt(),
settings.value("ZJUConnect/DisableZJUDNS").toBool(),
settings.value("ZJUConnect/CustomDNS", "").toString()
};
const QString bindPrefix = settings.value("ZJUConnect/OutsideAccess", false).toBool()
? "[::]:"
: "127.0.0.1:";
profile.proxy = {
bindPrefix + QString::number(settings.value("ZJUConnect/SOCKS5Port").toInt()),
bindPrefix + QString::number(settings.value("ZJUConnect/HTTPPort").toInt()),
settings.value("ZJUConnect/ShadowsocksURL").toString(),
settings.value("ZJUConnect/DialDirectProxy").toString(),
settings.value("ZJUConnect/ProxyAll").toBool(),
settings.value("ZJUConnect/CustomProxyDomain", "").toString()
};
profile.tunnel = {
settings.value("ZJUConnect/TUNMode").toBool(),
settings.value("ZJUConnect/AddRoute").toBool(),
settings.value("ZJUConnect/DNSHijack").toBool(),
settings.value("ZJUConnect/FakeIP").toBool(),
settings.value("ZJUConnect/TCPTunnelMode").toBool(),
settings.value("ZJUConnect/TCPPortForwarding").toString(),
settings.value("ZJUConnect/UDPPortForwarding").toString()
};
profile.behavior = {
settings.value("ZJUConnect/UpdateBestNodesInterval", 300).toInt(),
!settings.value("ZJUConnect/MultiLine").toBool(),
!settings.value("ZJUConnect/KeepAlive").toBool(),
settings.value("ZJUConnect/KeepAliveURL", "").toString(),
settings.value("ZJUConnect/BindInterface", "").toString(),
settings.value("ZJUConnect/AutoDetectInterface", false).toBool(),
settings.value("ZJUConnect/SkipDomainResource").toBool(),
settings.value("ZJUConnect/DisableServerConfig").toBool(),
!settings.value("ZJUConnect/ZJUDefault").toBool(),
settings.value("ZJUConnect/Debug").toBool()
};
profile.extraArguments = settings.value("ZJUConnect/ExtraArguments", "").toString();
return profile;
}

View File

@@ -0,0 +1,19 @@
#ifndef SETTINGSPROFILELOADER_H
#define SETTINGSPROFILELOADER_H
#include <QSettings>
#include "core/connectionprofile.h"
class SettingsProfileLoader
{
public:
static ConnectionProfile load(
const QSettings &settings,
const QString &profileId,
const QString &username,
const QString &password
);
};
#endif // SETTINGSPROFILELOADER_H

View File

@@ -0,0 +1,144 @@
#include <QCoreApplication>
#include <QDebug>
#include <QSettings>
#include <QTemporaryDir>
#include "infrastructure/settingsprofileloader.h"
namespace
{
bool loadsSettingsIntoTypedProfile()
{
QTemporaryDir directory;
if (!directory.isValid())
{
qCritical() << "Unable to create temporary directory";
return false;
}
QSettings settings(directory.filePath("profile.ini"), QSettings::IniFormat);
settings.setValue("Credential/TOTPSecret", "totp");
settings.setValue("Credential/CertFile", "/tmp/client.p12");
settings.setValue(
"Credential/CertPassword",
QString(QStringLiteral("cert-password").toUtf8().toBase64())
);
settings.setValue("ZJUConnect/Protocol", "atrust");
settings.setValue("ZJUConnect/AuthType", "cas");
settings.setValue("ZJUConnect/LoginDomain", "domain");
settings.setValue("ZJUConnect/PhoneCountryCode", "86");
settings.setValue("ZJUConnect/PhoneNumber", "123456");
settings.setValue("ZJUConnect/ServerAddress", "vpn.example.edu");
settings.setValue("ZJUConnect/ServerPort", 8443);
settings.setValue("ZJUConnect/DNS", "10.0.0.1");
settings.setValue("ZJUConnect/DNSAuto", false);
settings.setValue("ZJUConnect/SecondaryDNS", "10.0.0.2");
settings.setValue("ZJUConnect/DNSTTL", 60);
settings.setValue("ZJUConnect/DisableZJUDNS", true);
settings.setValue("ZJUConnect/CustomDNS", "example.org=1.1.1.1");
settings.setValue("ZJUConnect/OutsideAccess", true);
settings.setValue("ZJUConnect/SOCKS5Port", 1080);
settings.setValue("ZJUConnect/HTTPPort", 1081);
settings.setValue("ZJUConnect/ShadowsocksURL", "ss://url");
settings.setValue("ZJUConnect/DialDirectProxy", "http://direct");
settings.setValue("ZJUConnect/ProxyAll", true);
settings.setValue("ZJUConnect/CustomProxyDomain", "example.org");
settings.setValue("ZJUConnect/TUNMode", true);
settings.setValue("ZJUConnect/AddRoute", true);
settings.setValue("ZJUConnect/DNSHijack", true);
settings.setValue("ZJUConnect/FakeIP", true);
settings.setValue("ZJUConnect/TCPTunnelMode", true);
settings.setValue("ZJUConnect/TCPPortForwarding", "tcp-forward");
settings.setValue("ZJUConnect/UDPPortForwarding", "udp-forward");
settings.setValue("ZJUConnect/UpdateBestNodesInterval", 30);
settings.setValue("ZJUConnect/MultiLine", false);
settings.setValue("ZJUConnect/KeepAlive", false);
settings.setValue("ZJUConnect/KeepAliveURL", "https://keepalive");
settings.setValue("ZJUConnect/BindInterface", "en0");
settings.setValue("ZJUConnect/AutoDetectInterface", true);
settings.setValue("ZJUConnect/SkipDomainResource", true);
settings.setValue("ZJUConnect/DisableServerConfig", true);
settings.setValue("ZJUConnect/ZJUDefault", false);
settings.setValue("ZJUConnect/Debug", true);
settings.setValue("ZJUConnect/ExtraArguments", "-foo bar");
const ConnectionProfile profile =
SettingsProfileLoader::load(settings, "campus", "alice", "secret");
const bool passed =
profile.profileId == "campus"
&& profile.credentials.username == "alice"
&& profile.credentials.password == "secret"
&& profile.credentials.totpSecret == "totp"
&& profile.credentials.certFile == "/tmp/client.p12"
&& profile.credentials.certPassword == "cert-password"
&& profile.endpoint.protocol == "atrust"
&& profile.endpoint.authType == "cas"
&& profile.endpoint.loginDomain == "domain"
&& profile.endpoint.phone == "86-123456"
&& profile.endpoint.server == "vpn.example.edu"
&& profile.endpoint.port == 8443
&& profile.dns.primary == "10.0.0.1"
&& !profile.dns.automatic
&& profile.dns.secondary == "10.0.0.2"
&& profile.dns.ttl == 60
&& profile.dns.disableZjuDns
&& profile.dns.custom == "example.org=1.1.1.1"
&& profile.proxy.socksBind == "[::]:1080"
&& profile.proxy.httpBind == "[::]:1081"
&& profile.proxy.shadowsocksUrl == "ss://url"
&& profile.proxy.dialDirectProxy == "http://direct"
&& profile.proxy.proxyAll
&& profile.proxy.customDomains == "example.org"
&& profile.tunnel.tunMode
&& profile.tunnel.addRoute
&& profile.tunnel.dnsHijack
&& profile.tunnel.fakeIp
&& profile.tunnel.tcpTunnelMode
&& profile.tunnel.tcpPortForwarding == "tcp-forward"
&& profile.tunnel.udpPortForwarding == "udp-forward"
&& profile.behavior.updateBestNodesInterval == 30
&& profile.behavior.disableMultiLine
&& profile.behavior.disableKeepAlive
&& profile.behavior.keepAliveUrl == "https://keepalive"
&& profile.behavior.bindInterface == "en0"
&& profile.behavior.autoDetectInterface
&& profile.behavior.skipDomainResource
&& profile.behavior.disableServerConfig
&& profile.behavior.disableZjuConfig
&& profile.behavior.debugDump
&& profile.extraArguments == "-foo bar";
if (!passed)
{
qCritical() << "loadsSettingsIntoTypedProfile failed";
}
return passed;
}
bool usesCompatibleDefaults()
{
QTemporaryDir directory;
QSettings settings(directory.filePath("empty.ini"), QSettings::IniFormat);
const ConnectionProfile profile = SettingsProfileLoader::load(settings, "", "", "");
const bool passed =
profile.behavior.updateBestNodesInterval == 300
&& profile.behavior.disableMultiLine
&& profile.behavior.disableKeepAlive
&& profile.behavior.disableZjuConfig
&& profile.proxy.socksBind == "127.0.0.1:0"
&& profile.proxy.httpBind == "127.0.0.1:0";
if (!passed)
{
qCritical() << "usesCompatibleDefaults failed";
}
return passed;
}
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
return loadsSettingsIntoTypedProfile() && usesCompatibleDefaults() ? 0 : 1;
}

View File

@@ -188,69 +188,11 @@ QString ZjuConnectController::copyCoreForAppImage(const QString &programPath)
#endif
}
void ZjuConnectController::start(
const QString& program,
const QString& protocol,
const QString& authType,
const QString& loginDomain,
const QString& username,
const QString& password,
const QString& phone,
const QString& totpSecret,
const QString& server,
int port,
const QString& dns,
bool dnsAuto,
const QString& secondaryDns,
int dnsTtl,
const QString& socksBind,
const QString& httpBind,
const QString& shadowsocksUrl,
const QString& dialDirectProxy,
int updateBestNodesInterval,
bool disableMultiLine,
bool disableKeepAlive,
const QString& keepAliveUrl,
const QString& bindInterface,
bool autoDetectInterface,
bool skipDomainResource,
bool disableServerConfig,
bool proxyAll,
bool disableZjuDns,
bool disableZjuConfig,
bool debugDump,
bool tunMode,
bool addRoute,
bool dnsHijack,
bool fakeIp,
bool tcpTunnelMode,
const QString& tcpPortForwarding,
const QString& udpPortForwarding,
const QString& customDNS,
const QString& customProxyDomain,
const QString& certFile,
const QString& certPassword,
const QString& extraArguments,
const QString& profileId
)
void ZjuConnectController::start(const ConnectionProfile &profile)
{
ConnectionProfile profile;
profile.program = program;
profile.profileId = profileId;
profile.credentials = {username, password, totpSecret, certFile, certPassword};
profile.endpoint = {protocol, authType, loginDomain, phone, server, port};
profile.dns = {dns, dnsAuto, secondaryDns, dnsTtl, disableZjuDns, customDNS};
profile.proxy = {socksBind, httpBind, shadowsocksUrl, dialDirectProxy, proxyAll, customProxyDomain};
profile.tunnel = {tunMode, addRoute, dnsHijack, fakeIp, tcpTunnelMode,
tcpPortForwarding, udpPortForwarding};
profile.behavior = {updateBestNodesInterval, disableMultiLine, disableKeepAlive,
keepAliveUrl, bindInterface, autoDetectInterface, skipDomainResource,
disableServerConfig, disableZjuConfig, debugDump};
profile.extraArguments = extraArguments;
CoreRuntimePaths runtimePaths;
if (protocol == "atrust")
if (profile.endpoint.protocol == "atrust")
{
// 图形验证码文件路径
if (tempDir == nullptr)
@@ -260,27 +202,27 @@ void ZjuConnectController::start(
}
graphFile = tempDir->filePath("graph.jpg");
runtimePaths.graphCodeFile = graphFile;
runtimePaths.clientDataFile = Utils::getClientDataPath(profileId);
runtimePaths.clientDataFile = Utils::getClientDataPath(profile.profileId);
}
const CoreCommand command = CoreCommandBuilder::build(profile, runtimePaths);
QString timeString = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
emit outputRead(timeString + " VPN 启动!参数:" + command.loggableArguments.join(' '));
if (!totpSecret.isEmpty())
if (!profile.credentials.totpSecret.isEmpty())
{
emit outputRead(timeString + " 使用了 TOTP");
}
if (!certFile.isEmpty())
if (!profile.credentials.certFile.isEmpty())
{
emit outputRead(timeString + " 使用了证书文件");
}
QString programToStart = program;
QString programToStart = profile.program;
QStringList finalArgs = command.arguments;
#if defined(Q_OS_UNIX)
if (tunMode && !Utils::isRunningAsAdmin())
if (profile.tunnel.tunMode && !Utils::isRunningAsAdmin())
{
programToStart = copyCoreForAppImage(programToStart);

View File

@@ -3,6 +3,8 @@
#include <QtCore>
#include "core/connectionprofile.h"
enum class ZJU_ERROR
{
NONE,
@@ -29,51 +31,7 @@ public:
~ZjuConnectController() override;
void start(
const QString& program,
const QString& protocol,
const QString& authType,
const QString& loginDomain,
const QString& username,
const QString& password,
const QString& phone,
const QString& totpSecret,
const QString& server,
int port,
const QString& dns,
bool dnsAuto,
const QString& secondaryDns,
int dnsTtl,
const QString& socksBind,
const QString& httpBind,
const QString& shadowsocksUrl,
const QString& dialDirectProxy,
int updateBestNodesInterval,
bool disableMultiLine,
bool disableKeepAlive,
const QString& keepAliveUrl,
const QString& bindInterface,
bool autoDetectInterface,
bool skipDomainResource,
bool disableServerConfig,
bool proxyAll,
bool disableZjuDns,
bool disableZjuConfig,
bool debugDump,
bool tunMode,
bool addRoute,
bool dnsHijack,
bool fakeIp,
bool tcpTunnelMode,
const QString& tcpPortForwarding,
const QString& udpPortForwarding,
const QString& customDNS,
const QString& customProxyDomain,
const QString& certFile,
const QString& certPassword,
const QString& extraArguments,
const QString& profileId
);
void start(const ConnectionProfile &profile);
void stop();

View File

@@ -11,6 +11,7 @@
#include "ui_mainwindow.h"
#include "sudowindow/sudowindow.h"
#include "loginwindow/loginwindow.h"
#include "infrastructure/settingsprofileloader.h"
#include "utils/utils.h"
#include "zjuconnectcontroller/zjuconnectcontroller.h"
@@ -280,9 +281,6 @@ void MainWindow::initZjuConnect()
#endif
auto startZjuConnect = [this](const QString &username, const QString &password) {
QString program_path = Utils::getCorePath();
QString bind_prefix = settings->value("ZJUConnect/OutsideAccess", false).toBool() ? "[::]:" : "127.0.0.1:";
isZjuConnectLinked = true;
zjuConnectError = ZJU_ERROR::NONE;
ui->pushButton1->setText("断开服务器");
@@ -295,50 +293,10 @@ void MainWindow::initZjuConnect()
}
isAutoReconnecting = false;
QString countryCode = settings->value("ZJUConnect/PhoneCountryCode").toString();
QString phoneNumber = settings->value("ZJUConnect/PhoneNumber").toString();
QString phone = !countryCode.isEmpty() && !phoneNumber.isEmpty() ? (countryCode + "-" + phoneNumber) : "";
zjuConnectController->start(
program_path, settings->value("ZJUConnect/Protocol").toString(),
settings->value("ZJUConnect/AuthType").toString(),
settings->value("ZJUConnect/LoginDomain").toString(), username, password, phone,
settings->value("Credential/TOTPSecret").toString(),
settings->value("ZJUConnect/ServerAddress").toString(),
settings->value("ZJUConnect/ServerPort").toInt(),
settings->value("ZJUConnect/DNS").toString(),
settings->value("ZJUConnect/DNSAuto").toBool(),
settings->value("ZJUConnect/SecondaryDNS").toString(),
settings->value("ZJUConnect/DNSTTL").toInt(),
bind_prefix + QString::number(settings->value("ZJUConnect/SOCKS5Port").toInt()),
bind_prefix + QString::number(settings->value("ZJUConnect/HTTPPort").toInt()),
settings->value("ZJUConnect/ShadowsocksURL").toString(),
settings->value("ZJUConnect/DialDirectProxy").toString(),
settings->value("ZJUConnect/UpdateBestNodesInterval", 300).toInt(),
!settings->value("ZJUConnect/MultiLine").toBool(),
!settings->value("ZJUConnect/KeepAlive").toBool(),
settings->value("ZJUConnect/KeepAliveURL", "").toString(),
settings->value("ZJUConnect/BindInterface", "").toString(),
settings->value("ZJUConnect/AutoDetectInterface", false).toBool(),
settings->value("ZJUConnect/SkipDomainResource").toBool(),
settings->value("ZJUConnect/DisableServerConfig").toBool(),
settings->value("ZJUConnect/ProxyAll").toBool(),
settings->value("ZJUConnect/DisableZJUDNS").toBool(),
!settings->value("ZJUConnect/ZJUDefault").toBool(),
settings->value("ZJUConnect/Debug").toBool(),
settings->value("ZJUConnect/TUNMode").toBool(),
settings->value("ZJUConnect/AddRoute").toBool(),
settings->value("ZJUConnect/DNSHijack").toBool(),
settings->value("ZJUConnect/FakeIP").toBool(),
settings->value("ZJUConnect/TCPTunnelMode").toBool(),
settings->value("ZJUConnect/TCPPortForwarding").toString(),
settings->value("ZJUConnect/UDPPortForwarding").toString(),
settings->value("ZJUConnect/CustomDNS", "").toString(),
settings->value("ZJUConnect/CustomProxyDomain", "").toString(),
settings->value("Credential/CertFile", "").toString(),
QByteArray::fromBase64(settings->value("Credential/CertPassword", "").toString().toUtf8()),
settings->value("ZJUConnect/ExtraArguments", "").toString(),
currentProfileId);
ConnectionProfile profile =
SettingsProfileLoader::load(*settings, currentProfileId, username, password);
profile.program = Utils::getCorePath();
zjuConnectController->start(profile);
};
if (((protocol == "atrust" && authtype == "psw") ||