CAU抢课脚本

前言

选课系统改了之后可以宣布进入大脚本时代了,随机发放你让我抢牛魔。

本篇就是介绍一下CAU选课的脚本,代码和使用。 用cpp实现的。 不保证没有bug,如有问题请指出,因为本人也是第一次实现用cpp搞的脚本。(Windows平台)

酌情使用脚本,有可能会被封IP甚至封号。


简单介绍一下是干什么的,针对cau的选课,只能用于本学期计划选课和公选课选课-方案内的抢课脚本,也就是置入的必修课、专业选修课、体育课、英语课、通识课、美育课的抢课脚本,当然,抢其他的类别里面的原理差别不大,想实现可以自己搞,问问ai,简单学一下api和原理就可以弄

一、源文件以及用法

核心就用到了两个文件一个是源文件gc.cpp、一个是配置文件config.txt

至于环境Windows下只需要有g++编译器即可,HTTP请求用的是Windows自带的API,不需要安装。

注意: ./gc.exe --list只能用于本学期计划选课部分,因为公选课实在是太多了,就没弄,需要手动配置,下面有配置方案

先配置好config.txt才能正常编译运行抢课! 先看下面关于config.txt的描述,先配置Cookie,当然gc.cpp是什么不重要,拷贝下来即可

gc.cpp

cpp 复制代码
/**
 * CAU抢课脚本
 *
 * 编译: g++ -o gc.exe gc.cpp -lwinhttp -std=c++20 -O2
 * 用法:
 *   ./gc.exe --list 列出可选课程,选后自动写入config.txt,先执行这行再执行下一行 只能用于本学期计划选课! 公选课请手动配置config.txt
 *   ./gc.exe        直接抢课(参数从config.txt读取)
 */

#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
#include <mutex>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <algorithm>

#pragma comment(lib, "winhttp.lib")

std::atomic<bool> g_running{true};
std::atomic<int> g_totalAttempts{0};
std::atomic<int> g_failCount{0};
std::mutex g_printMutex;
auto g_startTime = std::chrono::steady_clock::now();

//console 参数
namespace console {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    enum Color {
        RED   = FOREGROUND_RED | FOREGROUND_INTENSITY,
        GREEN = FOREGROUND_GREEN | FOREGROUND_INTENSITY,
        YELLOW= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY,
        CYAN  = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY,
        WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
        GRAY  = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
    };

    void setColor(Color c) { SetConsoleTextAttribute(hConsole, static_cast<WORD>(c)); }
    void resetColor() { SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); }

    void init() {
        SetConsoleOutputCP(CP_UTF8);
        SetConsoleCtrlHandler([](DWORD) -> BOOL { g_running = false; return TRUE; }, TRUE);
    }

    void print(const std::string& msg, Color c = WHITE) {
        std::lock_guard<std::mutex> lock(g_printMutex);
        setColor(c);
        std::cout << msg;
        resetColor();
    }

    std::string timestamp() {
        auto now = std::chrono::system_clock::now();
        auto t = std::chrono::system_clock::to_time_t(now);
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
        std::tm tm;
        localtime_s(&tm, &t);
        char buf[32];
        snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03lld", tm.tm_hour, tm.tm_min, tm.tm_sec, ms.count());
        return buf;
    }

    void info(const std::string& msg)    { print("[" + timestamp() + "] " + msg + "\n", CYAN); }
    void success(const std::string& msg) { print("[" + timestamp() + "] [OK] " + msg + "\n", GREEN); }
    void error(const std::string& msg)   { print("[" + timestamp() + "] [FAIL] " + msg + "\n", RED); }
    void warn(const std::string& msg)    { print("[" + timestamp() + "] [WARN] " + msg + "\n", YELLOW); }
    void log(const std::string& msg)     { print("[" + timestamp() + "] " + msg + "\n", GRAY); }
}

//字符串处理函数
namespace strutil {
    std::string trim(const std::string& s) {
        auto start = s.find_first_not_of(" \t\r\n");
        if (start == std::string::npos) return "";
        return s.substr(start, s.find_last_not_of(" \t\r\n") - start + 1);
    }

    std::wstring toWide(const std::string& s) {
        if (s.empty()) return L"";
        int len = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0);
        std::wstring result(len, L'\0');
        MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &result[0], len);
        result.resize(len - 1);
        return result;
    }

    bool contains(const std::string& haystack, const std::string& needle) {
        return haystack.find(needle) != std::string::npos;
    }

    std::string jsonValue(const std::string& json, const std::string& key) {
        std::string search = "\"" + key + "\"";
        auto pos = json.find(search);
        if (pos == std::string::npos) return "";
        pos = json.find(':', pos + search.size());
        if (pos == std::string::npos) return "";
        pos++;
        while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t')) pos++;
        if (pos >= json.size()) return "";
        if (json[pos] == '"') {
            pos++;
            auto end = json.find('"', pos);
            return end == std::string::npos ? "" : json.substr(pos, end - pos);
        }
        auto end = json.find_first_of(",}\n\r", pos);
        return end == std::string::npos ? json.substr(pos) : json.substr(pos, end - pos);
    }
}

// config 
struct Config {
    std::string kcid;
    std::string jx0404id;

    std::string jsessionid1;
    std::string serverid = "125";
    std::string jsessionid2;

    int threadCount = 8;
    int retryIntervalMs = 100;
    int mode = 0; // 0 = 计划选课 1 = 公选课

    std::string cookieString() const {
        std::string c = "JSESSIONID=" + jsessionid1;
        if (!serverid.empty())  c += "; SERVERID=" + serverid;
        if (!jsessionid2.empty()) c += "; JSESSIONID=" + jsessionid2;
        return c;
    }
    //存入
    bool loadFromFile(const std::string& filename) {
        std::ifstream file(filename);
        if (!file.is_open()) return false;

        std::string line;
        while (std::getline(file, line)) {
            auto cp = line.find('#');
            if (cp != std::string::npos) line = line.substr(0, cp);

            auto eq = line.find('=');
            if (eq == std::string::npos) continue;

            std::string key = strutil::trim(line.substr(0, eq));
            std::string val = strutil::trim(line.substr(eq + 1));

            if (key == "kcid")                kcid = val;
            else if (key == "jx0404id")       jx0404id = val;
            else if (key == "jsessionid1")    jsessionid1 = val;
            else if (key == "jsessionid2")    jsessionid2 = val;
            else if (key == "serverid")       serverid = val;
            else if (key == "thread_count")   threadCount = std::stoi(val);
            else if (key == "retry_interval_ms") retryIntervalMs = std::stoi(val);
            else if (key == "mode")           mode = std::stoi(val);
        }
        return true;
    }

    bool validate() const {
        if (kcid.empty())        { console::error("config.txt 中缺少 kcid"); return false; }
        if (jx0404id.empty())    { console::error("config.txt 中缺少 jx0404id"); return false; }
        if (jsessionid1.empty()) { console::error("config.txt 中缺少 jsessionid1"); return false; }
        return true;
    }

    void print() const {
        console::info("模式: " + std::string(mode == 0 ? "计划选课" : "公选课"));
        console::info("课程ID: " + kcid + "  教学班ID: " + jx0404id);
        console::info("线程数: " + std::to_string(threadCount)
                      + "  间隔: " + std::to_string(retryIntervalMs) + "ms");
        console::info("Cookie: " + jsessionid1.substr(0, 16) + "...");
    }
};

//HTTP 请求
class HttpClient {
public:
    struct Response {
        int statusCode = 0;
        std::string body;
    };

    HttpClient() {
        m_session = WinHttpOpen(
            L"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
            WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    }
    ~HttpClient() { if (m_session) WinHttpCloseHandle(m_session); }

    Response get(const std::string& host, int port, const std::string& path,
                 const std::string& cookie, const std::string& referer) {
        return doRequest(L"GET", host, port, path, "", cookie, referer);
    }

    Response post(const std::string& host, int port, const std::string& path,
                  const std::string& postData,
                  const std::string& cookie, const std::string& referer) {
        return doRequest(L"POST", host, port, path, postData, cookie, referer);
    }

private:
    HINTERNET m_session = nullptr;

    Response doRequest(const std::wstring& method,
                       const std::string& host, int port, const std::string& path,
                       const std::string& postData,
                       const std::string& cookie, const std::string& referer) {
        Response resp;
        std::wstring wHost = strutil::toWide(host);
        HINTERNET hConnect = WinHttpConnect(m_session, wHost.c_str(), port, 0);
        if (!hConnect) { resp.statusCode = -1; return resp; }

        DWORD flags = (port == 443) ? WINHTTP_FLAG_SECURE : 0;
        HINTERNET hRequest = WinHttpOpenRequest(hConnect, method.c_str(),
            strutil::toWide(path).c_str(), nullptr,
            WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
        if (!hRequest) { WinHttpCloseHandle(hConnect); resp.statusCode = -1; return resp; }

        DWORD timeout = 10000;
        WinHttpSetOption(hRequest, WINHTTP_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
        WinHttpSetOption(hRequest, WINHTTP_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
        WinHttpSetOption(hRequest, WINHTTP_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));

        DWORD disableRedirect = WINHTTP_DISABLE_REDIRECTS;
        WinHttpSetOption(hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &disableRedirect, sizeof(disableRedirect));

        std::string headers = "Accept: */*\r\n"
                              "X-Requested-With: XMLHttpRequest\r\n"
                              "Referer: " + referer + "\r\n"
                              "Cache-Control: no-cache\r\n"
                              "Pragma: no-cache\r\n";
        if (!postData.empty())
            headers += "Content-Type: application/x-www-form-urlencoded\r\n";
        std::wstring wHeaders = strutil::toWide(headers);
        WinHttpAddRequestHeaders(hRequest, wHeaders.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);

        if (!cookie.empty()) {
            std::wstring wCookie = strutil::toWide("Cookie: " + cookie + "\r\n");
            WinHttpAddRequestHeaders(hRequest, wCookie.c_str(), -1,
                WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
        }

        LPVOID pData = nullptr;
        DWORD dataLen = 0;
        std::string pd = postData;
        if (!pd.empty()) { pData = (LPVOID)pd.data(); dataLen = (DWORD)pd.size(); }

        if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
                                pData, dataLen, dataLen, 0)) {
            resp.statusCode = -1;
            resp.body = "SendRequest failed: " + std::to_string(GetLastError());
            WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect);
            return resp;
        }

        if (!WinHttpReceiveResponse(hRequest, nullptr)) {
            resp.statusCode = -1;
            resp.body = "ReceiveResponse failed";
            WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect);
            return resp;
        }

        DWORD sc = 0, sz = sizeof(sc);
        WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
            WINHTTP_HEADER_NAME_BY_INDEX, &sc, &sz, WINHTTP_NO_HEADER_INDEX);
        resp.statusCode = static_cast<int>(sc);

        DWORD bytesRead = 0;
        char buffer[4096];
        while (WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0)
            resp.body.append(buffer, bytesRead);

        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        return resp;
    }
};


struct SelectResult {
    bool isSuccess = false;
    bool isFull = false;
    bool isConflict = false;
    bool isSessionExpired = false;
    std::string message;
};

SelectResult trySelectCourse(HttpClient& client, const Config& cfg) {
    SelectResult r;
    std::string oper = cfg.mode == 1 ? "ggxxkxkOper" : "bxqjhxkOper";
    std::string referer = cfg.mode == 1
        ? "https://newjw.cau.edu.cn/jsxsd/xsxkkc/comeInGgxxkxk"
        : "https://newjw.cau.edu.cn/jsxsd/xsxkkc/comeInBxqjhxk";
    std::string path = "/jsxsd/xsxkkc/" + oper + "?kcid=" + cfg.kcid
        + "&cfbs=null&jx0404id=" + cfg.jx0404id + "&xkzy=&trjf=";

    auto resp = client.get("newjw.cau.edu.cn", 443, path,
        cfg.cookieString(), referer);

    if (resp.statusCode <= 0) { r.message = "网络错误: " + resp.body; return r; }
    if (resp.statusCode == 302 || resp.statusCode == 401) {
        r.isSessionExpired = true; r.message = "Session过期"; return r;
    }

    if (strutil::contains(resp.body, "\"success\":true") ||
        strutil::contains(resp.body, "\"success\": true") ||
        strutil::contains(resp.body, "选课成功") ||
        strutil::contains(resp.body, "操作成功")) {
        r.isSuccess = true; r.message = "选课成功!"; return r;
    }

    r.message = strutil::jsonValue(resp.body, "msg");
    if (r.message.empty()) r.message = strutil::jsonValue(resp.body, "message");
    if (r.message.empty()) r.message = strutil::jsonValue(resp.body, "czMsg");
    if (r.message.empty()) r.message = resp.body.substr(0, std::min<size_t>(200, resp.body.size()));

    if (strutil::contains(r.message, "满") || strutil::contains(r.message, "人数") ||
        strutil::contains(r.message, "容量") || strutil::contains(r.message, "余量"))
        r.isFull = true;
    if (strutil::contains(r.message, "冲突") || strutil::contains(r.message, "时间"))
        r.isConflict = true;
    if (strutil::contains(r.message, "登录") || strutil::contains(r.message, "Session"))
        r.isSessionExpired = true;

    return r;
}

void grabWorker(int threadId, const Config& cfg) {
    HttpClient client;
    int localAttempts = 0;

    while (g_running) {
        localAttempts++;
        g_totalAttempts++;

        auto result = trySelectCourse(client, cfg);

        if (result.isSuccess) {
            console::success("[线程#" + std::to_string(threadId) + "]  选课成功!!!");
            console::success("服务器返回: " + result.message);
            g_running = false;
            return;
        }

        if (result.isSessionExpired) {
            console::error("[线程#" + std::to_string(threadId) + "] Session过期,请重新从浏览器复制Cookie!");
            g_running = false;
            return;
        }

        if (result.isConflict) {
            console::error("[线程#" + std::to_string(threadId) + "] 课程冲突: " + result.message);
            g_running = false;
            return;
        }

        g_failCount++;

        if (localAttempts % 20 == 0) {
            auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::steady_clock::now() - g_startTime).count();
            double rate = elapsed > 0 ? (g_totalAttempts * 1000.0 / elapsed) : 0;

            std::ostringstream oss;
            oss << "[#" << threadId << "] " << localAttempts
                << "次 | 总计:" << g_totalAttempts
                << " | " << std::fixed << std::setprecision(0) << rate << "req/s"
                << " | " << result.message;
            console::log(oss.str());
        }

        if (cfg.retryIntervalMs > 0 && g_running)
            std::this_thread::sleep_for(std::chrono::milliseconds(cfg.retryIntervalMs));
    }
}

//课程列表
struct CourseInfo {
    std::string kcid;      
    std::string jx0404id;
    std::string name;
    std::string teacher;
    std::string time;
    std::string remain;
    std::string dsfme;     // 待释放容量
};

std::string jsonStrVal(const std::string& json, const std::string& key, size_t from = 0) {
    std::string k = "\"" + key + "\":\"";
    auto p = json.find(k, from);
    if (p == std::string::npos) { k = "\"" + key + "\": \""; p = json.find(k, from); }
    if (p == std::string::npos) return "";
    p += k.size();
    auto e = json.find('"', p);
    return e == std::string::npos ? "" : json.substr(p, e - p);
}

std::string jsonStrValRev(const std::string& json, const std::string& key, size_t before) {
    std::string k = "\"" + key + "\":\"";
    auto p = json.rfind(k, before);
    if (p == std::string::npos) { k = "\"" + key + "\": \""; p = json.rfind(k, before); }
    if (p == std::string::npos) return "";
    p += k.size();
    auto e = json.find('"', p);
    return e == std::string::npos ? "" : json.substr(p, e - p);
}

// 获取json
std::string jsonValRev(const std::string& json, const std::string& key, size_t before) {
    std::string k = "\"" + key + "\":";
    auto p = json.rfind(k, before);
    if (p == std::string::npos) { k = "\"" + key + "\": "; p = json.rfind(k, before); }
    if (p == std::string::npos) return "";
    p += k.size();
    while (p < json.size() && json[p] == ' ') p++;
    if (p >= json.size()) return "";
    if (json[p] == '"') { p++; auto e = json.find('"', p); return e == std::string::npos ? "" : json.substr(p, e - p); }
    auto e = json.find_first_of(",}\n\r", p);
    return e == std::string::npos ? json.substr(p) : json.substr(p, e - p);
}

void listCourses(const Config& cfg) {
    console::info("正在从教务系统获取可选课程列表...");

    HttpClient client;
    // 访问选课
    auto page = client.get("newjw.cau.edu.cn", 443,
        "/jsxsd/xsxkkc/comeInBxqjhxk",
        cfg.cookieString(),
        "https://newjw.cau.edu.cn/jsxsd/framework/xsMainV_new_zgnydx.jsp?t1=1");

    if (page.statusCode != 200) {
        console::error("无法访问选课页面 HTTP " + std::to_string(page.statusCode));
        return;
    }

    // AJAX
    std::string postData =
        "sEcho=1&iColumns=18&iDisplayStart=0&iDisplayLength=500"
        "&mDataProp_0=jx0404id&mDataProp_1=kch&mDataProp_2=kcmc"
        "&mDataProp_3=jhkcsx&mDataProp_4=kth&mDataProp_5=skbjs"
        "&mDataProp_6=xf&mDataProp_7=skls&mDataProp_8=sksj"
        "&mDataProp_9=skdd&mDataProp_10=xqmc&mDataProp_11=xkrs"
        "&mDataProp_12=xxrs&mDataProp_13=syrs";

    auto resp = client.post("newjw.cau.edu.cn", 443,
        "/jsxsd/xsxkkc/xsxkBxqjhxk?kcxx=&skls=&skxq=&skjc=&sfym=&sfct=&sfxx=&skfs=",
        postData,
        cfg.cookieString(),
        "https://newjw.cau.edu.cn/jsxsd/xsxkkc/comeInBxqjhxk");

    if (resp.statusCode != 200) {
        console::error("获取课程列表失败 HTTP " + std::to_string(resp.statusCode));
        return;
    }

    // 解析JSON
    std::vector<CourseInfo> courses;
    size_t pos = 0;
    while (true) {
        pos = resp.body.find("\"jx02id\"", pos);
        if (pos == std::string::npos) break;
        std::string jx02id = jsonStrVal(resp.body, "jx02id", pos);
        if (jx02id.empty()) { pos += 12; continue; }

        CourseInfo ci;
        ci.kcid = jx02id;
        ci.jx0404id = jsonStrValRev(resp.body, "jx0404id", pos);
        if (ci.jx0404id.empty()) ci.jx0404id = jsonStrVal(resp.body, "jx0404id", pos);
        ci.name = jsonStrValRev(resp.body, "kcmc", pos);
        if (ci.name.empty()) ci.name = jsonStrVal(resp.body, "kcmc", pos);
        ci.teacher = jsonStrValRev(resp.body, "skls", pos);
        if (ci.teacher.empty()) ci.teacher = jsonStrVal(resp.body, "skls", pos);
        ci.time = jsonStrValRev(resp.body, "sksj", pos);
        if (ci.time.empty()) ci.time = jsonStrVal(resp.body, "sksj", pos);
        ci.remain = jsonStrValRev(resp.body, "syrs", pos);
        if (ci.remain.empty()) ci.remain = jsonStrVal(resp.body, "syrs", pos);
        ci.dsfme = jsonValRev(resp.body, "dsfme", pos);

        if (!ci.kcid.empty() && !ci.jx0404id.empty()) {
            bool dup = false;
            for (auto& c : courses)
                if (c.kcid == ci.kcid && c.jx0404id == ci.jx0404id) { dup = true; break; }
            if (!dup) courses.push_back(ci);
        }
        pos += 2;
        if (courses.size() >= 500) break;
    }

    if (courses.empty()) {
        console::warn("未能解析课程列表");
        console::info("已将API返回保存到 page_dump.txt");
        std::ofstream dump("page_dump.txt");
        dump << resp.body;
        dump.close();
        return;
    }

    console::info("找到 " + std::to_string(courses.size()) + " 门可选课程:\n");

    for (size_t i = 0; i < courses.size(); i++) {
        auto& c = courses[i];
        std::ostringstream oss;
        oss << std::setw(2) << (i + 1) << ". " << c.name;
        if (!c.teacher.empty()) oss << " | " << c.teacher;
        if (!c.time.empty()) oss << " | " << c.time;
        if (!c.remain.empty()) oss << " | 剩余:" << c.remain;
        oss << " | 待释放:" << (c.dsfme.empty() ? "0" : c.dsfme);
        console::print(oss.str() + "\n");
    }

    console::print("\n输入序号选择要抢的课 (1-" + std::to_string(courses.size()) + "): ");
    int choice;
    std::cin >> choice;

    if (choice < 1 || choice > (int)courses.size()) {
        console::error("无效选择");
        return;
    }

    auto& picked = courses[choice - 1];
    console::success("已选择: " + picked.name);
    console::info("  kcid:     " + picked.kcid);
    console::info("  jx0404id: " + picked.jx0404id);

    // 写入config.txt
    std::ifstream fin("config.txt");
    std::string content((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());
    fin.close();

    auto setval = [&](const std::string& key, const std::string& val) {
        size_t p = content.find(key + "=");
        if (p == std::string::npos) return;
        size_t e = content.find('\n', p);
        if (e == std::string::npos) e = content.size();
        content.replace(p, e - p, key + "=" + val);
    };
    setval("kcid", picked.kcid);
    setval("jx0404id", picked.jx0404id);

    std::ofstream fout("config.txt");
    fout << content;
    fout.close();

    console::success("已更新 config.txt! 直接运行 ./gc.exe 即可抢课");
}


int main(int argc, char* argv[]) {
    console::init();

    console::print("\n");
    console::print("  CAU抢课脚本\n", console::CYAN);
    console::print("  使用脚本有可能会被封IP甚至封号处理,慎重使用!\n",console::RED); 

    Config cfg;
    if (!cfg.loadFromFile("config.txt")) {
        console::error("无法读取 config.txt");
        return 1;
    }

    // --list 
    if (argc >= 2 && std::string(argv[1]) == "--list") {
        if (cfg.jsessionid1.empty()) {
            console::error("请先在 config.txt 中填写有效的 jsessionid1 和 jsessionid2");
            return 1;
        }
        listCourses(cfg);
        return 0;
    }

    if (!cfg.validate()) {
        console::error("config.txt 配置不完整");
        console::error("用 ./gc.exe --list 从教务系统选择课程");
        return 1;
    }

    cfg.print();
    console::print("\n");

    console::info(" 开始抢课! 按 Ctrl+C 停止\n");

    g_startTime = std::chrono::steady_clock::now();

    std::vector<std::thread> threads;
    for (int i = 0; i < cfg.threadCount; i++)
        threads.emplace_back(grabWorker, i + 1, std::cref(cfg));
    for (auto& t : threads)
        if (t.joinable()) t.join();

    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::steady_clock::now() - g_startTime).count();

    console::print("\n══════════════════════════════\n", console::CYAN);
    if (g_failCount > 0 && g_totalAttempts == g_failCount)
        console::error("所有请求均失败,请检查 config.txt 中的 Cookie 是否有效");
    else if (g_running)
        console::warn("被用户中断");
    else
        console::success("抢课成功! ");

    double rate = elapsed > 0 ? (g_totalAttempts * 1000.0 / elapsed) : 0;
    console::info("总请求: " + std::to_string(g_totalAttempts)
                  + "  耗时: " + std::to_string(elapsed / 1000.0) + "s"
                  + "  速率: " + std::to_string(static_cast<int>(rate)) + " req/s");
    console::print("══════════════════════════════\n", console::CYAN);

    return 0;
}

config.txt

txt 复制代码
# 抢课目标 
kcid=01B2B140F47E44F7B7E891A4FC2AC898
jx0404id=202620271000695

# Cookie
jsessionid1=xxxxx
serverid=xxxx
jsessionid2=xxxxx

# 抢课模式: 0=计划选课 1=公选课
mode=1

# 抢课参数
# 线程数 和 抢课的时间间隔
# 线程数越大、抢课的时间间隔越小,访问次数越多,但是不要太高,线程数不要超过15,间隔不要低于30ms,下面这个默认基本够用,也可以上升到10和50
thread_count=8
retry_interval_ms=100

配置config中的Cookie参数

最重要的参数是Cookie里面的参数,步骤是进入选课页面 + F12 + 点击应用 + 点击Cookie + 点击Cookie下面的https:xxxxxxx, 即可看到这三个参数,填入这三个参数即可。

拿到这三个值填入后不要退选课页面。配置Cookie参数必须做!然后可以看下面实现哪一种

本学期计划选课方式

选mode = 0,可以使用./gc.exe --list看到你本学期的可以选的所有课,包含冲冲突的,已满的,限选的。输入对应的号然后会自动配置config里的参数,然后直接./gc.exe 即可!

显示出来大概率像下面这样:

当然,你也可以手动配置选课的信息,请看下面公选课方式

公选课方式

首先选mode = 1,不可以使用./gc.exe --list (当然你使用了显示的也是本学期计划选课的东西) .需要配置的是kcid和jx0404id这两个参数,方法:

1.在浏览器选课页面中搜出你要选的课程,按F12

2.点击网络、点击Fetch/XHR,此时大概率是这个页面: 什么也没有显示出来

3.点击你要选的课程的选课,如下图,就和正常选课的流程一样,无论结果如何不重要(当然你得有结果,无论是失败了还是成功了,别浏览器弹出来问你确不确定你点取消了) ,回到刚才的F12出来的页面

4.此时名称下面大概率出现了三个东西,点击最长的那个,名称大概为ggxxkxkOper? kcid=01131565&cfbs=null&jx0404id=202620271001357&xkzy=&trjf= ,此时我们就看到了对应的参数,当然点击右边的载荷选项更方便看到

将kcid和jx0404id填入即可,另外也可以这样看到自己的JESSIONID1和2在标头中的Cookie可以看到

填入所有信息之后,编译 + ./gc.exe即可

编译和用法

和gc.cpp里面写的是一样的

cpp 复制代码
 * 编译: g++ -o gc.exe gc.cpp -lwinhttp -std=c++20 -O2
 * 用法:
 *   ./gc.exe --list 列出可选课程,选后自动写入config.txt,先执行这行再执行下一行 只能用于本学期计划选课! 公选课请手动配置config.txt
 *   ./gc.exe        抢课(参数从config.txt读取)

把所有的文件搞到一个目录下,在此目录下打开powershell(输入powershell), 针对自己的要求输入对应的指令。

另外,线程参数和时间间隔选项在config提到了,这个参数就可以了,不需要把线程数搞的太多和时间间隔搞的太低。

相关推荐
星夜夏空991 小时前
C++学习(3) —— C++输入输出流
c++·学习
MOONICK1 小时前
windows原生条件变量支持
c++·windows
汉克老师2 小时前
GESP2026年6月认证C++二级( 第三部分编程题(1、完全平方数计数))精讲
c++·循环·枚举算法·gesp2级·平方数·逆向枚举·区间判断
wuminyu2 小时前
markword在高并发场景下变化剖析
java·linux·c语言·jvm·c++
星夜夏空992 小时前
C++学习(1) ——C与C++
c语言·c++·学习
旖-旎2 小时前
QT界面优化(6)
开发语言·c++·qt
UP_Continue2 小时前
AutoCAD--图形命令和选项
c++·autopilot
零点零一2 小时前
QT 5升级到 Qt 6 使用 Clazy 检查将 C++ 应用程序移植到 Qt 6
开发语言·c++·qt
爱奥尼欧2 小时前
轻量级可扩展日志框架-异步日志与系统集成
开发语言·数据库·c++·学习