#!/usr/bin/env node /** * 开发期取 token —— 给 AI / 脚本 / 命令行用,不必开浏览器点登录。 * * 用法(在仓库根目录执行): * node scripts/dev-login.mjs # 用 web/.env.development 里的账号 * node scripts/dev-login.mjs G3HD g3soft # 指定机构码 + 账号 * node scripts/dev-login.mjs G3HD g3soft pwd # 再指定密码 * node scripts/dev-login.mjs --token-only # 只输出 token 一行,便于 $(...) 取值 * node scripts/dev-login.mjs --target http://127.0.0.1:8088 * * 直接打后端(默认 127.0.0.1:8088),不经过 Vite 代理。 * * ⚠️ 同一账号重复登录会互踢(后端 ActiveSessionRegistry,业务码 40101): * 这个脚本每跑一次就会把该账号上一个会话踢下线。别在别人正用同一账号时跑。 */ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import path from 'node:path' const ROOT = fileURLToPath(new URL('..', import.meta.url)) const ENV_FILE = path.join(ROOT, 'web', '.env.development') /* ---------------- 读取 web/.env.development 作为默认值 ---------------- */ function readEnvValue(key) { try { for (const line of readFileSync(ENV_FILE, 'utf8').split(/\r?\n/)) { const match = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line) if (match && match[1] === key) { return match[2].trim().replace(/^(['"])(.*)\1$/, '$2') } } } catch { // 文件不存在:回落到内置默认值,下面还会打印提示 } return '' } /* ---------------- 参数 ---------------- */ const positional = [] let target = '' let tokenOnly = false const args = process.argv.slice(2) for (let i = 0; i < args.length; i += 1) { if (args[i] === '--target') { target = args[i + 1] ?? '' i += 1 } else if (args[i] === '--token-only') { tokenOnly = true } else { positional.push(args[i]) } } const base = (target || process.env.VITE_PROXY_TARGET || 'http://127.0.0.1:8088').replace(/\/+$/, '') const orgId = positional[0] || readEnvValue('VITE_DEV_ORG') || 'G3HD' const account = positional[1] || readEnvValue('VITE_DEV_ACCOUNT') || 'g3soft' const password = positional[2] ?? readEnvValue('VITE_DEV_PASSWORD') /* ---------------- 登录 ---------------- */ let body try { const response = await fetch(`${base}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orgid: orgId, userid: account, password }), }) body = await response.json() } catch (error) { console.error(`连不上后端 ${base}:${error.message}`) console.error('先在 server/ 下起后端(legacy TLS 用这条):') console.error(' mvn "-Dspring-boot.run.jvmArguments=-Djava.security.properties=./config/legacy-tls.security" spring-boot:run') process.exit(1) } /* 后端鉴权失败返回 HTTP 200 + body.code=401(不是 HTTP 401),两个都要判。 */ if (body?.code !== 0 || !body?.data?.token) { console.error(`登录失败(code=${body?.code}):${body?.message || '响应里没有 token'}`) console.error(`用的账号:机构码 ${orgId} / 账号 ${account}${password ? ' / 密码已填' : ' / 密码为空'}`) console.error('账号密码来自 web/.env.development 的 VITE_DEV_*,也可命令行覆盖。') console.error('超管 g3soft 的密码在库里是空串;若你的库不是,传第三个参数指定密码。') process.exit(1) } const { token, user } = body.data /* ---------------- 输出 ---------------- */ if (tokenOnly) { process.stdout.write(`${token}\n`) } else { console.log(`已登录:${orgId}/${account}(${user?.name || '未命名'})`) console.log('') console.log('token:') console.log(token) console.log('') console.log('直接调业务接口(浏览器里不用这个,开发期页面已自动登录):') console.log(` curl -H "Authorization: Bearer ${token}" ${base}/api/data/loaddata`) }