<?php
session_start();
error_reporting(E_ALL);
ini_set('display_errors', 1);
define('BASE_URL', 'https://www.shgb.cn');
define('COOKIE_DIR', __DIR__ . '/cookies');
define('SPECIAL_COLUMN_ID', '6d01e944095bb2118eb1a399e40f6b5f');
if (!file_exists(COOKIE_DIR)) {
mkdir(COOKIE_DIR, 0755, true);
}
function getCookiePath($user) {
return COOKIE_DIR . '/' . md5($user) . '.txt';
}
function saveSession($user, $data) {
$_SESSION[$user] = $data;
}
function getSession($user) {
return $_SESSION[$user] ?? null;
}
function clearSession($user) {
unset($_SESSION[$user]);
}
function curlRequest($url, $method = 'GET', $data = null, $cookieFile = null, $extraHeaders = []) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$headers = [
'Accept: */*',
'Accept-Language: zh-CN,zh;q=0.9',
'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Mobile Safari/537.36',
];
if ($cookieFile) {
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile);
}
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
if (is_array($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
$headers[] = 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8';
}
$headers = array_merge($headers, $extraHeaders);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
return [
'response' => $response,
'httpCode' => $httpCode,
'error' => $error,
'contentType' => $contentType
];
}
function getCaptcha($user) {
$cookieFile = getCookiePath($user);
$ch = curl_init(BASE_URL . '/djrck/captcha/captchaImage?type=number&t=' . time());
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$img = curl_exec($ch);
curl_close($ch);
return $img;
}
function doLogin($user, $pwd, $code, $cookieFile) {
$postData = [
'nameid' => base64_encode($user),
'password' => base64_encode($pwd),
'imgvalidateCode' => $code,
'screenSize' => '1920x1080'
];
$result = curlRequest(BASE_URL . '/djrck/checkXcUserBynameOrId', 'POST', $postData, $cookieFile);
$data = json_decode($result['response'], true);
if ($data && $data['code'] === 0) {
return [
'success' => true,
'name' => $data['entity']['userName'] ?? '',
'userId' => $data['entity']['userId'] ?? ''
];
}
return ['success' => false, 'msg' => $data['msg'] ?? '登录失败: ' . substr($result['response'], 0, 100)];
}
function getCourseList($cookieFile) {
$specialColumnId = SPECIAL_COLUMN_ID;
$courses = [];
$pageNum = 1;
$pageSize = 10;
while (true) {
$url = BASE_URL . '/djrck/reception/specialColumnList/getDetailData?specialColumnId=' . $specialColumnId;
$postData = 'specialColumnId=' . $specialColumnId . '&pageSize=' . $pageSize . '&pageNum=' . $pageNum . '&orderByColumn=pcc.sort&isAsc=asc';
$result = curlRequest($url, 'POST', $postData, $cookieFile, [
'X-Requested-With: XMLHttpRequest',
'Referer: ' . BASE_URL . '/djrck/reception/specialColumnList/detail/' . $specialColumnId,
'Origin: ' . BASE_URL,
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-origin'
]);
if ($result['httpCode'] !== 200) {
return [
'success' => false,
'msg' => 'HTTP ' . $result['httpCode'],
'debug' => $result['response'],
'contentType' => $result['contentType']
];
}
$data = json_decode($result['response'], true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'success' => false,
'msg' => 'JSON解析失败: ' . json_last_error_msg(),
'debug' => $result['response'],
'httpCode' => $result['httpCode'],
'contentType' => $result['contentType']
];
}
if (!$data || ($data['code'] ?? null) !== 0) {
return [
'success' => false,
'msg' => '接口错误: code=' . ($data['code'] ?? 'null') . ' msg=' . ($data['msg'] ?? ''),
'debug' => $result['response'],
'httpCode' => $result['httpCode']
];
}
$total = $data['total'];
foreach ($data['rows'] as $row) {
$courses[] = [
'courseId' => $row['courseId'],
'courseName' => $row['courseName'],
'courseLecturername' => $row['courseLecturername'],
'truetime' => intval($row['truetime']),
'timelength' => intval($row['timelength']) * 60,
'courseFinish' => $row['courseFinish'],
'sort' => $row['sort']
];
}
if (count($courses) >= $total) break;
$pageNum++;
}
return ['success' => true, 'total' => $total, 'courses' => $courses];
}
function getCsid($courseId, $cookieFile, $specialColumnId) {
$url = BASE_URL . '/djrck/political/course/detail?id=' . $courseId . '&type=column&typeid=' . $specialColumnId;
$result = curlRequest($url, 'GET', null, $cookieFile);
if (!$result['response']) return null;
if (preg_match('/["\']?csid["\']?\s*[:=]\s*["\']([a-f0-9]{32})["\']/', $result['response'], $m)) {
return $m[1];
}
if (preg_match('/addDetail\s*\(\s*["\']([a-f0-9]{32})["\']/', $result['response'], $m)) {
return $m[1];
}
return null;
}
function sendLearnPacket($csid, $currentTime, $truetime, $cookieFile, $courseId, $specialColumnId) {
$url = BASE_URL . '/djrck/political/course/addDetail';
$postData = 'csid=' . $csid . '¤tTime=' . $currentTime . '&truetime=' . $truetime;
$result = curlRequest($url, 'POST', $postData, $cookieFile, [
'X-Requested-With: XMLHttpRequest',
'Referer: ' . BASE_URL . '/djrck/political/course/detail?id=' . $courseId . '&type=column&typeid=' . $specialColumnId,
'Origin: ' . BASE_URL
]);
$data = json_decode($result['response'], true);
return [
'success' => $data && ($data['code'] ?? null) === 0,
'msg' => $data['msg'] ?? '',
'response' => $data
];
}
$action = $_GET['action'] ?? '';
if ($action === 'captcha') {
header('Content-Type: image/jpeg');
echo getCaptcha($_GET['user'] ?? 'temp');
exit;
}
if ($action === 'login') {
header('Content-Type: application/json');
$user = trim($_POST['user'] ?? '');
$pwd = trim($_POST['pwd'] ?? '');
$code = trim($_POST['code'] ?? '');
if (empty($user) || empty($pwd) || empty($code)) {
echo json_encode(['success' => false, 'msg' => '参数不完整']);
exit;
}
$cookieFile = getCookiePath($user);
$result = doLogin($user, $pwd, $code, $cookieFile);
if ($result['success']) {
saveSession($user, ['user' => $user, 'name' => $result['name'], 'userId' => $result['userId']]);
}
echo json_encode($result);
exit;
}
if ($action === 'get_list') {
header('Content-Type: application/json');
$user = trim($_POST['user'] ?? '');
$session = getSession($user);
if (!$session) {
echo json_encode(['success' => false, 'msg' => '请先登录']);
exit;
}
$cookieFile = getCookiePath($user);
$result = getCourseList($cookieFile);
if ($result['success']) {
saveSession($user . '_courses', $result['courses']);
echo json_encode([
'success' => true,
'total' => $result['total'],
'courses' => $result['courses']
]);
} else {
echo json_encode($result);
}
exit;
}
if ($action === 'learn') {
header('Content-Type: application/json');
$user = trim($_POST['user'] ?? '');
$index = intval($_POST['index'] ?? 0);
$session = getSession($user);
if (!$session) {
echo json_encode(['success' => false, 'msg' => '请先登录']);
exit;
}
$courses = getSession($user . '_courses');
if (!$courses || !isset($courses[$index])) {
echo json_encode(['success' => false, 'msg' => '课程不存在']);
exit;
}
$course = $courses[$index];
$cookieFile = getCookiePath($user);
$csid = getCsid($course['courseId'], $cookieFile, SPECIAL_COLUMN_ID);
if (!$csid) {
echo json_encode(['success' => false, 'msg' => '获取csid失败,cookie可能已过期']);
exit;
}
$currentTime = intval($course['truetime']) + rand(30, 60);
$result = sendLearnPacket($csid, $currentTime, $currentTime, $cookieFile, $course['courseId'], SPECIAL_COLUMN_ID);
if ($result['success']) {
$courses[$index]['truetime'] = $currentTime;
saveSession($user . '_courses', $courses);
echo json_encode([
'success' => true,
'csid' => $csid,
'currentTime' => $currentTime,
'courseName' => $course['courseName'],
'timelength' => $course['timelength'],
'isFinished' => $currentTime >= $course['timelength']
]);
} else {
echo json_encode($result);
}
exit;
}
if ($action === 'logout') {
header('Content-Type: application/json');
$user = trim($_POST['user'] ?? '');
clearSession($user);
clearSession($user . '_courses');
$cookieFile = getCookiePath($user);
if (file_exists($cookieFile)) unlink($cookieFile);
echo json_encode(['success' => true]);
exit;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>专题库学习系统</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f7fa;padding:20px}
.container{max-width:900px;margin:0 auto}
.card{background:#fff;border-radius:12px;padding:24px;margin-bottom:20px;box-shadow:0 2px 12px rgba(0,0,0,0.08)}
h1{font-size:24px;margin-bottom:20px;color:#303133}
h2{font-size:18px;margin-bottom:16px;color:#606266}
.form-group{margin-bottom:16px}
.form-row{display:flex;gap:12px;flex-wrap:wrap}
label{display:block;margin-bottom:6px;font-size:14px;color:#606266}
input[type="text"],input[type="password"]{padding:10px 14px;border:1px solid #dcdfe6;border-radius:6px;font-size:14px;width:100%;transition:border-color .2s}
input[type="text"]:focus,input[type="password"]:focus{outline:none;border-color:#409eff}
.input-group{display:flex;gap:8px;align-items:flex-end}
.input-group input{flex:1}
.captcha-img{height:42px;border:1px solid #dcdfe6;border-radius:6px;cursor:pointer}
button{padding:10px 20px;background:#409eff;color:#fff;border:none;border-radius:6px;font-size:14px;cursor:pointer;transition:all .2s}
button:hover{background:#66b1ff}
button:disabled{background:#c0c4cc;cursor:not-allowed}
button.danger{background:#f56c6c}button.danger:hover{background:#f78989}
button.success{background:#67c23a}button.success:hover{background:#85ce61}
.status-bar{display:flex;gap:16px;align-items:center;padding:12px 16px;background:#f0f9ff;border-radius:8px;margin-bottom:16px}
.status-item{display:flex;align-items:center;gap:6px;font-size:14px}
.status-dot{width:10px;height:10px;border-radius:50%}
.status-dot.online{background:#67c23a}
.course-list{max-height:400px;overflow-y:auto;border:1px solid #ebeef5;border-radius:8px}
.course-item{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #ebeef5}
.course-item:last-child{border-bottom:none}
.course-item.current{background:#ecf5ff}
.course-item.finished{background:#f0f9eb;opacity:.7}
.course-info{flex:1}
.course-name{font-size:14px;font-weight:500;color:#303133;margin-bottom:4px}
.course-meta{font-size:12px;color:#909399}
.course-progress{width:120px;text-align:right}
.progress-bar{width:100%;height:6px;background:#ebeef5;border-radius:3px;overflow:hidden;margin-top:4px}
.progress-fill{height:100%;background:#409eff;border-radius:3px;transition:width .3s}
.control-panel{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
.btn-group{display:flex;gap:8px}
.log-area{background:#1e1e1e;color:#d4d4d4;padding:16px;border-radius:8px;font-family:'Consolas','Monaco',monospace;font-size:13px;max-height:300px;overflow-y:auto}
.log-entry{margin-bottom:6px}
.log-time{color:#6a9955}
.log-info{color:#4fc1ff}
.log-success{color:#67c23a}
.log-error{color:#f56c6c}
.debug-card{background:#fff1f0;border:1px solid #ffa39e;border-radius:8px;padding:20px;margin-top:10px}
.debug-card h3{color:#f56c6c;margin-bottom:15px}
.debug-card pre{background:#1e1e1e;color:#d4d4d4;padding:15px;border-radius:6px;overflow-x:auto;white-space:pre-wrap;word-break:break-all;max-height:300px;overflow-y:auto;font-size:12px}
.hidden{display:none!important}
</style>
</head>
<body>
<div class="container">
<h1>专题库学习系统</h1>
<div class="card" id="loginCard">
<h2>账号登录</h2>
<div class="form-row">
<div class="form-group" style="flex:1;min-width:200px">
<label>账号</label>
<input type="text" id="loginUser" placeholder="请输入账号">
</div>
<div class="form-group" style="flex:1;min-width:200px">
<label>密码</label>
<input type="password" id="loginPwd" placeholder="请输入密码">
</div>
<div class="form-group">
<label>验证码</label>
<div class="input-group">
<input type="text" id="loginCode" placeholder="4位验证码" maxlength="4" style="width:100px">
<img id="captchaImg" class="captcha-img" src="?action=captcha&user=temp" onclick="refreshCaptcha()">
</div>
</div>
</div>
<button onclick="doLogin()" id="loginBtn">登录</button>
</div>
<div class="card hidden" id="mainCard">
<div class="status-bar">
<div class="status-item"><span class="status-dot online"></span><span id="userName">--</span></div>
<div class="status-item" id="statusText">状态: 等待开始</div>
<div style="margin-left:auto"><button class="danger" onclick="doLogout()">退出</button></div>
</div>
<h2>专题设置</h2>
<div class="form-group">
<label>专题ID (已固定)</label>
<input type="text" value="6d01e944095bb2118eb1a399e40f6b5f" readonly>
</div>
<button onclick="getCourseList()" style="margin-bottom:16px">获取课程列表</button>
<h2>课程列表</h2>
<div class="course-list" id="courseList">
<div style="padding:40px;text-align:center;color:#909399">请先获取课程列表</div>
</div>
<div class="control-panel" style="margin-top:16px">
<div class="btn-group">
<button class="success" id="startBtn" onclick="startAutoLearn()">开始自动学习</button>
<button class="danger" id="stopBtn" onclick="stopAutoLearn()" disabled>停止</button>
<button id="stepBtn" onclick="stepLearn()">手动发包一次</button>
</div>
<div class="form-group" style="margin:0">
<label>发包间隔 (秒)</label>
<input type="number" id="intervalInput" value="10" min="5" max="60" style="width:80px">
</div>
</div>
<h2 style="margin-top:20px">运行日志</h2>
<div class="log-area" id="logArea">
<div class="log-entry"><span class="log-time">[系统]</span> <span class="log-info">等待操作...</span></div>
</div>
</div>
</div>
<script>
let currentUser = '';
let courses = [];
let currentIndex = 0;
let autoLearnInterval = null;
function refreshCaptcha() {
const u = document.getElementById('loginUser').value || 'temp';
document.getElementById('captchaImg').src = '?action=captcha&user=' + encodeURIComponent(u) + '&t=' + Date.now();
}
function addLog(msg, type) {
type = type || 'info';
const a = document.getElementById('logArea');
const t = new Date().toLocaleTimeString('zh-CN');
const cls = type === 'success' ? 'log-success' : type === 'error' ? 'log-error' : 'log-info';
const e = document.createElement('div');
e.className = 'log-entry';
e.innerHTML = '<span class="log-time">[' + t + ']</span> <span class="' + cls + '">' + msg + '</span>';
a.insertBefore(e, a.firstChild);
if (a.querySelectorAll('.log-entry').length > 100) a.lastChild.remove();
}
async function doLogin() {
const user = document.getElementById('loginUser').value.trim();
const pwd = document.getElementById('loginPwd').value.trim();
const code = document.getElementById('loginCode').value.trim();
if (!user || !pwd || !code) { alert('请填写完整信息'); return; }
document.getElementById('loginBtn').disabled = true;
document.getElementById('loginBtn').textContent = '登录中...';
const fd = new FormData();
fd.append('user', user); fd.append('pwd', pwd); fd.append('code', code);
try {
const r = await fetch('?action=login', { method: 'POST', body: fd });
const j = await r.json();
if (j.success) {
currentUser = user;
addLog('登录成功: ' + j.name, 'success');
document.getElementById('userName').textContent = j.name;
document.getElementById('loginCard').classList.add('hidden');
document.getElementById('mainCard').classList.remove('hidden');
} else {
addLog('登录失败: ' + j.msg, 'error');
alert(j.msg);
refreshCaptcha();
}
} catch(e) { addLog('网络错误', 'error'); alert('网络错误'); }
document.getElementById('loginBtn').disabled = false;
document.getElementById('loginBtn').textContent = '登录';
document.getElementById('loginCode').value = '';
}
async function doLogout() {
if (!confirm('确定退出?')) return;
const fd = new FormData(); fd.append('user', currentUser);
try { await fetch('?action=logout', { method: 'POST', body: fd }); } catch(e) {}
currentUser = ''; courses = []; currentIndex = 0; stopAutoLearn();
document.getElementById('mainCard').classList.add('hidden');
document.getElementById('loginCard').classList.remove('hidden');
}
async function getCourseList() {
const fd = new FormData(); fd.append('user', currentUser);
addLog('正在获取课程列表...', 'info');
try {
const r = await fetch('?action=get_list', { method: 'POST', body: fd });
const j = await r.json();
if (j.success) {
courses = j.courses; currentIndex = 0;
renderCourseList();
addLog('获取成功, 共 ' + courses.length + ' 门课程', 'success');
} else {
var div = document.getElementById('courseList');
div.innerHTML = '<div class="debug-card">'
+ '<h3>错误调试信息</h3>'
+ '<p style="margin-bottom:8px"><strong>错误:</strong> ' + eschtml(j.msg) + '</p>'
+ '<p style="margin-bottom:8px"><strong>HTTP:</strong> ' + (j.httpCode || '-') + '</p>'
+ '<p style="margin-bottom:8px"><strong>ContentType:</strong> ' + (j.contentType || '-') + '</p>'
+ '<p style="margin-bottom:8px"><strong>原始响应(前1000字符):</strong></p>'
+ '<pre>' + eschtml((j.debug || '').substring(0, 1000)) + '</pre>'
+ '</div>';
addLog('失败: ' + j.msg, 'error');
}
} catch(e) { addLog('网络错误: ' + e.message, 'error'); }
}
function eschtml(s) {
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function fmtTime(s) {
var m = Math.floor(s / 60);
var sec = s % 60;
return ('0' + m).slice(-2) + ':' + ('0' + sec).slice(-2);
}
function renderCourseList() {
var c = document.getElementById('courseList');
if (courses.length === 0) {
c.innerHTML = '<div style="padding:40px;text-align:center;color:#909399">暂无课程</div>';
return;
}
var h = '';
courses.forEach(function(v, i) {
var p = Math.min(100, Math.round((v.truetime / v.timelength) * 100));
var fin = v.truetime >= v.timelength;
var cur = i === currentIndex;
h += '<div class="course-item ' + (cur ? 'current' : '') + ' ' + (fin ? 'finished' : '') + '">'
+ '<div class="course-info">'
+ '<div class="course-name">' + (cur ? '▶ ' : '') + (i + 1) + '. ' + v.courseName + '</div>'
+ '<div class="course-meta">讲师: ' + (v.courseLecturername || '--') + '</div>'
+ '</div>'
+ '<div class="course-progress">'
+ '<div>' + fmtTime(v.truetime) + ' / ' + fmtTime(v.timelength) + '</div>'
+ '<div class="progress-bar"><div class="progress-fill" style="width:' + p + '%"></div></div>'
+ '</div></div>';
});
c.innerHTML = h;
}
async function stepLearn() {
if (courses.length === 0) { alert('请先获取课程列表'); return; }
if (currentIndex >= courses.length) { addLog('所有课程已完成!', 'success'); return; }
document.getElementById('stepBtn').disabled = true;
document.getElementById('statusText').textContent = '状态: 正在学习 - ' + courses[currentIndex].courseName;
var fd = new FormData();
fd.append('user', currentUser);
fd.append('index', currentIndex);
try {
var r = await fetch('?action=learn', { method: 'POST', body: fd });
var j = await r.json();
if (j.success) {
courses[currentIndex].truetime = j.currentTime;
renderCourseList();
addLog('[' + courses[currentIndex].courseName + '] 发包成功: ' + fmtTime(j.currentTime) + '/' + fmtTime(j.timelength), 'success');
if (j.isFinished) {
addLog('[' + courses[currentIndex].courseName + '] 学习完成!', 'success');
currentIndex++;
if (currentIndex < courses.length) {
addLog('切换到下一课: ' + courses[currentIndex].courseName, 'info');
renderCourseList();
} else {
addLog('所有课程学习完成!', 'success');
document.getElementById('statusText').textContent = '状态: 全部完成';
stopAutoLearn();
}
}
} else {
addLog('发包失败: ' + j.msg, 'error');
}
} catch(e) { addLog('网络错误', 'error'); }
document.getElementById('stepBtn').disabled = false;
}
function startAutoLearn() {
if (courses.length === 0) { alert('请先获取课程列表'); return; }
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('statusText').textContent = '状态: 自动学习中...';
addLog('开始自动学习', 'info');
var interval = Math.max(5, parseInt(document.getElementById('intervalInput').value) || 10) * 1000;
stepLearn();
autoLearnInterval = setInterval(function() {
if (currentIndex >= courses.length) { stopAutoLearn(); return; }
stepLearn();
}, interval);
}
function stopAutoLearn() {
if (autoLearnInterval) { clearInterval(autoLearnInterval); autoLearnInterval = null; }
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
document.getElementById('statusText').textContent = '状态: 已停止';
addLog('停止学习', 'info');
}
document.getElementById('loginUser').addEventListener('input', refreshCaptcha);
</script>
</body>
</html>
最后修改:2026 年 05 月 23 日
© 允许规范转载