《Qt 手写 HTTP 登录服务实战》

Qt 手写 HTTP 登录服务实战:从 0 到 1 实现前后端认证闭环

关键词 :Qt、QTcpServer、HTTP Server、登录认证、前后端分离
适用环境 :Qt 4.8+ / Qt 5、VS2010、Win7、IE
特点:无第三方库、纯 Qt、稳定可控


一、背景说明

本文将完整讲解:

  • 如何使用 QTcpServer 实现 HTTP 服务
  • 如何处理 GET 静态页面 + POST 登录认证
  • 如何与前端 HTML 完整打通
  • 如何让 认证结果完全由后端控制

二、整体架构设计

1️⃣ 效果展示

2️⃣ 功能拆分

功能 实现方式
HTTP Server QTcpServer
静态页面 GET /index.html
登录认证 POST /login
数据格式 x-www-form-urlencoded
返回结果 JSON

三、Qt 手写 HTTP Server 实现

1️⃣ 继承 QTcpServer


设计原则:

  • 前端只负责展示
  • 后端完全控制认证逻辑
  • HTTP 短连接,简单可靠

四、HttpServer 类设计(头文件)

HttpServer.h

cpp 复制代码
#ifndef HTTPSERVER_H
#define HTTPSERVER_H

#include <QTcpServer>
#include <QTcpSocket>

/**
 * @brief HttpServer
 * @author yangyang
 *
 * @details
 * 一个基于 QTcpServer 的极简 HTTP 服务器实现,主要用于:
 *
 * 1️⃣ 提供静态网页访问(如 index.html)
 * 2️⃣ 接收浏览器发起的 POST /login 请求
 * 3️⃣ 解析 HTTP 报文并返回 JSON 格式的认证结果
 *
 * 【适用场景】
 * - 内嵌式 Web 管理页面
 * - 本地配置工具(Qt + HTML)
 * - BS 架构 Demo / 教学示例
 *
 * 【设计说明】
 * - 不依赖 QtWebEngine / CEF
 * - 手动解析 HTTP 请求头和 Body
 * - 单连接、短连接模型(请求完成即断开)
 * - 适合轻量级、低并发场景
 *
 * 【兼容性】
 * - Qt 4.8 及以上
 * - Windows / Linux
 */
class HttpServer : public QTcpServer
{
    Q_OBJECT
public:
    /**
     * @brief 构造函数
     * @param parent QObject 父对象,用于 Qt 对象树管理
     *
     * 仅完成基础初始化,不启动监听
     */
    explicit HttpServer(QObject *parent = 0);

    /**
     * @brief 启动 HTTP 服务
     * @param port 监听端口号
     * @return true  启动成功
     * @return false 启动失败(端口被占用等)
     *
     * 内部调用 QTcpServer::listen()
     */
    bool start(quint16 port);

protected:
    /**
     * @brief 新客户端连接回调
     * @param socketDescriptor 系统层 socket 描述符
     *
     * Qt 在有新 TCP 连接时自动调用该函数
     *
     * 常规处理流程:
     * 1️⃣ 创建 QTcpSocket
     * 2️⃣ setSocketDescriptor(socketDescriptor)
     * 3️⃣ 绑定 readyRead / disconnected 信号
     * 4️⃣ 等待客户端发送 HTTP 请求
     */
    void incomingConnection(qintptr socketDescriptor);

private slots:
    /**
     * @brief Socket 可读事件槽函数
     *
     * 当浏览器发送 HTTP 请求数据时触发:
     * - 读取完整 HTTP 请求
     * - 解析请求方法 / URL / Body
     * - 调用 handleRequest() 生成响应数据
     * - 通过 socket->write() 返回给客户端
     */
    void onReadyRead();

private:
    /**
     * @brief 处理 HTTP 请求核心函数
     * @param request 原始 HTTP 请求数据
     * @return 完整 HTTP 响应数据
     *
     * 主要逻辑:
     * - 解析请求行(GET / POST)
     * - 区分路径(/ 或 /login)
     * - GET  :返回 index.html
     * - POST :解析 JSON / 表单并返回认证结果
     */
    QByteArray handleRequest(const QByteArray &request);

    /**
     * @brief 生成标准 HTTP 响应报文
     * @param body 响应体内容(HTML / JSON)
     * @param type Content-Type 类型
     * @param code HTTP 状态码(默认 200)
     * @return 拼接好的 HTTP 响应
     *
     * 返回格式示例:
     * HTTP/1.1 200 OK
     * Content-Type: application/json
     * Content-Length: xxx
     *
     * { "result": 1 }
     */
    QByteArray makeResponse(const QByteArray &body,
                            const QByteArray &type,
                            int code = 200);

    /**
     * @brief 根据文件路径获取 MIME 类型
     * @param filePath 文件路径
     * @return 对应的 Content-Type
     *
     * 示例:
     * - .html -> text/html
     * - .css  -> text/css
     * - .js   -> application/javascript
     * - .json -> application/json
     */
    QByteArray contentType(const QString &filePath);
};

#endif // HTTPSERVER_H

HttpServer.cpp

cpp 复制代码
#include "HttpServer.h"
#include <QFile>
#include <QUrlQuery>
#include <QDebug>
#include <QCoreApplication>

/**
 * @brief HttpServer 构造函数
 * @param parent 父对象
 *
 * 仅调用 QTcpServer 构造函数,
 * 不做任何网络监听操作
 */
HttpServer::HttpServer(QObject *parent)
    : QTcpServer(parent)
{
}

/**
 * @brief 启动 HTTP Server
 * @param port 监听端口
 * @return true 监听成功
 * @return false 监听失败(端口被占用等)
 *
 * 使用 Any 地址,允许本机及局域网访问
 */
bool HttpServer::start(quint16 port)
{
    return listen(QHostAddress::Any, port);
}

/**
 * @brief 新 TCP 客户端连接回调
 * @param socketDescriptor 系统 socket 描述符
 *
 * 当浏览器或客户端连接到服务器端口时,
 * Qt 框架会自动调用此函数
 *
 * 处理流程:
 * 1️⃣ 创建 QTcpSocket
 * 2️⃣ 将系统 socket 与 Qt socket 绑定
 * 3️⃣ 监听数据读取和断开连接信号
 */
void HttpServer::incomingConnection(qintptr socketDescriptor)
{
    QTcpSocket *socket = new QTcpSocket(this);

    // 将底层 socket 句柄绑定到 Qt 的 QTcpSocket
    socket->setSocketDescriptor(socketDescriptor);

    // 当有数据可读时,触发 onReadyRead()
    connect(socket, SIGNAL(readyRead()),
            this, SLOT(onReadyRead()));

    // 客户端断开后自动释放 socket
    connect(socket, SIGNAL(disconnected()),
            socket, SLOT(deleteLater()));
}

/**
 * @brief Socket 数据可读槽函数
 *
 * 浏览器发送完整 HTTP 请求后触发:
 * - 读取 HTTP 请求原始数据
 * - 交由 handleRequest() 处理
 * - 将响应写回客户端
 * - 主动断开连接(短连接)
 */
void HttpServer::onReadyRead()
{
    // 获取触发信号的 socket 对象
    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
    if (!socket) return;

    // 读取浏览器发送的完整 HTTP 请求
    QByteArray request = socket->readAll();

    // 打印原始 HTTP 请求,方便调试
    qDebug() << "\n----- REQUEST -----\n" << request;

    // 处理请求并生成响应
    QByteArray response = handleRequest(request);

    // 发送 HTTP 响应
    socket->write(response);

    // HTTP 短连接,发送完成后断开
    socket->disconnectFromHost();
}

/**
 * @brief HTTP 请求处理核心函数
 * @param req 原始 HTTP 请求数据
 * @return 完整 HTTP 响应
 *
 * 支持两类请求:
 * - POST /login   :处理登录认证
 * - GET  /xxx     :返回静态文件
 */
QByteArray HttpServer::handleRequest(const QByteArray &req)
{
    /* ================= POST /login ================= */
    if (req.startsWith("POST /login")) {

        // 找到 HTTP 头与 Body 的分隔位置
        int idx = req.indexOf("\r\n\r\n");

        // 提取 POST 请求体
        QByteArray body = req.mid(idx + 4);

        // 使用 QUrlQuery 解析表单数据
        QUrlQuery query;
        query.setQuery(body);

        // 获取表单字段
        QString pwd      = query.queryItemValue("password");
        QString name     = query.queryItemValue("name");
        QString grade    = query.queryItemValue("grade");
        QString cls      = query.queryItemValue("class");
        QString location = query.queryItemValue("location");
        QString sid      = query.queryItemValue("sid");

        // 输出认证信息,方便调试
        qDebug() << "AuthPwd:" << pwd
                 << "Name:" << name
                 << "Grade:" << grade
                 << "Class:" << cls
                 << "Location:" << location
                 << "SID:" << sid;

        int code = 0;      // 认证结果码
        QString msg;       // 返回信息

        // 简单权限密码校验
        if (pwd == "admin123") {
            code = 1;
            msg = QString::fromLocal8Bit("ABCDEFG");
        } else {
            msg = QString::fromLocal8Bit("认证失败,权限密码错误");
        }

        // 构造 JSON 返回结果
        QByteArray json =
            QString("{\"code\":%1,\"msg\":\"%2\"}")
            .arg(code)
            .arg(msg)
            .toUtf8();

        // 返回 JSON 响应
        return makeResponse(json,
            "application/json; charset=utf-8");
    }

    /* ================= GET 静态文件 ================= */
    if (req.startsWith("GET ")) {

        // 解析请求路径:GET /xxx HTTP/1.1
        int p1 = req.indexOf(' ');
        int p2 = req.indexOf(' ', p1 + 1);
        QByteArray path = req.mid(p1 + 1, p2 - p1 - 1);

        // 根路径默认返回 index.html
        if (path == "/")
            path = "/index.html";

        // 获取程序 EXE 所在目录
        QString baseDir =
            QCoreApplication::applicationDirPath();

        // 拼接静态文件完整路径
        QString filePath = baseDir + path;
        QFile file(filePath);

        // 文件不存在或打开失败
        if (!file.open(QIODevice::ReadOnly)) {
            return makeResponse(
                "404 Not Found",
                "text/plain", 404);
        }

        // 读取文件内容
        QByteArray data = file.readAll();
        file.close();

        // 返回文件数据
        return makeResponse(
            data,
            contentType(filePath));
    }

    // 未支持的请求类型
    return makeResponse("Bad Request",
                        "text/plain", 400);
}

/**
 * @brief 构造 HTTP 响应
 * @param body 响应体内容
 * @param type Content-Type
 * @param code HTTP 状态码
 * @return 完整 HTTP 响应数据
 *
 * 统一拼装 HTTP 响应头和 Body
 */
QByteArray HttpServer::makeResponse(
        const QByteArray &body,
        const QByteArray &type,
        int code)
{
    // 根据状态码生成状态行
    QByteArray status =
        (code == 200) ? "HTTP/1.1 200 OK\r\n"
                      : "HTTP/1.1 404 Not Found\r\n";

    return status +
        "Content-Type: " + type + "\r\n" +
        "Content-Length: " +
        QByteArray::number(body.size()) + "\r\n" +
        "Connection: close\r\n\r\n" +
        body;
}

/**
 * @brief 根据文件后缀返回 MIME 类型
 * @param filePath 文件路径
 * @return Content-Type 字符串
 *
 * 用于浏览器正确解析资源
 */
QByteArray HttpServer::contentType(const QString &filePath)
{
    if (filePath.endsWith(".html")) return "text/html; charset=utf-8";
    if (filePath.endsWith(".css"))  return "text/css";
    if (filePath.endsWith(".js"))   return "application/javascript";

    // 默认二进制流
    return "application/octet-stream";
}

main.cpp

cpp 复制代码
#include <QCoreApplication>
#include "HttpServer.h"

/**
 * @brief 程序入口函数
 *
 * 本程序为一个基于 Qt 的控制台应用,
 * 用于启动一个轻量级 HTTP Server。
 *
 * 功能说明:
 * 1️⃣ 初始化 Qt 核心事件循环
 * 2️⃣ 创建 HttpServer 实例
 * 3️⃣ 监听指定端口(8085)
 * 4️⃣ 进入 Qt 事件循环,持续处理网络事件
 *
 * 适用场景:
 * - 本地配置 Web 服务
 * - Qt + HTML 内嵌管理页面
 * - 轻量级 BS 架构 Demo
 */
int main(int argc, char *argv[])
{
    /**
     * @brief Qt 核心应用对象
     *
     * QCoreApplication 提供:
     * - 事件循环
     * - 信号槽调度
     * - 网络事件分发
     *
     * 对于无界面的服务器程序,
     * 使用 QCoreApplication 即可
     */
    QCoreApplication a(argc, argv);

    /**
     * @brief 创建 HTTP Server 对象
     *
     * server 生命周期受 main 函数控制,
     * 程序退出时自动释放
     */
    HttpServer server;

    /**
     * @brief 启动服务器监听
     *
     * 监听端口:8085
     * - 可通过浏览器访问:http://localhost:8085
     *
     * 若端口被占用或权限不足,
     * 启动将失败
     */
    if (!server.start(8085)) {
        qDebug() << "Server start failed";
        return -1;
    }

    /**
     * @brief 进入 Qt 事件循环
     *
     * 程序将在此处阻塞运行,
     * 持续响应 HTTP 请求,
     * 直到调用 quit() 或程序退出
     */
    return a.exec();
}

pro文件

cpp 复制代码
QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets


CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has 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 it uses 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 \
    HttpServer.cpp

HEADERS += \
    HttpServer.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

---## 四、前端 HTML + JavaScript 实现详解

在本实战中,前端页面的职责非常明确

✅ 负责数据采集与展示

❌ 不参与任何认证逻辑判断

所有认证结果,完全以服务端返回为准


4.1 页面结构说明

前端页面主要由以下几部分组成:

模块 作用
粒子 Canvas 提供动态背景效果
登录面板 输入权限密码、学生信息
Tip 提示区 展示前端校验提示
认证结果区 展示后端返回的真实结果

4.2 认证结果输入框设计(只读)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">

<!--
    页面标题
    浏览器标签页显示名称
-->
<title>学生权限认证系统</title>

<style>
/* =========================================================
 * 全局基础样式
 * ========================================================= */
html, body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;            /* 禁止滚动条 */
    background: #000;            /* 黑色背景 */
    font-family: "Microsoft YaHei", Arial;
}

/* =========================================================
 * 粒子背景 Canvas
 * =========================================================
 * - fixed 固定全屏
 * - z-index:0 作为背景层
 */
canvas {
    position: fixed;
    left: 0;
    top: 0;
    display: block;
    z-index: 0;
}

/* =========================================================
 * 登录面板容器
 * ========================================================= */
.panel {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 380px;
    padding: 20px 26px 24px;
    transform: translate(-50%, -50%);  /* 水平垂直居中 */
    background: rgba(20,20,20,0.85);   /* 半透明黑色 */
    border-radius: 10px;
    box-shadow: 0 0 30px rgba(0,200,255,0.25);
    color: #fff;
    z-index: 1;                        /* 位于粒子之上 */
}

/* 面板标题 */
.panel h2 {
    margin: 0 0 10px;
    text-align: center;
    color: #6cf;
}

/* 滚动提示信息 */
.marquee {
    font-size: 13px;
    color: #9cf;
    margin-bottom: 12px;
}

/* =========================================================
 * 表单元素
 * ========================================================= */
label {
    display: block;
    margin-top: 10px;
    font-size: 13px;
    color: #ccc;
}

/* 输入框 / 下拉框统一样式 */
input, select {
    width: 100%;
    height: 32px;
    margin-top: 4px;
    padding: 0 8px;
    box-sizing: border-box;
    background: #111;
    border: 1px solid #333;
    border-radius: 4px;
    color: #fff;
}

/* 禁用状态样式 */
input[disabled], select[disabled] {
    background: #0a0a0a;
    color: #777;
}

/* =========================================================
 * 提示信息
 * =========================================================
 * - 不可交互
 * - 不可选中
 */
.tip {
    height: 16px;
    font-size: 12px;
    color: #ff6666;
    pointer-events: none;
    user-select: none;
}

/* =========================================================
 * 认证结果样式
 * ========================================================= */
.result-ok {
    border-color: #00ffcc;
    color: #00ffcc;
    box-shadow: 0 0 8px rgba(0,255,204,0.8);
}

.result-fail {
    border-color: #ff4444;
    color: #ff4444;
    box-shadow: 0 0 8px rgba(255,68,68,0.8);
}

/* =========================================================
 * 登录按钮
 * ========================================================= */
button {
    width: 100%;
    height: 36px;
    margin-top: 14px;
    border: none;
    border-radius: 18px;
    background: linear-gradient(to right, #00c6ff, #0072ff);
    color: #fff;
    font-size: 14px;
    cursor: pointer;
}
</style>
</head>

<body>

<!-- =========================================================
     粒子背景 Canvas
     ========================================================= -->
<canvas id="canvas"></canvas>

<!-- =========================================================
     登录面板
     ========================================================= -->
<div class="panel">
    <h2>学生权限认证</h2>

    <!-- 滚动标语 -->
    <div class="marquee">
        <marquee>净化网络环境 · 人人有责</marquee>
    </div>

    <!-- 权限密码 -->
    <label>权限密码</label>
    <input id="pwd" type="password"
           placeholder="请输入权限密码"
           oninput="checkPwd()">
    <div id="pwdTip" class="tip"></div>

    <!-- 姓名 -->
    <label>姓名</label>
    <input id="name" disabled>

    <!-- 年级 -->
    <label>所属年级</label>
    <select id="grade" disabled onchange="clearTip('gradeTip')">
        <option value="">请选择</option>
        <option>一年级</option>
        <option>二年级</option>
        <option>三年级</option>
    </select>
    <div id="gradeTip" class="tip"></div>

    <!-- 班级 -->
    <label>所属班级</label>
    <select id="cls" disabled onchange="clearTip('clsTip')">
        <option value="">请选择</option>
        <option>一班</option>
        <option>二班</option>
        <option>三班</option>
    </select>
    <div id="clsTip" class="tip"></div>

    <!-- 位置 -->
    <label>位置</label>
    <select id="loc" disabled>
        <option value="">请选择</option>
        <option>教学楼A</option>
        <option>教学楼B</option>
        <option>实验楼</option>
    </select>

    <!-- 学号 -->
    <label>学号</label>
    <input id="sid" disabled
           placeholder="10 位或 20 位数字"
           oninput="clearTip('sidTip')">
    <div id="sidTip" class="tip"></div>

    <!-- 认证结果(只读) -->
    <label>认证结果</label>
    <input id="result" readonly tabindex="-1">

    <!-- 登录按钮 -->
    <button onclick="login()">登 录</button>
</div>

<script>
/* =========================================================
 * 粒子背景动画
 * =========================================================
 * - 使用 Canvas 绘制
 * - requestAnimationFrame 刷新
 */
(function () {
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    /* 自适应窗口大小 */
    function resize() {
        canvas.width = document.documentElement.clientWidth;
        canvas.height = document.documentElement.clientHeight;
    }
    resize();
    window.onresize = resize;

    var DOT_COUNT = 160;   // 粒子数量
    var LINK_DIST = 90;    // 连线最大距离
    var dots = [];

    /* 粒子对象 */
    function Dot() {
        this.x = Math.random() * canvas.width;
        this.y = Math.random() * canvas.height;
        this.vx = (Math.random() - 0.5) * 0.6;
        this.vy = (Math.random() - 0.5) * 0.6;
        this.r  = Math.random() * 0.6 + 0.4;
    }

    Dot.prototype.move = function () {
        this.x += this.vx;
        this.y += this.vy;
        if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
        if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
    };

    Dot.prototype.draw = function () {
        ctx.beginPath();
        ctx.fillStyle = "rgba(255,255,255,0.8)";
        ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
        ctx.fill();
    };

    /* 初始化粒子 */
    for (var i = 0; i < DOT_COUNT; i++) dots.push(new Dot());

    /* 动画主循环 */
    function draw() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        for (var i = 0; i < dots.length; i++) {
            var d1 = dots[i];
            d1.move();

            /* 粒子连线 */
            for (var j = i + 1; j < dots.length; j++) {
                var d2 = dots[j];
                var dx = d1.x - d2.x;
                var dy = d1.y - d2.y;
                var dist = Math.sqrt(dx*dx + dy*dy);

                if (dist < LINK_DIST) {
                    ctx.strokeStyle =
                        "rgba(0,200,255," + (1 - dist / LINK_DIST) + ")";
                    ctx.lineWidth = 0.3;
                    ctx.beginPath();
                    ctx.moveTo(d1.x, d1.y);
                    ctx.lineTo(d2.x, d2.y);
                    ctx.stroke();
                }
            }
            d1.draw();
        }
        requestAnimationFrame(draw);
    }
    draw();
})();

/* =========================================================
 * 登录业务逻辑(与 Qt 后端强绑定)
 * ========================================================= */

/* 前端权限密码(仅用于交互控制) */
var AUTH_PWD = "admin123";

/* 控制表单是否可编辑 */
function setEditable(enable) {
    var ids = ["name","grade","cls","loc","sid"];
    for (var i = 0; i < ids.length; i++) {
        document.getElementById(ids[i]).disabled = !enable;
    }
}

/* 清空提示文本 */
function clearTip(id) {
    document.getElementById(id).innerText = "";
}

/* 权限密码输入检测 */
function checkPwd() {
    var pwd = document.getElementById("pwd").value;
    var tip = document.getElementById("pwdTip");
    tip.innerText = "";

    if (!pwd) {
        setEditable(false);
        tip.innerText = "请输入权限密码";
        return;
    }

    if (pwd === AUTH_PWD) {
        setEditable(true);
    } else {
        setEditable(false);
        tip.innerText = "权限密码错误";
    }
}

/* 表单合法性校验 */
function validateForm() {
    var ok = true;

    if (!document.getElementById("grade").value) {
        document.getElementById("gradeTip").innerText = "请选择所属年级";
        ok = false;
    }

    if (!document.getElementById("cls").value) {
        document.getElementById("clsTip").innerText = "请选择所属班级";
        ok = false;
    }

    var sid = document.getElementById("sid").value;
    if (!sid) {
        document.getElementById("sidTip").innerText = "学号不能为空";
        ok = false;
    } else if (!/^\d{10}$|^\d{20}$/.test(sid)) {
        document.getElementById("sidTip").innerText =
            "学号必须是 10 位或 20 位数字";
        ok = false;
    }
    return ok;
}

/* 登录提交 */
function login() {
    /* 权限密码不正确直接拦截 */
    if (document.getElementById("pwd").value !== AUTH_PWD) {
        document.getElementById("pwdTip").innerText =
            "权限密码错误,禁止登录";
        return;
    }

    /* 表单校验 */
    if (!validateForm()) return;

    /* AJAX 请求 Qt 后端 */
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/login", true);
    xhr.setRequestHeader(
        "Content-Type",
        "application/x-www-form-urlencoded");

    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var obj = JSON.parse(xhr.responseText);
            var result = document.getElementById("result");

            /* 认证结果完全由 Qt 后端返回 */
            result.value = obj.msg || "";

            /* 根据 code 高亮显示 */
            result.className =
                obj.code === 1
                ? "result-ok"
                : "result-fail";
        }
    };

    /* 与 Qt 后端字段完全一致 */
    var params =
        "password=" + encodeURIComponent(document.getElementById("pwd").value) +
        "&name="     + encodeURIComponent(document.getElementById("name").value) +
        "&grade="    + encodeURIComponent(document.getElementById("grade").value) +
        "&class="    + encodeURIComponent(document.getElementById("cls").value) +
        "&location=" + encodeURIComponent(document.getElementById("loc").value) +
        "&sid="      + encodeURIComponent(document.getElementById("sid").value);

    xhr.send(params);
}
</script>

</body>
</html>
相关推荐
阿华hhh2 小时前
数据结构(树)
linux·c语言·开发语言·数据结构
雪域迷影2 小时前
Windows11中VS2026使用C++ 现代化json库nlohmann的3种方式
开发语言·c++·json
步步为营DotNet2 小时前
深度剖析.NET中HttpClient的请求重试机制:可靠性提升与实践优化
开发语言·php·.net
zephyr052 小时前
C++ STL string 用法详解与示例
开发语言·c++
郝学胜-神的一滴2 小时前
Linux线程的共享资源与非共享资源详解
linux·服务器·开发语言·c++·程序人生·设计模式
默凉2 小时前
c++使用http发送图像
开发语言·c++·http
木千2 小时前
Qt中关于QLineEdit控件的editingFinished信号执行两次的处理方式
开发语言·qt
山风wind2 小时前
设计模式-单例模式详解
开发语言·javascript·ecmascript
未来之窗软件服务2 小时前
幽冥大陆(五十三)人工智能开发语言选型指南——东方仙盟筑基期
开发语言·人工智能·仙盟创梦ide·东方仙盟