Skip to main content
Glama
account-manager.js15.6 kB
/** * 账号管理服务 * 管理小红书账号的登录、状态检测、信息维护等功能 */ import { logger } from '../utils/logger.js'; import { query, transaction } from '../database/index.js'; import { BrowserManager } from '../browser/browser-manager.js'; import { ProxyManager } from '../proxy/proxy-manager.js'; import { FingerprintManager } from '../fingerprint/fingerprint-manager.js'; import { encrypt, decrypt } from '../utils/crypto.js'; /** * 账号管理器类 */ export class AccountManager { constructor() { this.browserManager = new BrowserManager(); this.proxyManager = new ProxyManager(); this.fingerprintManager = new FingerprintManager(); this.loginSessions = new Map(); // 登录会话缓存 } /** * 获取账号列表 * @param {Object} params - 查询参数 * @returns {Promise<Object>} 账号列表 */ async getAccountList(params = {}) { const { page = 1, pageSize = 20, status, search } = params; const offset = (page - 1) * pageSize; try { // 构建查询条件 let whereConditions = []; let queryParams = []; if (status) { whereConditions.push('status = ?'); queryParams.push(status); } if (search) { whereConditions.push('(username LIKE ? OR nickname LIKE ? OR phone LIKE ?)'); queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`); } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : ''; // 查询总数 const countQuery = ` SELECT COUNT(*) as total FROM idea_xiaohongshu_accounts ${whereClause} `; const [countResult] = await query(countQuery, queryParams); const total = countResult.total; // 查询列表 const listQuery = ` SELECT a.*, p.host as proxy_host, p.port as proxy_port, p.country as proxy_country, f.fingerprint_id, f.user_agent FROM idea_xiaohongshu_accounts a LEFT JOIN idea_xiaohongshu_proxies p ON a.proxy_id = p.id LEFT JOIN idea_xiaohongshu_fingerprints f ON a.fingerprint_id = f.id ${whereClause} ORDER BY a.created_at DESC LIMIT ? OFFSET ? `; queryParams.push(pageSize, offset); const accounts = await query(listQuery, queryParams); // 处理敏感信息 const processedAccounts = accounts.map(account => ({ id: account.id, username: account.username, phone: account.phone ? this.maskPhone(account.phone) : null, email: account.email ? this.maskEmail(account.email) : null, nickname: account.nickname, avatarUrl: account.avatar_url, status: account.status, loginStatus: account.login_status, lastLoginTime: account.last_login_time, proxy: account.proxy_host ? { host: account.proxy_host, port: account.proxy_port, country: account.proxy_country } : null, fingerprint: account.fingerprint_id ? { id: account.fingerprint_id, userAgent: account.user_agent } : null, createdAt: account.created_at, updatedAt: account.updated_at })); return { data: processedAccounts, pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize) } }; } catch (error) { logger.error('获取账号列表失败:', error); throw new Error('获取账号列表失败: ' + error.message); } } /** * 添加账号 * @param {Object} accountData - 账号数据 * @returns {Promise<Object>} 添加结果 */ async addAccount(accountData) { const { username, phone, email, nickname, proxyId, fingerprintId } = accountData; try { // 检查用户名是否已存在 const existingAccount = await query( 'SELECT id FROM idea_xiaohongshu_accounts WHERE username = ?', [username] ); if (existingAccount.length > 0) { throw new Error('用户名已存在'); } // 验证代理和指纹是否存在 if (proxyId) { const proxy = await query( 'SELECT id FROM idea_xiaohongshu_proxies WHERE id = ? AND status = "active"', [proxyId] ); if (proxy.length === 0) { throw new Error('代理不存在或不可用'); } } if (fingerprintId) { const fingerprint = await query( 'SELECT id FROM idea_xiaohongshu_fingerprints WHERE id = ? AND status = "active"', [fingerprintId] ); if (fingerprint.length === 0) { throw new Error('指纹不存在或不可用'); } } // 插入新账号 const result = await query( `INSERT INTO idea_xiaohongshu_accounts (username, phone, email, nickname, proxy_id, fingerprint_id, status, login_status) VALUES (?, ?, ?, ?, ?, ?, 'active', false)`, [username, phone, email, nickname, proxyId, fingerprintId] ); const accountId = result.insertId; logger.info(`添加账号成功: ${username} (ID: ${accountId})`); return { id: accountId, username, status: 'active', loginStatus: false, createdAt: new Date() }; } catch (error) { logger.error('添加账号失败:', error); throw error; } } /** * 删除账号 * @param {number} accountId - 账号ID * @returns {Promise<Object>} 删除结果 */ async removeAccount(accountId) { try { // 检查账号是否存在 const account = await query( 'SELECT id, username FROM idea_xiaohongshu_accounts WHERE id = ?', [accountId] ); if (account.length === 0) { throw new Error('账号不存在'); } const username = account[0].username; // 如果账号已登录,先登出 if (this.loginSessions.has(accountId)) { await this.logoutAccount(accountId); } // 删除账号 await query('DELETE FROM idea_xiaohongshu_accounts WHERE id = ?', [accountId]); logger.info(`删除账号成功: ${username} (ID: ${accountId})`); return { id: accountId, username, deleted: true }; } catch (error) { logger.error('删除账号失败:', error); throw error; } } /** * 登录账号 * @param {Object} loginData - 登录数据 * @returns {Promise<Object>} 登录结果 */ async loginAccount(loginData) { const { username, password, verificationCode, proxyId, fingerprintId } = loginData; try { // 获取账号信息 const account = await query( 'SELECT * FROM idea_xiaohongshu_accounts WHERE username = ?', [username] ); if (account.length === 0) { throw new Error('账号不存在'); } const accountData = account[0]; const accountId = accountData.id; // 检查是否已登录 if (this.loginSessions.has(accountId)) { const session = this.loginSessions.get(accountId); if (session.isValid()) { return { success: true, accountId, message: '账号已登录' }; } else { // 会话过期,移除旧会话 this.loginSessions.delete(accountId); } } // 获取代理配置 let proxyConfig = null; if (proxyId || accountData.proxy_id) { const proxyManager = new ProxyManager(); proxyConfig = await proxyManager.getProxy(proxyId || accountData.proxy_id); } // 获取指纹配置 let fingerprintConfig = null; if (fingerprintId || accountData.fingerprint_id) { const fingerprintManager = new FingerprintManager(); fingerprintConfig = await fingerprintManager.getFingerprint(fingerprintId || accountData.fingerprint_id); } // 使用浏览器进行登录 const browserManager = new BrowserManager(); const loginResult = await browserManager.loginXiaohongshu({ username, password, verificationCode, proxy: proxyConfig, fingerprint: fingerprintConfig }); if (loginResult.success) { // 更新账号状态 await query( `UPDATE idea_xiaohongshu_accounts SET login_status = true, last_login_time = NOW(), cookies_encrypted = ?, user_agent = ? WHERE id = ?`, [encrypt(JSON.stringify(loginResult.cookies)), loginResult.userAgent, accountId] ); // 保存登录会话 this.loginSessions.set(accountId, new LoginSession({ accountId, username, cookies: loginResult.cookies, userAgent: loginResult.userAgent, expiresAt: Date.now() + 24 * 60 * 60 * 1000 // 24小时 })); logger.info(`账号登录成功: ${username} (ID: ${accountId})`); return { success: true, accountId, message: '登录成功' }; } else { throw new Error(loginResult.error || '登录失败'); } } catch (error) { logger.error('账号登录失败:', error); if (error.message.includes('验证码')) { throw new Error('需要验证码,请检查手机或邮箱'); } if (error.message.includes('密码错误')) { throw new Error('用户名或密码错误'); } if (error.message.includes('账号被封')) { // 更新账号状态 await query( 'UPDATE idea_xiaohongshu_accounts SET status = "banned" WHERE username = ?', [username] ); throw new Error('该账号已被封禁'); } throw error; } } /** * 登出账号 * @param {number} accountId - 账号ID * @returns {Promise<Object>} 登出结果 */ async logoutAccount(accountId) { try { // 检查账号是否存在 const account = await query( 'SELECT id, username FROM idea_xiaohongshu_accounts WHERE id = ?', [accountId] ); if (account.length === 0) { throw new Error('账号不存在'); } const username = account[0].username; // 移除登录会话 if (this.loginSessions.has(accountId)) { const session = this.loginSessions.get(accountId); // 使用浏览器登出 if (session.cookies) { try { const browserManager = new BrowserManager(); await browserManager.logoutXiaohongshu(session.cookies); } catch (error) { logger.warn('浏览器登出失败:', error); } } this.loginSessions.delete(accountId); } // 更新账号状态 await query( 'UPDATE idea_xiaohongshu_accounts SET login_status = false WHERE id = ?', [accountId] ); logger.info(`账号登出成功: ${username} (ID: ${accountId})`); return { success: true, accountId, message: '登出成功' }; } catch (error) { logger.error('账号登出失败:', error); throw error; } } /** * 检测账号状态 * @param {number} accountId - 账号ID * @returns {Promise<Object>} 状态信息 */ async checkAccountStatus(accountId) { try { // 获取账号信息 const account = await query( 'SELECT * FROM idea_xiaohongshu_accounts WHERE id = ?', [accountId] ); if (account.length === 0) { throw new Error('账号不存在'); } const accountData = account[0]; // 检查登录会话 let isLoggedIn = false; let sessionInfo = null; if (this.loginSessions.has(accountId)) { const session = this.loginSessions.get(accountId); if (session.isValid()) { isLoggedIn = true; sessionInfo = { loginTime: session.loginTime, expiresAt: session.expiresAt }; } else { // 会话过期,移除旧会话 this.loginSessions.delete(accountId); // 更新数据库状态 await query( 'UPDATE idea_xiaohongshu_accounts SET login_status = false WHERE id = ?', [accountId] ); } } // 如果数据库显示已登录但会话不存在,更新数据库 if (accountData.login_status && !isLoggedIn) { await query( 'UPDATE idea_xiaohongshu_accounts SET login_status = false WHERE id = ?', [accountId] ); } return { accountId, username: accountData.username, status: accountData.status, isLoggedIn, lastLoginTime: accountData.last_login_time, sessionInfo, proxy: accountData.proxy_id ? { id: accountData.proxy_id } : null, fingerprint: accountData.fingerprint_id ? { id: accountData.fingerprint_id } : null }; } catch (error) { logger.error('检测账号状态失败:', error); throw error; } } /** * 更新账号状态 * @param {number} accountId - 账号ID * @param {string} status - 状态 * @param {string} reason - 原因 */ async updateAccountStatus(accountId, status, reason = '') { try { await query( 'UPDATE idea_xiaohongshu_accounts SET status = ? WHERE id = ?', [status, accountId] ); logger.info(`更新账号状态: ${accountId} -> ${status}`, { reason }); } catch (error) { logger.error('更新账号状态失败:', error); throw error; } } /** * 获取登录会话 * @param {number} accountId - 账号ID * @returns {Object} 会话信息 */ getLoginSession(accountId) { return this.loginSessions.get(accountId); } /** * 手机号脱敏 * @param {string} phone - 手机号 * @returns {string} 脱敏后的手机号 */ maskPhone(phone) { if (!phone || phone.length < 7) { return phone; } return phone.substring(0, 3) + '****' + phone.substring(7); } /** * 邮箱脱敏 * @param {string} email - 邮箱 * @returns {string} 脱敏后的邮箱 */ maskEmail(email) { if (!email || !email.includes('@')) { return email; } const [localPart, domain] = email.split('@'); if (localPart.length <= 3) { return localPart + '@' + domain; } return localPart.substring(0, 3) + '***@' + domain; } } /** * 登录会话类 */ class LoginSession { constructor({ accountId, username, cookies, userAgent, expiresAt }) { this.accountId = accountId; this.username = username; this.cookies = cookies; this.userAgent = userAgent; this.loginTime = Date.now(); this.expiresAt = expiresAt; } /** * 检查会话是否有效 * @returns {boolean} 是否有效 */ isValid() { return Date.now() < this.expiresAt; } /** * 刷新会话 */ refresh() { this.expiresAt = Date.now() + 24 * 60 * 60 * 1000; // 延长24小时 } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/billyangbc/xiaohongshu-mcp-nodejs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server