mirror of
https://github.com/Aurora1949/AHNU-Portal-Authenticator.git
synced 2026-03-04 09:33:08 +08:00
first commit
This commit is contained in:
122
.gitignore
vendored
Normal file
122
.gitignore
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
# vscode ================================
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# C++ ================================
|
||||||
|
Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
|
||||||
|
# CMake ================================
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
CMakeLists.txt.user
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CMakeScripts
|
||||||
|
Testing
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
CTestTestfile.cmake
|
||||||
69
CMakeLists.txt
Normal file
69
CMakeLists.txt
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.0)
|
||||||
|
project(AHNU-Portal-Authenticator)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
|
set(APP_NAME AHNU)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/icon.rc")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
find_package(Qt6 COMPONENTS
|
||||||
|
Core
|
||||||
|
Gui
|
||||||
|
Widgets
|
||||||
|
Network
|
||||||
|
REQUIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(${APP_NAME}
|
||||||
|
WIN32
|
||||||
|
main.cpp
|
||||||
|
mainwindow.cpp
|
||||||
|
mainwindow.h
|
||||||
|
mainwindow.ui
|
||||||
|
res.qrc
|
||||||
|
${app_icon_resource_windows}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${APP_NAME} PRIVATE PRIVATE APPNAME="${APP_NAME}")
|
||||||
|
|
||||||
|
target_link_libraries(${APP_NAME}
|
||||||
|
Qt::Core
|
||||||
|
Qt::Gui
|
||||||
|
Qt::Widgets
|
||||||
|
Qt::Network
|
||||||
|
)
|
||||||
|
|
||||||
|
if (WIN32 AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
|
||||||
|
set(DEBUG_SUFFIX)
|
||||||
|
if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
set(DEBUG_SUFFIX "d")
|
||||||
|
endif ()
|
||||||
|
set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")
|
||||||
|
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
|
||||||
|
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
|
||||||
|
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
|
||||||
|
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")
|
||||||
|
add_custom_command(TARGET ${APP_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
"$<TARGET_FILE_DIR:${APP_NAME}>/plugins/platforms/")
|
||||||
|
add_custom_command(TARGET ${APP_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
"${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
|
||||||
|
"$<TARGET_FILE_DIR:${APP_NAME}>/plugins/platforms/")
|
||||||
|
endif ()
|
||||||
|
foreach (QT_LIB Core Gui Widgets Network)
|
||||||
|
add_custom_command(TARGET ${APP_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
"${QT_INSTALL_PATH}/bin/Qt6${QT_LIB}${DEBUG_SUFFIX}.dll"
|
||||||
|
"$<TARGET_FILE_DIR:${APP_NAME}>")
|
||||||
|
endforeach (QT_LIB)
|
||||||
|
endif ()
|
||||||
52
README.md
Normal file
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# AHNU Portal Authenticator
|
||||||
|
|
||||||
|
**AHNU Portal Authenticator** 是一个为 **安徽师范大学校园网(AHNU)** 设计的轻量级网络认证工具,
|
||||||
|
实现了登录与在线状态维持。支持 **开机自启**、**后台运行**、**实时在线检测**, 让校园网登录更加稳定、无感、安全。
|
||||||
|
本项目受到此[项目]('https://github.com/SweetCaviar/AHNU-Network-Automatic-Csharp')的启发应运而生。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- **使用 Qt 编写**
|
||||||
|
具有直观、简洁的使用界面。
|
||||||
|
|
||||||
|
- **Portal 协议认证**
|
||||||
|
自动完成校园网账号认证流程,支持账户与服务商选择。
|
||||||
|
|
||||||
|
- **开机自启**
|
||||||
|
自动注册到系统启动项,无需手动登录即可联网。
|
||||||
|
|
||||||
|
- **后台运行**
|
||||||
|
无窗口静默运行,不打扰使用体验,可在系统托盘中查看状态。
|
||||||
|
|
||||||
|
- **实时在线监测**
|
||||||
|
定时检测网络连接状态,掉线后提示,保证网络稳定在线。
|
||||||
|
|
||||||
|
- **多服务商支持**
|
||||||
|
支持多个运营商(电信、移动、联通)认证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 编译方法
|
||||||
|
|
||||||
|
1. **环境准备**
|
||||||
|
|
||||||
|
| 名称 | 推荐版本 | 说明 |
|
||||||
|
|-------|-----------------------|-------------------|
|
||||||
|
| Qt | ≥ 6.5 | 推荐安装 Qt 6.6 或更新版本 |
|
||||||
|
| CMake | ≥ 4.0 | 用于跨平台构建 |
|
||||||
|
| 编译器 | MSVC 2022 / MinGW 11+ | 均可使用 |
|
||||||
|
|
||||||
|
2. **编译**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Aurora1949/AHNU-Portal-Authenticator.git
|
||||||
|
cd AHNU-Portal-Authenticator
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
BIN
images/app.ico
Normal file
BIN
images/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 560 KiB |
BIN
images/banner.png
Normal file
BIN
images/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
images/logo.png
Normal file
BIN
images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 558 KiB |
56
main.cpp
Normal file
56
main.cpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QLocalServer>
|
||||||
|
|
||||||
|
bool isAlreadyRunning() {
|
||||||
|
QLocalSocket socket;
|
||||||
|
socket.connectToServer(APPNAME);
|
||||||
|
|
||||||
|
// 已有实例存在
|
||||||
|
if (socket.waitForConnected(100)) {
|
||||||
|
socket.write("raise");
|
||||||
|
socket.flush();
|
||||||
|
socket.waitForBytesWritten(100);
|
||||||
|
socket.disconnectFromServer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试清除之前未正常退出的 socket 文件
|
||||||
|
QLocalServer::removeServer(APPNAME);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
if (isAlreadyRunning()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow w;
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
// 建立本地 server
|
||||||
|
QLocalServer server;
|
||||||
|
if (!server.listen(APPNAME)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(&server, &QLocalServer::newConnection, [&]() {
|
||||||
|
QLocalSocket *client = server.nextPendingConnection();
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
QObject::connect(client, &QLocalSocket::readyRead, [&]() {
|
||||||
|
QByteArray msg = client->readAll();
|
||||||
|
if (msg == "raise") {
|
||||||
|
w.showFromTray();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(client, &QLocalSocket::disconnected, client, &QLocalSocket::deleteLater);
|
||||||
|
});
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
||||||
309
mainwindow.cpp
Normal file
309
mainwindow.cpp
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QCloseEvent>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QNetworkProxy>
|
||||||
|
|
||||||
|
#include "ui_mainwindow.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
|
: QMainWindow(parent)
|
||||||
|
, ui(new Ui::MainWindow)
|
||||||
|
, provider({{"中国移动", "cmcc"}, {"中国电信", "telecom"}, {"中国联通", "unicom"}})
|
||||||
|
, baseUrl("http://100.64.4.10:801/eportal/portal/login")
|
||||||
|
, testUrl("http://www.baidu.com")
|
||||||
|
, logoutUrl("http://100.64.4.10:801/eportal/portal/logout")
|
||||||
|
, isOnline(false)
|
||||||
|
, trayMenu(new QMenu(this))
|
||||||
|
, trayIcon(new QSystemTrayIcon(this))
|
||||||
|
, networkChecker(new QTimer(this)) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
init();
|
||||||
|
getUserInfo();
|
||||||
|
testOnline();
|
||||||
|
doAutoRun();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::init() {
|
||||||
|
setWindowTitle("AHNU上号器");
|
||||||
|
setWindowIcon(QIcon(":/images/logo.png"));
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint & ~Qt::WindowMinimizeButtonHint);
|
||||||
|
setFixedSize(400, 178);
|
||||||
|
|
||||||
|
manager.setProxy(QNetworkProxy::NoProxy);
|
||||||
|
|
||||||
|
ui->logoLabel->setPixmap(QPixmap(":/images/banner.png"));
|
||||||
|
for (const auto &p: provider)
|
||||||
|
ui->providerComboBox->addItem(p.name, p.value);
|
||||||
|
ui->selfStartup->setChecked(isAutoRunEnabled());
|
||||||
|
|
||||||
|
trayIcon->setIcon(QIcon(":/images/logo.png"));
|
||||||
|
trayIcon->setToolTip(TrayIconMsg::Offline);
|
||||||
|
|
||||||
|
QAction *showAction = new QAction("显示窗口", this);
|
||||||
|
QAction *exitAction = new QAction("退出", this);
|
||||||
|
trayMenu->addAction(showAction);
|
||||||
|
trayMenu->addSeparator();
|
||||||
|
trayMenu->addAction(exitAction);
|
||||||
|
|
||||||
|
trayIcon->setContextMenu(trayMenu);
|
||||||
|
|
||||||
|
connect(networkChecker, &QTimer::timeout, this, &MainWindow::testOnline);
|
||||||
|
|
||||||
|
connect(showAction, &QAction::triggered, this, &MainWindow::onShowFromTray);
|
||||||
|
connect(exitAction, &QAction::triggered, this, &MainWindow::onQuitFromTray);
|
||||||
|
connect(trayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
|
||||||
|
if (reason == QSystemTrayIcon::Trigger) // 单击托盘图标
|
||||||
|
onShowFromTray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow() {
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_loginBtn_clicked() {
|
||||||
|
doLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::online() {
|
||||||
|
ui->loginBtn->setText("登出");
|
||||||
|
ui->loginBtn->setStyleSheet("color: red;");
|
||||||
|
trayIcon->setToolTip(TrayIconMsg::Online);
|
||||||
|
|
||||||
|
setInputEnable(false);
|
||||||
|
networkChecker->start(5000);
|
||||||
|
|
||||||
|
isOnline = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::offline() {
|
||||||
|
ui->loginBtn->setText("登录");
|
||||||
|
ui->loginBtn->setStyleSheet("color: black;");
|
||||||
|
trayIcon->setToolTip(TrayIconMsg::Offline);
|
||||||
|
|
||||||
|
setInputEnable(true);
|
||||||
|
if (!this->isVisible())
|
||||||
|
onShowFromTray();
|
||||||
|
|
||||||
|
networkChecker->stop();
|
||||||
|
|
||||||
|
isOnline = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::setInputEnable(bool enable) {
|
||||||
|
ui->passwdLe->setEnabled(enable);
|
||||||
|
ui->usernameLe->setEnabled(enable);
|
||||||
|
ui->providerComboBox->setEnabled(enable);
|
||||||
|
ui->rememberMe->setEnabled(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::saveUserInfo() {
|
||||||
|
// 获取 APPDATA 路径
|
||||||
|
QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
QDir dir(appDataPath);
|
||||||
|
if (!dir.exists())
|
||||||
|
dir.mkpath(appDataPath);
|
||||||
|
|
||||||
|
QString filePath = appDataPath + "/userinfo.json";
|
||||||
|
QFile file(filePath);
|
||||||
|
|
||||||
|
// 创建 JSON 对象
|
||||||
|
QJsonObject userInfo;
|
||||||
|
userInfo["username"] = ui->usernameLe->text();
|
||||||
|
userInfo["password"] = ui->passwdLe->text();
|
||||||
|
userInfo["provider"] = ui->providerComboBox->currentText();
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||||
|
QJsonDocument doc(userInfo);
|
||||||
|
file.write(doc.toJson(QJsonDocument::Indented));
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
QMessageBox::warning(this, "注意", "无法保存账户与密码");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::getUserInfo() {
|
||||||
|
QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
QString filePath = appDataPath + "/userinfo.json";
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
QJsonObject userInfo;
|
||||||
|
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
QByteArray data = file.readAll();
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||||
|
if (doc.isObject())
|
||||||
|
userInfo = doc.object();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
ui->usernameLe->setText(userInfo.take("username").toString());
|
||||||
|
ui->passwdLe->setText(userInfo.take("password").toString());
|
||||||
|
ui->providerComboBox->setEditText(userInfo.take("provider").toString());
|
||||||
|
ui->rememberMe->setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::testOnline() {
|
||||||
|
QNetworkRequest request{testUrl};
|
||||||
|
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
||||||
|
QNetworkRequest::ManualRedirectPolicy);
|
||||||
|
QNetworkReply *reply{manager.head(request)};
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply] {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QUrl reUrl{reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl()};
|
||||||
|
if (reUrl.host() == "rz.ahnu.edu.cn") {
|
||||||
|
ui->statusbar->showMessage(StatusBarMsg::AlreadyOffline);
|
||||||
|
offline();
|
||||||
|
} else {
|
||||||
|
ui->statusbar->showMessage(StatusBarMsg::AlreadyOnline);
|
||||||
|
online();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::setAutoRun(bool isStart) {
|
||||||
|
QString appName = QApplication::applicationName(); // 获取应用名称
|
||||||
|
QSettings settings(RegKey, QSettings::NativeFormat); // 创建QSettings对象
|
||||||
|
|
||||||
|
if (isStart) {
|
||||||
|
QString appPath = QApplication::applicationFilePath(); // 获取应用路径
|
||||||
|
appPath += " --auto-start";
|
||||||
|
settings.setValue(appName, appPath.replace("/", "\\")); // 写入注册表
|
||||||
|
} else {
|
||||||
|
settings.remove(appName); // 从注册表中删除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::isAutoRunEnabled() {
|
||||||
|
QSettings settings(RegKey, QSettings::NativeFormat);
|
||||||
|
QString appPath = QDir::toNativeSeparators(QApplication::applicationFilePath());
|
||||||
|
return settings.value(QApplication::applicationName()).toString().split(" ")[0] == appPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_selfStartup_checkStateChanged() {
|
||||||
|
setAutoRun(ui->selfStartup->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||||
|
hideToBackground();
|
||||||
|
event->ignore(); // 阻止真正关闭
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onShowFromTray() {
|
||||||
|
showNormal();
|
||||||
|
activateWindow(); // 把窗口带到前台
|
||||||
|
trayIcon->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onQuitFromTray() {
|
||||||
|
qApp->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::showFromTray() {
|
||||||
|
onShowFromTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::doAutoRun() {
|
||||||
|
if (!QCoreApplication::arguments().contains("--auto-start"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!isOnline) {
|
||||||
|
doLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
QTimer::singleShot(500, [this] {
|
||||||
|
if (isOnline) {
|
||||||
|
hideToBackground();
|
||||||
|
trayIcon->showMessage(APPNAME, TrayIconMsg::AutoHideToBackground);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::doLogin() {
|
||||||
|
QNetworkRequest request;
|
||||||
|
|
||||||
|
if (ui->rememberMe->isChecked()) {
|
||||||
|
saveUserInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnline) {
|
||||||
|
request.setUrl(logoutUrl);
|
||||||
|
} else {
|
||||||
|
ui->statusbar->showMessage(QString(StatusBarMsg::WaitForProvider).arg(ui->providerComboBox->currentText()));
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("user_account", ui->usernameLe->text());
|
||||||
|
query.addQueryItem("user_password", ui->passwdLe->text());
|
||||||
|
query.addQueryItem("provider", ui->providerComboBox->currentData().toString());
|
||||||
|
baseUrl.setQuery(query);
|
||||||
|
|
||||||
|
request.setUrl(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply{manager.get(request)};
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply] {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray d{reply->readAll()};
|
||||||
|
d.remove(0, 12); // remove jsonpReturn("
|
||||||
|
d.chop(2); // remove ")
|
||||||
|
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument jd{QJsonDocument::fromJson(d, &err)};
|
||||||
|
QString msg;
|
||||||
|
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
QMessageBox::critical(this, "数据解析错误", err.errorString() + "\n" + d);
|
||||||
|
reply->deleteLater();
|
||||||
|
ui->statusbar->clearMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonObject jo{jd.object()};
|
||||||
|
|
||||||
|
if (isOnline) {
|
||||||
|
if (jo.contains("msg"))
|
||||||
|
msg = jo.take("msg").toString();
|
||||||
|
offline();
|
||||||
|
} else {
|
||||||
|
LoginStatus result{0};
|
||||||
|
RetCode code{0};
|
||||||
|
if (jo.contains("msg"))
|
||||||
|
msg = jo.take("msg").toString();
|
||||||
|
|
||||||
|
if (jo.contains("result"))
|
||||||
|
result = static_cast<LoginStatus>(jo.take("result").toInt());
|
||||||
|
|
||||||
|
if (jo.contains("ret_code") && jo.value("ret_code").isDouble()) {
|
||||||
|
code = static_cast<RetCode>(jo.take("ret_code").toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == LoginStatus::Success
|
||||||
|
|| (result == LoginStatus::Failed && code == RetCode::AlreadyOnline)) {
|
||||||
|
online();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->statusbar->showMessage(QString(StatusBarMsg::MsgFromProvider).arg(msg));
|
||||||
|
} else {
|
||||||
|
ui->statusbar->showMessage(QString(StatusBarMsg::NetworkError).arg(reply->errorString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::hideToBackground() {
|
||||||
|
hide();
|
||||||
|
trayIcon->show();
|
||||||
|
}
|
||||||
111
mainwindow.h
Normal file
111
mainwindow.h
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#ifndef MAINWINDOW_H
|
||||||
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
#include <vector>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainWindow(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
~MainWindow() override;
|
||||||
|
|
||||||
|
void showFromTray();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_loginBtn_clicked();
|
||||||
|
|
||||||
|
void on_selfStartup_checkStateChanged();
|
||||||
|
|
||||||
|
void onShowFromTray();
|
||||||
|
|
||||||
|
void onQuitFromTray();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void online();
|
||||||
|
|
||||||
|
void offline();
|
||||||
|
|
||||||
|
void setInputEnable(bool);
|
||||||
|
|
||||||
|
void saveUserInfo();
|
||||||
|
|
||||||
|
void getUserInfo();
|
||||||
|
|
||||||
|
void testOnline();
|
||||||
|
|
||||||
|
void setAutoRun(bool);
|
||||||
|
|
||||||
|
bool isAutoRunEnabled();
|
||||||
|
|
||||||
|
void doAutoRun();
|
||||||
|
|
||||||
|
void doLogin();
|
||||||
|
|
||||||
|
void hideToBackground();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Provider {
|
||||||
|
QString name;
|
||||||
|
QString value;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LoginStatus {
|
||||||
|
Failed,
|
||||||
|
Success,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RetCode {
|
||||||
|
Unknown,
|
||||||
|
WrongAccountOrPasswd,
|
||||||
|
AlreadyOnline,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TrayIconMsg {
|
||||||
|
public:
|
||||||
|
static constexpr char Online[]{"AHNU上号器正在后台运行\n目前状态:在线"};
|
||||||
|
static constexpr char Offline[]{"AHNU上号器正在后台运行\n目前状态:离线"};
|
||||||
|
static constexpr char AutoHideToBackground[]{"你已成功上线,AHNU上号器正在后台运行"};
|
||||||
|
};
|
||||||
|
|
||||||
|
class StatusBarMsg {
|
||||||
|
public:
|
||||||
|
static constexpr char WaitForProvider[]{"正在登录到%1..."};
|
||||||
|
static constexpr char AlreadyOnline[]{"你已经连接上互联网"};
|
||||||
|
static constexpr char NetworkError[]{"认证服务器连接错误:%1"};
|
||||||
|
static constexpr char MsgFromProvider[]{"认证服务器:%1"};
|
||||||
|
static constexpr char AlreadyOffline[]{"已断开连接"};
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr char RegKey[]{"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"};
|
||||||
|
|
||||||
|
Ui::MainWindow *ui;
|
||||||
|
std::vector<Provider> provider;
|
||||||
|
QUrl baseUrl;
|
||||||
|
QUrl testUrl;
|
||||||
|
QUrl logoutUrl;
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
bool isOnline;
|
||||||
|
QSystemTrayIcon *trayIcon;
|
||||||
|
QMenu *trayMenu;
|
||||||
|
QTimer *networkChecker;
|
||||||
|
};
|
||||||
|
#endif // MAINWINDOW_H
|
||||||
242
mainwindow.ui
Normal file
242
mainwindow.ui
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>402</width>
|
||||||
|
<height>219</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>402</width>
|
||||||
|
<height>178</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>402</width>
|
||||||
|
<height>219</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>MainWindow</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SizeConstraint::SetFixedSize</enum>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="logoLabel">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777209</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>TextLabel</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>80</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>80</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>账 号</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="usernameLe"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>密 码</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="passwdLe">
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::EchoMode::Password</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>运营商</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="providerComboBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="selfStartup">
|
||||||
|
<property name="text">
|
||||||
|
<string>开机自启</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="rememberMe">
|
||||||
|
<property name="text">
|
||||||
|
<string>记住我</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="loginBtn">
|
||||||
|
<property name="text">
|
||||||
|
<string>登录</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>402</width>
|
||||||
|
<height>21</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
Reference in New Issue
Block a user