移除登录失败限流;修复连接信息被 e2e 串味为 127.0.0.1
- bootstrap.php/login.php:整体移除「5 次失败锁 10 分钟」限流,登录失败仅即时提示原因 - 新增 tools/fix-config.php:一键双写修正连接地址(默认 exam.nxxy335.top),清空账号密码让用户重输真实卷王凭据 - NY_DATA 隔离落地:run-all.sh 给写配置的 e2e 指向临时目录;修 Windows Git Bash /c/... 路径规范化 bug;.ny-test-data 加 .gitignore - 清理 dead throttle 引用(e2e 脚本 + docs) - 修 login.php 真实 bug:ny_is_auth() 分支在加载 $cfg 前引用导致 Undefined variable 警告 - 全量测试 116 通过(PHP 44 + e2e 72)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* 修复 / 重置系统连接配置(终端一次性工具)
|
||||
*
|
||||
* 背景:
|
||||
* 连接配置同时存在于 data/ny.db(SQLite,加载时优先)与 data/config.json(文件备份)。
|
||||
* ny_load_config() 以 DB 为准,因此一旦部署实例的 ny.db 被 e2e 测试串味或误改写成
|
||||
* 127.0.0.1 / mock 地址,登录页就会显示「系统连接信息已配置(127.0.0.1/api)」且连不上真实卷王。
|
||||
* 本工具通过 ny_save_config 双写,一次性把两处都纠正回正确的生产地址。
|
||||
*
|
||||
* 用法:
|
||||
* php tools/fix-config.php [--base-url=https://exam.nxxy335.top] [--api-prefix=/api] [--keep-creds]
|
||||
*
|
||||
* 默认行为:
|
||||
* - base_url 修正为 https://exam.nxxy335.top(可用 --base-url 覆盖)
|
||||
* - api_prefix 修正为 /api
|
||||
* - 账号/密码清空(绝不臆造真实卷王凭据),需在登录页重新输入真实账号
|
||||
* - 加 --keep-creds 则保留现有账号密码不动
|
||||
*
|
||||
* 测试隔离:可用 NY_DATA=/tmp/xxx 指向临时目录,不影响生产库。
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
require __DIR__ . '/../bootstrap.php';
|
||||
|
||||
$args = array_slice($argv, 1);
|
||||
$baseUrl = 'https://exam.nxxy335.top';
|
||||
$apiPrefix = '/api';
|
||||
$keepCreds = false;
|
||||
foreach ($args as $a) {
|
||||
if (strncmp($a, '--base-url=', 11) === 0) {
|
||||
$baseUrl = substr($a, 11);
|
||||
} elseif (strncmp($a, '--api-prefix=', 13) === 0) {
|
||||
$apiPrefix = substr($a, 13);
|
||||
} elseif ($a === '--keep-creds') {
|
||||
$keepCreds = true;
|
||||
} elseif ($a === '--help' || $a === '-h') {
|
||||
echo "用法: php tools/fix-config.php [--base-url=URL] [--api-prefix=/api] [--keep-creds]\n";
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
$cfg = ny_load_config();
|
||||
$before = (string)($cfg['base_url'] ?? '');
|
||||
$cfg['base_url'] = rtrim(trim($baseUrl), '/');
|
||||
$cfg['api_prefix'] = '/' . ltrim(trim($apiPrefix), '/');
|
||||
if (!$keepCreds) {
|
||||
$cfg['username'] = '';
|
||||
$cfg['password'] = '';
|
||||
}
|
||||
|
||||
$ok = ny_save_config($cfg);
|
||||
if (!$ok) {
|
||||
fwrite(STDERR, "保存失败:DB 与文件均不可写?请检查 " . NY_DATA . " 目录权限。\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$host = (string) parse_url($cfg['base_url'], PHP_URL_HOST);
|
||||
echo "修复前 base_url = " . ($before === '' ? '(空)' : $before) . "\n";
|
||||
echo "修复后 base_url = " . $cfg['base_url'] . "\n";
|
||||
echo "api_prefix = " . $cfg['api_prefix'] . "\n";
|
||||
echo "登录页将显示 = 系统连接信息已配置(" . $host . $cfg['api_prefix'] . ")\n";
|
||||
echo "账号/密码 = " . ($keepCreds ? '保持不变' : '已清空(请在登录页重新输入真实卷王账号)') . "\n";
|
||||
echo "配置来源 = " . ny_config_source() . "\n";
|
||||
echo "OK\n";
|
||||
+16
-10
@@ -11,10 +11,23 @@ const PHP = process.env.PHP_BIN || 'C:/Users/Zhang/.workbuddy/binaries/php/php.
|
||||
const INI = process.env.PHP_INI || '-d extension_dir=C:/Users/Zhang/.workbuddy/binaries/php/ext -d extension=pdo_sqlite';
|
||||
|
||||
// 1) 备份 / 准备文件存储模式
|
||||
const cfgFile = path.join(ROOT, 'data', 'config.json');
|
||||
// 数据目录默认用项目下的 data/,但优先读 NY_DATA 环境变量(bootstrap.php 同样支持):
|
||||
// 测试统一把 NY_DATA 指向临时目录,彻底避免把 mock 配置写进生产库(曾导致生产 base_url 被改成 127.0.0.1)。
|
||||
// Git Bash 风格路径 /c/Users/... 在 Windows 上 path.resolve 会误判成 C:\c\...,需还原成 C:\Users\...;
|
||||
// 规范化后写回 process.env.NY_DATA,让子进程(PHP 内置服务器)也拿到正确的 Windows 绝对路径。
|
||||
function resolveDataDir() {
|
||||
let p = process.env.NY_DATA || path.join(ROOT, 'data');
|
||||
if (process.platform === 'win32' && /^\/[a-zA-Z]\//.test(p)) p = p[1].toUpperCase() + ':' + p.slice(2);
|
||||
p = path.resolve(p);
|
||||
process.env.NY_DATA = p;
|
||||
return p;
|
||||
}
|
||||
const DATA = resolveDataDir();
|
||||
fs.mkdirSync(DATA, { recursive: true });
|
||||
const cfgFile = path.join(DATA, 'config.json');
|
||||
const cfgBak = cfgFile + '.bak';
|
||||
const origCfg = fs.existsSync(cfgFile) ? fs.readFileSync(cfgFile, 'utf8') : null;
|
||||
const tokenFile = path.join(ROOT, 'data', 'cache', 'token.json');
|
||||
const tokenFile = path.join(DATA, 'cache', 'token.json');
|
||||
const tokenBak = tokenFile + '.bak';
|
||||
const hasToken = fs.existsSync(tokenFile);
|
||||
const tokenSnap = hasToken ? fs.readFileSync(tokenFile, 'utf8') : null;
|
||||
@@ -22,15 +35,11 @@ if (hasToken) fs.renameSync(tokenFile, tokenBak);
|
||||
|
||||
// SQLite 优先:若不先移走 data/ny.db,里面的旧配置会盖掉本测试写入的 config.json,
|
||||
// 导致登录用的是上一次遗留的账号密码(表现为"登录成功但 auth_check=false")。
|
||||
// 同时清掉登录限流记录,避免上一轮失败把本轮锁住。
|
||||
const dbFile = path.join(ROOT, 'data', 'ny.db');
|
||||
const dbFile = path.join(DATA, 'ny.db');
|
||||
const dbBak = dbFile + '.bak';
|
||||
const hasDb = fs.existsSync(dbFile);
|
||||
if (hasDb) { try { fs.renameSync(dbFile, dbBak); } catch (e) {} }
|
||||
|
||||
const throttleFile = path.join(ROOT, 'data', 'login_fail.json');
|
||||
if (fs.existsSync(throttleFile)) { try { fs.unlinkSync(throttleFile); } catch (e) {} }
|
||||
|
||||
fs.writeFileSync(cfgFile, JSON.stringify({
|
||||
base_url: 'http://127.0.0.1:19911',
|
||||
api_prefix: '/api',
|
||||
@@ -67,9 +76,6 @@ function cleanup() {
|
||||
if (fs.existsSync(dbFile)) fs.unlinkSync(dbFile);
|
||||
if (hasDb && fs.existsSync(dbBak)) fs.renameSync(dbBak, dbFile);
|
||||
} catch(e) { console.error('cleanup db failed:', e.message); }
|
||||
try {
|
||||
if (fs.existsSync(throttleFile)) fs.unlinkSync(throttleFile);
|
||||
} catch(e) {}
|
||||
}
|
||||
process.on('exit', cleanup);
|
||||
process.on('SIGINT', () => { cleanup(); process.exit(1); });
|
||||
|
||||
+14
-12
@@ -16,9 +16,20 @@ const PHP = process.env.PHP_BIN || 'C:/Users/Zhang/.workbuddy/binaries/php/php.e
|
||||
const INI = process.env.PHP_INI || '-d extension_dir=C:/Users/Zhang/.workbuddy/binaries/php/ext -d extension=pdo_sqlite';
|
||||
const DEBUG = !!process.env.NY_DEBUG;
|
||||
|
||||
const DATA = path.join(ROOT, 'data');
|
||||
// 数据目录优先读 NY_DATA 环境变量(与 bootstrap.php 一致),让测试整体指向临时目录,
|
||||
// 不污染生产库(曾因写 mock 配置把生产 base_url 改成 127.0.0.1)。
|
||||
// Git Bash 风格 /c/Users/... 在 Windows 上 path.resolve 会误判成 C:\c\...,需还原;并把规范化后的
|
||||
// Windows 绝对路径写回 process.env.NY_DATA,让子进程(PHP 内置服务器)也拿到正确路径。
|
||||
function resolveDataDir() {
|
||||
let p = process.env.NY_DATA || path.join(ROOT, 'data');
|
||||
if (process.platform === 'win32' && /^\/[a-zA-Z]\//.test(p)) p = p[1].toUpperCase() + ':' + p.slice(2);
|
||||
p = path.resolve(p);
|
||||
process.env.NY_DATA = p;
|
||||
return p;
|
||||
}
|
||||
const DATA = resolveDataDir();
|
||||
fs.mkdirSync(DATA, { recursive: true });
|
||||
const UPLOADS = path.join(DATA, 'uploads');
|
||||
const throttleFile = path.join(DATA, 'login_fail.json');
|
||||
|
||||
// ---------- 备份 / 还原运行时数据 ----------
|
||||
// ny.db 必须移走:配置是 DB 优先,留着库里的真实配置会盖掉本测试写入的 config.json。
|
||||
@@ -41,13 +52,6 @@ for (const f of ['config.json', 'roster.json', 'project_config.json']) {
|
||||
catch (e) { console.error(' 备份失败(复制)' + f + ':' + e.message); }
|
||||
}
|
||||
}
|
||||
// 清空登录限流记录:用覆盖写而不是删除(部分环境下删除会被安全策略拦掉)
|
||||
function clearThrottle() {
|
||||
try { fs.writeFileSync(throttleFile, '{}'); } catch (e) {}
|
||||
try { if (fs.existsSync(throttleFile)) fs.unlinkSync(throttleFile); } catch (e) {}
|
||||
}
|
||||
clearThrottle();
|
||||
|
||||
const cfgFile = path.join(DATA, 'config.json');
|
||||
fs.writeFileSync(cfgFile, JSON.stringify({
|
||||
base_url: 'http://127.0.0.1:' + MOCK_PORT,
|
||||
@@ -81,7 +85,6 @@ function cleanup() {
|
||||
const p = path.join(DATA, f);
|
||||
try { if (fs.existsSync(p)) fs.unlinkSync(p); } catch (e) {}
|
||||
}
|
||||
clearThrottle();
|
||||
// 统一用「覆盖写回 + 尽力删除备份」的方式还原,避免删除被拦导致还原失败
|
||||
for (const [p, bak] of moved.concat(copied)) {
|
||||
try { if (fs.existsSync(bak)) fs.copyFileSync(bak, p); } catch (e) {}
|
||||
@@ -199,9 +202,8 @@ function chk(name, cond, extra) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 登录(失败会触发 5 次锁定,所以每次尝试前清掉限流记录)
|
||||
// 登录(最多重试 3 次)
|
||||
for (let i = 0; i < 3 && CSRF === ''; i++) {
|
||||
clearThrottle(); // 上一次尝试若失败会累计限流,这里先清零
|
||||
const lg = await formPost('/login.php', { username: 'mock', password: 'surveyking' });
|
||||
const ac = await get('/api.php?action=auth_check');
|
||||
if (DEBUG) {
|
||||
|
||||
Reference in New Issue
Block a user