Files
steel_prices_service/loginApi.js
2026-01-06 11:01:47 +08:00

349 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const axios = require("axios");
const fs = require('fs');
const path = require('path');
/**
* Token 缓存信息
* @typedef {Object} TokenCache
* @property {string} token - Token 值
* @property {string} expiresAt - 过期时间 (ISO 字符串)
*/
/**
* 外部 Token 获取服务
* 支持文件持久化和自动过期更新
* @class ExternalToken
*/
class ExternalToken {
constructor() {
/**
* Token 文件存储路径
* @type {string}
* @private
*/
this._tokenFilePath = path.join(__dirname, '.token-cache.json');
/**
* Token 有效期(毫秒),默认 30 分钟
* @type {number}
* @private
*/
this._tokenValidityMs = 30 * 60 * 1000;
/**
* 是否正在刷新 Token防止并发重复请求
* @type {Promise|null}
* @private
*/
this._refreshingPromise = null;
/**
* 内存中的 Token 缓存
* @type {TokenCache|null}
* @private
*/
this._memoryCache = null;
// 初始化时从文件加载 Token
this._loadTokenFromFile();
}
/**
* 从文件加载 Token
* @private
*/
_loadTokenFromFile() {
try {
if (fs.existsSync(this._tokenFilePath)) {
const data = fs.readFileSync(this._tokenFilePath, 'utf-8');
const cache = JSON.parse(data);
// 验证数据结构
if (cache.token && cache.expiresAt) {
this._memoryCache = {
token: cache.token,
expiresAt: new Date(cache.expiresAt)
};
const isExpired = this._isTokenExpired();
console.log(`📂 从文件加载 Token ${isExpired ? '(已过期)' : '(有效)'}`);
console.log(` 过期时间: ${this._memoryCache.expiresAt.toLocaleString('zh-CN')}`);
} else {
console.warn('⚠️ Token 文件格式无效,将重新获取');
this._memoryCache = null;
}
} else {
console.log('📄 Token 文件不存在,将创建新文件');
}
} catch (error) {
console.error('❌ 读取 Token 文件失败:', error.message);
this._memoryCache = null;
}
}
/**
* 保存 Token 到文件
* @param {string} token - Token 值
* @private
*/
_saveTokenToFile(token) {
try {
const expiresAt = new Date(Date.now() + this._tokenValidityMs);
const cache = {
token,
expiresAt: expiresAt.toISOString(),
updatedAt: new Date().toISOString()
};
fs.writeFileSync(this._tokenFilePath, JSON.stringify(cache, null, 2), 'utf-8');
console.log(`💾 Token 已保存到文件: ${this._tokenFilePath}`);
console.log(` 过期时间: ${expiresAt.toLocaleString('zh-CN')}`);
} catch (error) {
console.error('❌ 保存 Token 文件失败:', error.message);
}
}
/**
* 删除 Token 文件
* @private
*/
_deleteTokenFile() {
try {
if (fs.existsSync(this._tokenFilePath)) {
fs.unlinkSync(this._tokenFilePath);
console.log('🗑️ Token 文件已删除');
}
} catch (error) {
console.error('❌ 删除 Token 文件失败:', error.message);
}
}
/**
* 检查 Token 是否过期
* @returns {boolean}
* @private
*/
_isTokenExpired() {
if (!this._memoryCache) {
return true;
}
const now = new Date();
return now >= this._memoryCache.expiresAt;
}
/**
* 清除 Token 缓存(内存和文件)
*/
clearTokenCache() {
this._memoryCache = null;
this._deleteTokenFile();
console.log('🗑️ Token 缓存已清除');
}
/**
* 设置 Token 有效期
* @param {number} minutes - 有效期(分钟)
*/
setTokenValidity(minutes) {
this._tokenValidityMs = minutes * 60 * 1000;
console.log(`⏱️ Token 有效期已设置为 ${minutes} 分钟`);
}
/**
* 获取新的登录 Token
* @returns {Promise<string>} Token 值
* @throws {Error} 当获取失败时抛出错误
* @private
*/
async _fetchNewToken() {
const config = {
method: 'POST',
url: 'https://xdwlgyl.yciccloud.com/gdpaas/login/doLogin.htm',
headers: {
'host': 'xdwlgyl.yciccloud.com',
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
'accept': 'application/json, text/javascript, */*; q=0.01',
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'x-requested-with': 'XMLHttpRequest',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'origin': 'https://xdwlgyl.yciccloud.com',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://xdwlgyl.yciccloud.com/gdpaas/login/index.htm',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cookie': 'SYS-A7EE-D0E8-BE614B80=1108%405b2bc1b4b8be40dfb104b1f5c84c1245;SameSite=Lax;HWWAFSESTIME=1767662772583;HWWAFSESID=7817173569731ea415;JSESSIONID=7AF7F82CBD7C1FFC6CBCAF67CFD22AC6',
'Connection': 'keep-alive'
},
data: 'userId=15758339512&pwd=4E71002969FCD46813B869E931AEDF4B&randCode=&langId='
};
try {
console.log('🔄 正在请求新 Token...');
const response = await axios.request(config);
// 安全地提取 tokenId
const tokenId = response?.data?.data?.user?.exts?.tokenId;
if (!tokenId) {
throw new Error('响应中未找到 tokenId');
}
console.log('✅ 新 Token 获取成功:', tokenId);
// 保存到内存和文件
const expiresAt = new Date(Date.now() + this._tokenValidityMs);
this._memoryCache = { token: tokenId, expiresAt };
this._saveTokenToFile(tokenId);
return tokenId;
} catch (error) {
const errorMsg = error.response?.data || error.message;
console.error('❌ Token 获取失败:', errorMsg);
throw new Error(`Token 获取失败: ${errorMsg}`);
}
}
/**
* 获取登录 Token支持文件持久化和自动过期更新
* @param {boolean} forceRefresh - 是否强制刷新 Token
* @returns {Promise<{success: boolean, data?: string, error?: string, timestamp: string, cached?: boolean, source?: string}>}
*/
async getToken(forceRefresh = false) {
try {
// 如果未强制刷新且 Token 未过期,直接返回缓存
if (!forceRefresh && !this._isTokenExpired()) {
const source = this._memoryCache ? '文件缓存' : '内存缓存';
console.log(`♻️ 使用${source}的 Token`);
return {
success: true,
data: this._memoryCache.token,
timestamp: new Date().toISOString(),
cached: true,
source
};
}
// 如果正在刷新,等待刷新完成(防止并发请求)
if (this._refreshingPromise) {
console.log('⏳ Token 刷新中,等待完成...');
await this._refreshingPromise;
return {
success: true,
data: this._memoryCache.token,
timestamp: new Date().toISOString(),
cached: false,
source: '新获取'
};
}
// 开始刷新 Token
this._refreshingPromise = this._fetchNewToken();
const token = await this._refreshingPromise;
// 清除刷新标记
this._refreshingPromise = null;
return {
success: true,
data: token,
timestamp: new Date().toISOString(),
cached: false,
source: '新获取'
};
} catch (error) {
this._refreshingPromise = null;
return {
success: false,
error: error.message,
timestamp: new Date().toISOString()
};
}
}
/**
* 获取当前缓存的 Token不检查过期
* @returns {string|null}
*/
getCachedToken() {
return this._memoryCache?.token || null;
}
/**
* 获取 Token 过期时间
* @returns {Date|null}
*/
getTokenExpiresAt() {
return this._memoryCache?.expiresAt || null;
}
/**
* 检查 Token 是否即将过期(剩余时间少于 5 分钟)
* @returns {boolean}
*/
isTokenExpiringSoon() {
if (!this._memoryCache) {
return true;
}
const now = new Date();
const timeLeft = this._memoryCache.expiresAt - now;
const fiveMinutes = 5 * 60 * 1000;
return timeLeft < fiveMinutes;
}
/**
* 获取 Token 剩余有效时间(秒)
* @returns {number|null} 剩余秒数,如果 Token 不存在或已过期返回 null
*/
getTokenRemainingTime() {
if (!this._memoryCache) {
return null;
}
const now = new Date();
const timeLeft = Math.max(0, this._memoryCache.expiresAt - now);
return Math.floor(timeLeft / 1000);
}
/**
* 获取 Token 文件路径
* @returns {string}
*/
getTokenFilePath() {
return this._tokenFilePath;
}
}
/**
* 导出单例实例
*/
module.exports = new ExternalToken();
/**
* 如果直接运行此文件,执行示例请求
*/
if (require.main === module) {
const collector = require('./loginApi.js');
collector.getToken()
.then(result => {
if (result.success) {
console.log('✅ 数据获取成功');
console.log('📅 时间:', result.timestamp);
console.log('📊 数据预览:', JSON.stringify(result.data, null, 2));
} else {
console.error('❌ 数据获取失败:', result.error);
}
})
.catch(error => {
console.error('💥 未捕获的错误:', error);
});
}