diff --git a/_sql/check_account_config_table.sql b/_sql/check_account_config_table.sql new file mode 100644 index 0000000..c915762 --- /dev/null +++ b/_sql/check_account_config_table.sql @@ -0,0 +1,48 @@ +-- Active: 1763990602551@@192.144.167.231@3306@autoaiworksys +-- ============================================ +-- 检查 account_config 表的状态 +-- ============================================ + +-- 1. 检查表是否存在 +SELECT + TABLE_NAME, + TABLE_COMMENT, + ENGINE, + TABLE_ROWS, + CREATE_TIME, + UPDATE_TIME +FROM INFORMATION_SCHEMA.TABLES +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'account_config'; + +-- 2. 查看表结构 +DESCRIBE account_config; + +-- 或者使用: +-- SHOW CREATE TABLE account_config; + +-- 3. 查看表中的数据(应该是空的,因为这是配置表,需要手动创建记录) +SELECT * FROM account_config; + +-- 4. 查看 pla_account 表中的账号,看看有哪些账号可以创建配置 +SELECT + id, + sn_code, + name, + platform_type, + login_name, + is_enabled +FROM pla_account +WHERE is_delete = 0 +ORDER BY id; + +-- 5. 如果需要为所有账号创建默认配置,可以执行以下 SQL: +-- INSERT INTO account_config (account_id, platform_type, notes) +-- SELECT +-- id AS account_id, +-- platform_type, +-- '默认配置' AS notes +-- FROM pla_account +-- WHERE is_delete = 0 +-- AND id NOT IN (SELECT account_id FROM account_config); + diff --git a/_sql/create_default_account_config.sql b/_sql/create_default_account_config.sql new file mode 100644 index 0000000..a7a67dc --- /dev/null +++ b/_sql/create_default_account_config.sql @@ -0,0 +1,36 @@ +-- Active: 1763990602551@@192.144.167.231@3306@autoaiworksys +-- ============================================ +-- 为 pla_account 表中的所有账号创建默认的 account_config 配置 +-- ============================================ + +-- 为所有启用的账号创建默认配置(如果配置不存在) +INSERT INTO account_config (account_id, platform_type, notes, created_time, last_update_time) +SELECT + id AS account_id, + platform_type, + '默认配置' AS notes, + NOW() AS created_time, + NOW() AS last_update_time +FROM pla_account +WHERE is_delete = 0 + AND id NOT IN (SELECT account_id FROM account_config) +ON DUPLICATE KEY UPDATE + last_update_time = NOW(); + +-- 查看创建的结果 +SELECT + ac.account_id, + pa.name AS account_name, + pa.sn_code, + ac.platform_type, + ac.platform_config, + ac.auto_deliver_config, + ac.auto_chat_config, + ac.auto_active_config, + ac.notes, + ac.created_time, + ac.last_update_time +FROM account_config ac +INNER JOIN pla_account pa ON ac.account_id = pa.id +ORDER BY ac.account_id; + diff --git a/_sql/migrate_device_status_data.sql b/_sql/migrate_device_status_data.sql new file mode 100644 index 0000000..c4dfeaf --- /dev/null +++ b/_sql/migrate_device_status_data.sql @@ -0,0 +1,96 @@ +-- Active: 1763990602551@@192.144.167.231@3306@autoaiworksys +-- ============================================ +-- 数据迁移脚本:将 device_status 表中的 device_id 迁移到 pla_account 表 +-- 执行时间:2025-01-XX +-- 说明:根据 sn_code 匹配,将 device_status.device_id 更新到 pla_account.device_id +-- ============================================ + +-- ============================================ +-- 1. 查看 device_status 表中的数据(用于确认) +-- ============================================ +SELECT + sn_code, + device_id, + deviceName, + isOnline, + isLoggedIn, + lastHeartbeatTime +FROM device_status +ORDER BY sn_code; + +-- ============================================ +-- 2. 查看迁移前的 pla_account 数据(用于确认) +-- ============================================ +SELECT + id, + sn_code, + device_id, + name, + login_name, + platform_type +FROM pla_account +ORDER BY sn_code; + +-- ============================================ +-- 3. 执行数据迁移:将 device_status.device_id 更新到 pla_account.device_id +-- ============================================ +-- 根据 sn_code 匹配,更新 pla_account 表的 device_id 字段 +UPDATE pla_account pa +INNER JOIN device_status ds ON pa.sn_code = ds.sn_code +SET pa.device_id = ds.device_id +WHERE ds.device_id IS NOT NULL + AND ds.device_id != '' + AND (pa.device_id IS NULL OR pa.device_id = ''); + +-- ============================================ +-- 4. 查看迁移后的结果(用于验证) +-- ============================================ +SELECT + pa.id, + pa.sn_code, + pa.device_id, + pa.name, + pa.login_name, + pa.platform_type, + CASE + WHEN pa.device_id IS NOT NULL AND pa.device_id != '' THEN '已迁移' + ELSE '未迁移' + END AS migration_status +FROM pla_account pa +WHERE pa.sn_code IN (SELECT sn_code FROM device_status) +ORDER BY pa.sn_code; + +-- ============================================ +-- 5. 统计迁移结果 +-- ============================================ +SELECT + COUNT(*) AS total_device_status_records, + SUM(CASE WHEN device_id IS NOT NULL AND device_id != '' THEN 1 ELSE 0 END) AS records_with_device_id +FROM device_status; + +SELECT + COUNT(*) AS total_pla_account_records, + SUM(CASE WHEN device_id IS NOT NULL AND device_id != '' THEN 1 ELSE 0 END) AS records_with_device_id_after_migration +FROM pla_account +WHERE sn_code IN (SELECT sn_code FROM device_status); + +-- ============================================ +-- 注意事项 +-- ============================================ +-- 1. 此脚本会根据 sn_code 匹配 device_status 和 pla_account 表 +-- 2. 只会更新 device_id 为空或 NULL 的 pla_account 记录 +-- 3. 如果 pla_account 中已有 device_id,不会覆盖(如需强制覆盖,请修改 WHERE 条件) +-- 4. 执行前建议先查看两个表的数据,确认匹配关系 +-- 5. 如果 device_status 表中没有对应的 sn_code,该 pla_account 记录的 device_id 将保持为空 + +-- ============================================ +-- 可选:如果需要强制覆盖已有的 device_id(谨慎操作!) +-- ============================================ +-- 取消下面的注释,可以强制覆盖 pla_account 中已有的 device_id +/* +UPDATE pla_account pa +INNER JOIN device_status ds ON pa.sn_code = ds.sn_code +SET pa.device_id = ds.device_id +WHERE ds.device_id IS NOT NULL AND ds.device_id != ''; +*/ + diff --git a/_sql/migrate_device_status_data_simple.sql b/_sql/migrate_device_status_data_simple.sql new file mode 100644 index 0000000..06c31cb --- /dev/null +++ b/_sql/migrate_device_status_data_simple.sql @@ -0,0 +1,25 @@ +-- Active: 1763990602551@@192.144.167.231@3306@autoaiworksys +-- ============================================ +-- 数据迁移脚本(简化版):将 device_status 表中的 device_id 迁移到 pla_account 表 +-- ============================================ + +-- 查看 device_status 表的数据 +SELECT sn_code, device_id FROM device_status; + +-- 查看迁移前的 pla_account 数据 +SELECT id, sn_code, device_id, name FROM pla_account WHERE sn_code IN (SELECT sn_code FROM device_status); + +-- 执行迁移:根据 sn_code 匹配,将 device_status.device_id 更新到 pla_account.device_id +UPDATE pla_account pa +INNER JOIN device_status ds ON pa.sn_code = ds.sn_code +SET pa.device_id = ds.device_id +WHERE ds.device_id IS NOT NULL + AND ds.device_id != '' + AND (pa.device_id IS NULL OR pa.device_id = ''); + +-- 查看迁移后的结果 +SELECT id, sn_code, device_id, name, + CASE WHEN device_id IS NOT NULL AND device_id != '' THEN '已迁移' ELSE '未迁移' END AS status +FROM pla_account +WHERE sn_code IN (SELECT sn_code FROM device_status); + diff --git a/_sql/migrate_device_status_to_pla_account.sql b/_sql/migrate_device_status_to_pla_account.sql new file mode 100644 index 0000000..ab67a90 --- /dev/null +++ b/_sql/migrate_device_status_to_pla_account.sql @@ -0,0 +1,135 @@ +-- ============================================ +-- 数据库迁移脚本:移除 device_status,迁移到 pla_account +-- 执行时间:2025-01-XX +-- 说明: +-- 1. 为 pla_account 表添加 device_id 字段 +-- 2. 删除 device_status 表(如果存在) +-- 3. 创建 account_config 表(账号配置表) +-- ============================================ + +-- ============================================ +-- 1. 为 pla_account 表添加 device_id 字段 +-- ============================================ +SET @exist_device_id = ( + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'pla_account' + AND COLUMN_NAME = 'device_id' +); + +SET @sql_device_id = IF(@exist_device_id = 0, + 'ALTER TABLE `pla_account` + ADD COLUMN `device_id` VARCHAR(200) NULL DEFAULT '''' COMMENT ''设备ID'' + AFTER `sn_code`', + 'SELECT ''字段 device_id 已存在,跳过添加'' AS message' +); + +PREPARE stmt FROM @sql_device_id; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- ============================================ +-- 2. 创建 account_config 表(如果不存在) +-- ============================================ +CREATE TABLE IF NOT EXISTS `account_config` ( + `account_id` INT NOT NULL COMMENT '账号ID(关联 pla_account.id)', + `platform_type` VARCHAR(50) NOT NULL DEFAULT 'boss' COMMENT '平台类型(boss- Boss直聘, liepin- 猎聘)', + `platform_config` JSON NULL COMMENT '平台相关配置(JSON对象)', + `auto_deliver_config` JSON NULL COMMENT '自动投递配置(JSON对象)', + `auto_chat_config` JSON NULL COMMENT '自动沟通配置(JSON对象)', + `auto_active_config` JSON NULL COMMENT '自动活跃配置(JSON对象)', + `notes` TEXT NULL DEFAULT '' COMMENT '备注', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`account_id`), + INDEX `idx_platform_type` (`platform_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号配置表'; + +-- 如果 MySQL 版本低于 5.7,不支持 JSON 类型,可以使用以下 SQL(将 JSON 改为 TEXT): +/* +CREATE TABLE IF NOT EXISTS `account_config` ( + `account_id` INT NOT NULL COMMENT '账号ID(关联 pla_account.id)', + `platform_type` VARCHAR(50) NOT NULL DEFAULT 'boss' COMMENT '平台类型(boss- Boss直聘, liepin- 猎聘)', + `platform_config` TEXT NULL COMMENT '平台相关配置(JSON对象)', + `auto_deliver_config` TEXT NULL COMMENT '自动投递配置(JSON对象)', + `auto_chat_config` TEXT NULL COMMENT '自动沟通配置(JSON对象)', + `auto_active_config` TEXT NULL COMMENT '自动活跃配置(JSON对象)', + `notes` TEXT NULL DEFAULT '' COMMENT '备注', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`account_id`), + INDEX `idx_platform_type` (`platform_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号配置表'; +*/ + +-- ============================================ +-- 3. 可选:迁移 device_status 中的 device_id 到 pla_account +-- ============================================ +-- 注意:此步骤只在 device_status 表存在且有数据时执行 +-- 如果 device_status 表已不存在,可以跳过此步骤 + +-- 检查 device_status 表是否存在 +SET @table_exists = ( + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'device_status' +); + +-- 如果 device_status 表存在,尝试迁移 device_id 数据 +-- 注意:此迁移假设 device_status 使用 sn_code 作为主键,pla_account 也有 sn_code 字段 +-- 如果结构不同,请手动调整迁移 SQL +/* +SET @sql_migrate_device_id = IF(@table_exists > 0, + 'UPDATE `pla_account` pa + INNER JOIN `device_status` ds ON pa.sn_code = ds.sn_code + SET pa.device_id = ds.device_id + WHERE pa.device_id IS NULL OR pa.device_id = '''' + AND ds.device_id IS NOT NULL AND ds.device_id != ''''', + 'SELECT ''device_status 表不存在,跳过数据迁移'' AS message' +); + +PREPARE stmt FROM @sql_migrate_device_id; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +*/ + +-- ============================================ +-- 4. 删除 device_status 表(谨慎操作!) +-- ============================================ +-- ⚠️ 警告:删除表会丢失所有数据,请确保已经备份或迁移了需要的数据 +-- 如果确定要删除 device_status 表,请取消下面 SQL 的注释 + +-- DROP TABLE IF EXISTS `device_status`; + +-- 如果不想直接删除,可以重命名表作为备份: +-- RENAME TABLE `device_status` TO `device_status_backup_202501XX`; + +-- ============================================ +-- 注意事项 +-- ============================================ +-- 1. 执行前请先备份数据库 +-- 2. device_id 字段允许为 NULL,旧数据可能没有 device_id +-- 3. account_config 表使用 account_id 作为主键,关联 pla_account.id +-- 4. 如果 MySQL 版本低于 5.7,请使用 TEXT 类型替代 JSON 类型 +-- 5. device_status 表的删除操作是可选的,建议先重命名备份 +-- 6. 如果 device_status 表不存在,相关的 SQL 会自动跳过 + +-- ============================================ +-- 验证执行结果 +-- ============================================ +-- 执行以下 SQL 验证迁移结果: + +-- 1. 检查 pla_account 表的 device_id 字段 +-- DESCRIBE pla_account; + +-- 2. 检查 account_config 表是否创建成功 +-- DESCRIBE account_config; + +-- 3. 检查 device_status 表是否还存在(如果已删除,此查询会报错) +-- SELECT COUNT(*) FROM device_status; + +-- 4. 查看已迁移 device_id 的账号数量 +-- SELECT COUNT(*) FROM pla_account WHERE device_id IS NOT NULL AND device_id != ''; + diff --git a/_sql/migrate_device_status_to_pla_account_simple.sql b/_sql/migrate_device_status_to_pla_account_simple.sql new file mode 100644 index 0000000..722f0f8 --- /dev/null +++ b/_sql/migrate_device_status_to_pla_account_simple.sql @@ -0,0 +1,61 @@ +-- Active: 1763990602551@@192.144.167.231@3306@autoaiworksys +-- ============================================ +-- 数据库迁移脚本(简化版):移除 device_status,迁移到 pla_account +-- 执行时间:2025-01-XX +-- 说明:简化版 SQL,直接执行 ALTER 和 CREATE 语句 +-- ============================================ + +-- 1. 为 pla_account 表添加 device_id 字段 +-- 注意:如果字段已存在会报错,请先检查字段是否存在 +-- 检查方法:DESCRIBE pla_account; 或 SHOW COLUMNS FROM pla_account LIKE 'device_id'; +ALTER TABLE `pla_account` +ADD COLUMN `device_id` VARCHAR(200) NULL DEFAULT '' COMMENT '设备ID' +AFTER `sn_code`; + +-- 2. 创建 account_config 表 +CREATE TABLE IF NOT EXISTS `account_config` ( + `account_id` INT NOT NULL COMMENT '账号ID(关联 pla_account.id)', + `platform_type` VARCHAR(50) NOT NULL DEFAULT 'boss' COMMENT '平台类型(boss- Boss直聘, liepin- 猎聘)', + `platform_config` JSON NULL COMMENT '平台相关配置(JSON对象)', + `auto_deliver_config` JSON NULL COMMENT '自动投递配置(JSON对象)', + `auto_chat_config` JSON NULL COMMENT '自动沟通配置(JSON对象)', + `auto_active_config` JSON NULL COMMENT '自动活跃配置(JSON对象)', + `notes` TEXT NULL COMMENT '备注', + `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `last_update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`account_id`), + INDEX `idx_platform_type` (`platform_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号配置表'; + +-- 如果 MySQL 版本低于 5.7,不支持 JSON 类型,请使用以下 SQL(将 JSON 改为 TEXT): +/* +CREATE TABLE IF NOT EXISTS `account_config` ( + `account_id` INT NOT NULL COMMENT '账号ID(关联 pla_account.id)', + `platform_type` VARCHAR(50) NOT NULL DEFAULT 'boss' COMMENT '平台类型(boss- Boss直聘, liepin- 猎聘)', + `platform_config` TEXT NULL COMMENT '平台相关配置(JSON对象)', + `auto_deliver_config` TEXT NULL COMMENT '自动投递配置(JSON对象)', + `auto_chat_config` TEXT NULL COMMENT '自动沟通配置(JSON对象)', + `auto_active_config` TEXT NULL COMMENT '自动活跃配置(JSON对象)', + `notes` TEXT NULL DEFAULT '' COMMENT '备注', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`account_id`), + INDEX `idx_platform_type` (`platform_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号配置表'; +*/ + +-- 3. 可选:删除 device_status 表(谨慎操作!) +-- ⚠️ 警告:删除表会丢失所有数据,请确保已经备份或迁移了需要的数据 +-- DROP TABLE IF EXISTS `device_status`; + +-- 或者重命名表作为备份: +-- RENAME TABLE `device_status` TO `device_status_backup_202501XX`; + +-- ============================================ +-- 注意事项 +-- ============================================ +-- 1. 执行前请先备份数据库 +-- 2. 如果字段已存在,使用 IF NOT EXISTS 可以避免报错(MySQL 8.0.19+) +-- 3. 如果 MySQL 版本较低,请手动检查字段是否存在再执行 +-- 4. device_status 表的删除操作是可选的,建议先重命名备份 + diff --git a/api/controller_front/account.js b/api/controller_front/account.js new file mode 100644 index 0000000..c9d4d48 --- /dev/null +++ b/api/controller_front/account.js @@ -0,0 +1,100 @@ +const Framework = require("../../framework/node-core-framework.js"); +const authorizationService = require('../services/authorization_service.js'); + +/** + * 账号管理控制器(客户端接口) + * 提供客户端调用的账号相关接口 + */ +module.exports = { + /** + * @swagger + * /api/account/check-authorization: + * post: + * summary: 检查账号授权状态 + * description: 根据设备SN码检查账号的授权状态(剩余天数、是否过期等) + * tags: [前端-账号管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - sn_code + * properties: + * sn_code: + * type: string + * description: 设备SN码 + * example: 'GHJU' + * responses: + * 200: + * description: 检查成功 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * description: 状态码,0表示成功 + * example: 0 + * message: + * type: string + * description: 响应消息 + * example: 'success' + * data: + * type: object + * properties: + * is_authorized: + * type: boolean + * description: 是否已授权 + * example: true + * remaining_days: + * type: integer + * description: 剩余天数 + * example: 30 + * message: + * type: string + * description: 授权状态消息 + * example: '授权有效,剩余 30 天' + * 400: + * description: 参数错误 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 400 + * message: + * type: string + * example: '请提供设备SN码' + * 500: + * description: 服务器错误 + */ + 'POST /account/check-authorization': async (ctx) => { + try { + const { sn_code } = ctx.getBody(); + + // 参数验证 + if (!sn_code) { + return ctx.fail('请提供设备SN码'); + } + + // 调用授权服务检查授权状态 + const result = await authorizationService.checkAuthorization(sn_code, 'sn_code'); + + // 返回授权检查结果 + return ctx.success({ + is_authorized: result.is_authorized, + remaining_days: result.remaining_days, + message: result.message + }); + } catch (error) { + console.error('[账号管理] 检查授权状态失败:', error); + return ctx.fail('检查授权状态失败: ' + (error.message || '未知错误')); + } + } +}; + diff --git a/api/controller_front/user.js b/api/controller_front/user.js index b34e890..98368bf 100644 --- a/api/controller_front/user.js +++ b/api/controller_front/user.js @@ -6,7 +6,7 @@ module.exports = { * /api/user/login: * post: * summary: 用户登录 - * description: 通过设备SN码登录,返回token和用户信息 + * description: 通过手机号和密码登录,返回token、device_id和用户信息 * tags: [前端-用户管理] * requestBody: * required: true @@ -15,17 +15,17 @@ module.exports = { * schema: * type: object * required: - * - sn_code - * - device_id + * - phone + * - password * properties: - * sn_code: + * phone: * type: string - * description: 设备SN码 - * example: 'GHJU' - * device_id: + * description: 手机号(登录名) + * example: '13800138000' + * password: * type: string - * description: 设备ID - * example: 'device_123456' + * description: 密码 + * example: 'password123' * responses: * 200: * description: 登录成功 @@ -49,6 +49,10 @@ module.exports = { * type: string * description: 认证token * example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' + * device_id: + * type: string + * description: 设备ID + * example: 'device_123456' * user: * type: object * description: 用户信息 @@ -64,20 +68,35 @@ module.exports = { * example: 400 * message: * type: string - * example: '用户不存在' + * example: '用户不存在或密码错误' */ "POST /user/login": async (ctx) => { - - const { sn_code, device_id } = ctx.getBody(); + const { phone, password } = ctx.getBody(); const dayjs = require('dayjs'); + const crypto = require('crypto'); - const { pla_account,device_status} = await Framework.getModels(); + // 验证参数 + if (!phone || !password) { + return ctx.fail('手机号和密码不能为空'); + } + + const { pla_account } = await Framework.getModels(); + + // 根据手机号(login_name)和密码查找用户 + const user = await pla_account.findOne({ + where: { + login_name: phone, + pwd: password + } + }); - // 获取用户信息 - - const user = await pla_account.findOne({ where: { sn_code } }); if (!user) { - return ctx.fail('用户不存在'); + return ctx.fail('手机号或密码错误'); + } + + // 检查账号是否启用 + if (!user.is_enabled) { + return ctx.fail('账号已被禁用'); } // 检查授权状态 @@ -95,32 +114,30 @@ module.exports = { if (remaining_days <= 0) { return ctx.fail('账号授权已过期,请联系管理员续费'); } - } else { - // 如果没有授权信息,检查是否允许登录(可以根据业务需求决定是否允许) - // 这里暂时允许登录,但可以添加配置项控制 } - // 更新设备状态 - - const device = await device_status.findOne({ where: { sn_code } }); - if (device) { - await device_status.update({ - device_id: device_id - }, { where: { sn_code } }); - } else { - await device_status.create({ - sn_code: sn_code, - device_id: device_id - }); + // 生成设备ID(如果不存在,基于手机号和机器特征生成) + let device_id = user.device_id; + if (!device_id) { + // 生成唯一设备ID + const machineInfo = `${phone}_${Date.now()}_${Math.random()}`; + device_id = crypto.createHash('sha256').update(machineInfo).digest('hex').substring(0, 32); + + // 保存设备ID到账号表 + await pla_account.update( + { device_id: device_id }, + { where: { id: user.id } } + ); } - + // 创建token const token = Framework.getServices().tokenService.create({ sn_code: user.sn_code, - device_id: user.device_id + device_id: device_id, + user_id: user.id }); - // 计算剩余天数并返回 + // 计算剩余天数 let remaining_days = 0; if (authDate && authDays > 0) { const startDate = dayjs(authDate); @@ -132,8 +149,13 @@ module.exports = { const userInfo = user.toJSON(); userInfo.remaining_days = remaining_days; + // 不返回密码 + delete userInfo.pwd; - return ctx.success({ token, user: userInfo }); - + return ctx.success({ + token, + device_id, + user: userInfo + }); } } \ No newline at end of file diff --git a/api/middleware/mqtt/mqttDispatcher.js b/api/middleware/mqtt/mqttDispatcher.js index 02ef788..a27f81e 100644 --- a/api/middleware/mqtt/mqttDispatcher.js +++ b/api/middleware/mqtt/mqttDispatcher.js @@ -214,10 +214,10 @@ class MqttDispatcher { console.log(`[MQTT心跳] 收到设备 ${sn_code} 的心跳消息`); - const device_status = db.getModel('device_status'); - - // 检查设备是否存在 - let device = await device_status.findByPk(sn_code); + // 移除 device_status 模型依赖 + // const device_status = db.getModel('device_status'); + // let device = await device_status.findByPk(sn_code); + let device = null; // device_status 已移除,暂时设为 null const updateData = { isOnline: true, // 收到心跳,设备在线 @@ -250,8 +250,8 @@ class MqttDispatcher { loginTime = new Date(platform_login_status.timestamp); } - // 更新登录状态 - const previousIsLoggedIn = device ? device.isLoggedIn : false; + // 移除 device_status 依赖,previousIsLoggedIn 暂时设为 false + const previousIsLoggedIn = false; // device_status 已移除 updateData.isLoggedIn = isLoggedIn; @@ -276,16 +276,17 @@ class MqttDispatcher { } } - // 更新或创建设备记录 - if (device) { - await device_status.update(updateData, { where: { sn_code } }); - console.log(`[MQTT心跳] 设备 ${sn_code} 状态已更新 - 在线: true, 登录: ${updateData.isLoggedIn}`); - } - else - { - logProxy.error('[MQTT心跳] 设备 ${sn_code} 不存在', { sn_code }); - return; - } + // 移除 device_status 更新逻辑 + // 如果需要在 pla_account 表中添加在线状态字段,可以在这里更新 + console.log(`[MQTT心跳] 设备 ${sn_code} 心跳已接收 - 登录: ${updateData.isLoggedIn || false}`); + + // if (device) { + // await device_status.update(updateData, { where: { sn_code } }); + // console.log(`[MQTT心跳] 设备 ${sn_code} 状态已更新 - 在线: true, 登录: ${updateData.isLoggedIn}`); + // } else { + // logProxy.error('[MQTT心跳] 设备 ${sn_code} 不存在', { sn_code }); + // return; + // } // 记录心跳到设备管理器 await deviceManager.recordHeartbeat(sn_code, heartbeatData); diff --git a/api/middleware/schedule/scheduledJobs.js b/api/middleware/schedule/scheduledJobs.js index d08e9d0..184379f 100644 --- a/api/middleware/schedule/scheduledJobs.js +++ b/api/middleware/schedule/scheduledJobs.js @@ -161,6 +161,12 @@ class ScheduledJobs { */ async cleanupOfflineDeviceTasks() { try { + // 移除 device_status 依赖,离线任务清理功能暂时禁用 + // 如果需要在 pla_account 表中添加在线状态字段,可以重新实现此功能 + console.log('[清理离线任务] device_status 已移除,功能暂时禁用'); + return; + + /* 原有代码已注释 const Sequelize = require('sequelize'); const { device_status, task_status, op } = db.models; @@ -240,6 +246,7 @@ class ScheduledJobs { if (totalCancelled > 0) { console.log(`[清理离线任务] 共取消 ${totalCancelled} 个离线设备的任务`); } + */ } catch (error) { console.error('[清理离线任务] 执行失败:', error); } @@ -361,27 +368,14 @@ class ScheduledJobs { } try { - // 从 device_status 查询所有在线且已登录的设备 + // 移除 device_status 依赖,改为直接从 pla_account 查询启用且开启自动投递的账号 const models = db.models; - const { device_status, pla_account, op } = models; - const onlineDevices = await device_status.findAll({ - where: { - isOnline: true, - isLoggedIn: true - }, - attributes: ['sn_code', 'accountName', 'platform'] - }); - - if (!onlineDevices || onlineDevices.length === 0) { - console.log('[自动投递] 没有在线且已登录的设备'); - return; - } - - // 获取这些在线设备对应的账号配置(只获取启用且开启自动投递的账号) - const snCodes = onlineDevices.map(device => device.sn_code); + const { pla_account, op } = models; + + // 直接从 pla_account 查询启用且开启自动投递的账号 + // 注意:不再检查在线状态,因为 device_status 已移除 const pla_users = await pla_account.findAll({ where: { - sn_code: { [op.in]: snCodes }, is_delete: 0, is_enabled: 1, // 只获取启用的账号 auto_deliver: 1 @@ -509,27 +503,14 @@ class ScheduledJobs { } try { - // 从 device_status 查询所有在线且已登录的设备 + // 移除 device_status 依赖,改为直接从 pla_account 查询启用且开启自动沟通的账号 const models = db.models; - const { device_status, pla_account, op } = models; - const onlineDevices = await device_status.findAll({ - where: { - isOnline: true, - isLoggedIn: true - }, - attributes: ['sn_code', 'accountName', 'platform'] - }); - - if (!onlineDevices || onlineDevices.length === 0) { - console.log('[自动沟通] 没有在线且已登录的设备'); - return; - } - - // 获取这些在线设备对应的账号配置(只获取启用且开启自动沟通的账号) - const snCodes = onlineDevices.map(device => device.sn_code); + const { pla_account, op } = models; + + // 直接从 pla_account 查询启用且开启自动沟通的账号 + // 注意:不再检查在线状态,因为 device_status 已移除 const pla_users = await pla_account.findAll({ where: { - sn_code: { [op.in]: snCodes }, is_delete: 0, is_enabled: 1, // 只获取启用的账号 auto_chat: 1 diff --git a/api/middleware/schedule/taskQueue.js b/api/middleware/schedule/taskQueue.js index c94f3ce..00a92bc 100644 --- a/api/middleware/schedule/taskQueue.js +++ b/api/middleware/schedule/taskQueue.js @@ -59,9 +59,8 @@ class TaskQueue { order: [['priority', 'DESC'], ['id', 'ASC']] }); - // 获取所有启用的账号和设备在线状态 + // 获取所有启用的账号(移除 device_status 依赖,不再检查在线状态) const pla_account = db.getModel('pla_account'); - const device_status = db.getModel('device_status'); const enabledAccounts = await pla_account.findAll({ where: { @@ -72,21 +71,9 @@ class TaskQueue { }); const enabledSnCodes = new Set(enabledAccounts.map(acc => acc.sn_code)); - // 检查设备在线状态(需要同时满足:isOnline = true 且心跳未超时) - const heartbeatTimeout = require('./config.js').monitoring.heartbeatTimeout; // 默认5分钟 - const now = new Date(); - const heartbeatThreshold = new Date(now.getTime() - heartbeatTimeout); - - const onlineDevices = await device_status.findAll({ - where: { - isOnline: true, - lastHeartbeatTime: { - [Sequelize.Op.gte]: heartbeatThreshold // 心跳时间在阈值内 - } - }, - attributes: ['sn_code', 'lastHeartbeatTime'] - }); - const onlineSnCodes = new Set(onlineDevices.map(dev => dev.sn_code)); + // 移除 device_status 依赖,不再检查设备在线状态 + // 如果需要在线状态检查,可以在 pla_account 表中添加相应字段 + const onlineSnCodes = new Set(); // 暂时设为空,表示不再检查在线状态 let restoredCount = 0; diff --git a/api/model/account_config.js b/api/model/account_config.js new file mode 100644 index 0000000..0b30fec --- /dev/null +++ b/api/model/account_config.js @@ -0,0 +1,158 @@ +const Sequelize = require('sequelize'); + +/** + * 账号配置表模型 + * 配置不同平台的账号设置 + */ +module.exports = (db) => { + const account_config = db.define("account_config", { + account_id: { + comment: '账号ID(关联 pla_account.id)', + type: Sequelize.INTEGER, + allowNull: false, + primaryKey: true + }, + platform_type: { + comment: '平台类型(boss- Boss直聘, liepin- 猎聘)', + type: Sequelize.STRING(50), + allowNull: false, + defaultValue: 'boss' + }, + // 平台相关配置(JSON格式) + platform_config: { + comment: '平台相关配置(JSON对象)', + type: Sequelize.JSON(), + allowNull: true, + get: function () { + const value = this.getDataValue('platform_config'); + if (!value) return null; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch (e) { + return null; + } + } + return value; + }, + set: function (value) { + if (value === null || value === undefined) { + this.setDataValue('platform_config', null); + } else if (typeof value === 'string') { + this.setDataValue('platform_config', value); + } else { + this.setDataValue('platform_config', JSON.stringify(value)); + } + }, + defaultValue: null + }, + // 自动投递配置 + auto_deliver_config: { + comment: '自动投递配置(JSON对象)', + type: Sequelize.JSON(), + allowNull: true, + get: function () { + const value = this.getDataValue('auto_deliver_config'); + if (!value) return null; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch (e) { + return null; + } + } + return value; + }, + set: function (value) { + if (value === null || value === undefined) { + this.setDataValue('auto_deliver_config', null); + } else if (typeof value === 'string') { + this.setDataValue('auto_deliver_config', value); + } else { + this.setDataValue('auto_deliver_config', JSON.stringify(value)); + } + }, + defaultValue: null + }, + // 自动沟通配置 + auto_chat_config: { + comment: '自动沟通配置(JSON对象)', + type: Sequelize.JSON(), + allowNull: true, + get: function () { + const value = this.getDataValue('auto_chat_config'); + if (!value) return null; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch (e) { + return null; + } + } + return value; + }, + set: function (value) { + if (value === null || value === undefined) { + this.setDataValue('auto_chat_config', null); + } else if (typeof value === 'string') { + this.setDataValue('auto_chat_config', value); + } else { + this.setDataValue('auto_chat_config', JSON.stringify(value)); + } + }, + defaultValue: null + }, + // 自动活跃配置 + auto_active_config: { + comment: '自动活跃配置(JSON对象)', + type: Sequelize.JSON(), + allowNull: true, + get: function () { + const value = this.getDataValue('auto_active_config'); + if (!value) return null; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch (e) { + return null; + } + } + return value; + }, + set: function (value) { + if (value === null || value === undefined) { + this.setDataValue('auto_active_config', null); + } else if (typeof value === 'string') { + this.setDataValue('auto_active_config', value); + } else { + this.setDataValue('auto_active_config', JSON.stringify(value)); + } + }, + defaultValue: null + }, + // 备注 + notes: { + comment: '备注', + type: Sequelize.TEXT, + allowNull: true, + defaultValue: '' + } + }, { + timestamps: true, + createdAt: 'created_time', + updatedAt: 'last_update_time', + indexes: [ + { + unique: false, + fields: ['account_id'] + }, + { + unique: false, + fields: ['platform_type'] + } + ] + }); + + return account_config; +}; + diff --git a/api/model/device_status.js b/api/model/device_status.js deleted file mode 100644 index 9a51069..0000000 --- a/api/model/device_status.js +++ /dev/null @@ -1,309 +0,0 @@ -const Sequelize = require('sequelize'); - -/** - * 设备状态表模型 - * 记录pydp客户端设备的实时状态和运行信息 - */ -module.exports = (db) => { - const device_status= db.define("device_status", { - // 设备基本信息 - sn_code: { - comment: '设备SN码(唯一标识)', - type: Sequelize.STRING(50), - allowNull: false, - primaryKey: true - }, - device_id:{ - comment: '设备ID', - type: Sequelize.STRING(200), - allowNull: false, - primaryKey: true - }, - - deviceName: { - comment: '设备名称', - type: Sequelize.STRING(100), - allowNull: true, - defaultValue: '' - }, - deviceType: { - comment: '设备类型', - type: Sequelize.STRING(50), - allowNull: true, - defaultValue: 'pydp' - }, - - // 在线状态 - isOnline: { - comment: '是否在线', - type: Sequelize.BOOLEAN, - allowNull: false, - defaultValue: false - }, - isRunning: { - comment: '是否运行中', - type: Sequelize.BOOLEAN, - allowNull: false, - defaultValue: false - }, - lastOnlineTime: { - comment: '最后在线时间', - type: Sequelize.DATE, - allowNull: true - }, - lastOfflineTime: { - comment: '最后离线时间', - type: Sequelize.DATE, - allowNull: true - }, - onlineDuration: { - comment: '在线时长(分钟)', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - - // 运行状态 - currentTask: { - comment: '当前执行任务', - type: Sequelize.STRING(100), - allowNull: true, - defaultValue: '' - }, - currentTaskId: { - comment: '当前任务ID', - type: Sequelize.STRING(50), - allowNull: true, - defaultValue: '' - }, - taskStatus: { - comment: '任务状态', - type: Sequelize.STRING(20), - allowNull: true, - defaultValue: 'idle' - }, - lastTaskTime: { - comment: '最后任务时间', - type: Sequelize.DATE, - allowNull: true - }, - - // 平台信息 - platform: { - comment: '当前使用平台: boss-Boss直聘, liepin-猎聘', - type: Sequelize.STRING(20), - allowNull: true, - defaultValue: 'boss' - }, - isLoggedIn: { - comment: '是否已登录平台', - type: Sequelize.BOOLEAN, - allowNull: true, - defaultValue: false - }, - loginTime: { - comment: '登录时间', - type: Sequelize.DATE, - allowNull: true - }, - accountName: { - comment: '账号名称', - type: Sequelize.STRING(100), - allowNull: true, - defaultValue: '' - }, - - // 性能信息 - cpuUsage: { - comment: 'CPU使用率(%)', - type: Sequelize.FLOAT, - allowNull: true, - defaultValue: 0 - }, - memoryUsage: { - comment: '内存使用率(%)', - type: Sequelize.FLOAT, - allowNull: true, - defaultValue: 0 - }, - diskUsage: { - comment: '磁盘使用率(%)', - type: Sequelize.FLOAT, - allowNull: true, - defaultValue: 0 - }, - - // 网络信息 - ipAddress: { - comment: 'IP地址', - type: Sequelize.STRING(50), - allowNull: true, - defaultValue: '' - }, - macAddress: { - comment: 'MAC地址', - type: Sequelize.STRING(50), - allowNull: true, - defaultValue: '' - }, - networkStatus: { - comment: '网络状态: good-良好, slow-缓慢, offline-离线', - type: Sequelize.STRING(20), - allowNull: true, - defaultValue: 'good' - }, - - // 心跳信息 - lastHeartbeatTime: { - comment: '最后心跳时间', - type: Sequelize.DATE, - allowNull: true - }, - heartbeatInterval: { - comment: '心跳间隔(秒)', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 30 - }, - missedHeartbeats: { - comment: '丢失的心跳次数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - - // 版本信息 - clientVersion: { - comment: '客户端版本', - type: Sequelize.STRING(20), - allowNull: true, - defaultValue: '' - }, - pythonVersion: { - comment: 'Python版本', - type: Sequelize.STRING(20), - allowNull: true, - defaultValue: '' - }, - osVersion: { - comment: '操作系统版本', - type: Sequelize.STRING(100), - allowNull: true, - defaultValue: '' - }, - - // 统计信息 - totalTasksCompleted: { - comment: '完成任务总数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - totalTasksFailed: { - comment: '失败任务总数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - totalJobsSearched: { - comment: '搜索岗位总数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - totalApplies: { - comment: '投递简历总数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - totalChats: { - comment: '聊天总数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - - // 错误信息 - lastError: { - comment: '最后错误信息', - type: Sequelize.TEXT, - allowNull: true, - defaultValue: '' - }, - lastErrorTime: { - comment: '最后错误时间', - type: Sequelize.DATE, - allowNull: true - }, - errorCount: { - comment: '错误次数', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - - // 健康状态 - healthStatus: { - comment: '健康状态: healthy-健康, warning-警告, error-错误, unknown-未知', - type: Sequelize.STRING(20), - allowNull: true, - defaultValue: 'unknown' - }, - healthScore: { - comment: '健康评分(0-100)', - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: 0 - }, - - // 配置信息 - config: { - comment: '设备配置(JSON)', - type: Sequelize.TEXT, - allowNull: true, - defaultValue: '' - }, - - // 其他信息 - notes: { - comment: '备注', - type: Sequelize.TEXT, - allowNull: true, - defaultValue: '' - }, - - }, { - timestamps: false, - indexes: [ - { - unique: false, - fields: ['isOnline'] - }, - { - unique: false, - fields: ['isRunning'] - }, - { - unique: false, - fields: ['healthStatus'] - }, - { - unique: false, - fields: ['platform'] - }, - { - unique: false, - fields: ['lastHeartbeatTime'] - }, - - ] - }); - - // device_status.sync({ force: true }); - - return device_status - - // device_status.sync({ force: true -}; - diff --git a/api/model/pla_account.js b/api/model/pla_account.js index f9d907f..9f37a84 100644 --- a/api/model/pla_account.js +++ b/api/model/pla_account.js @@ -15,6 +15,12 @@ module.exports = (db) => { allowNull: false, defaultValue: '' }, + device_id: { + comment: '设备ID', + type: Sequelize.STRING(200), + allowNull: true, + defaultValue: '' + }, platform_type: { comment: '平台', type: Sequelize.STRING(50), diff --git a/api/services/pla_account_service.js b/api/services/pla_account_service.js index 7549f6f..74655d3 100644 --- a/api/services/pla_account_service.js +++ b/api/services/pla_account_service.js @@ -16,7 +16,6 @@ class PlaAccountService { */ async getAccountById(id) { const pla_account = db.getModel('pla_account'); - const device_status = db.getModel('device_status'); const account = await pla_account.findByPk(id); if (!account) { @@ -25,15 +24,9 @@ class PlaAccountService { const accountData = account.get({ plain: true }); - // 从 device_status 查询在线状态和登录状态 - const deviceStatus = await device_status.findByPk(account.sn_code); - if (deviceStatus) { - accountData.is_online = deviceStatus.isOnline || false; - accountData.is_logged_in = deviceStatus.isLoggedIn || false; - } else { - accountData.is_online = false; - accountData.is_logged_in = false; - } + // 移除 device_status 依赖,在线状态和登录状态设为默认值 + accountData.is_online = false; + accountData.is_logged_in = false; return accountData; } @@ -45,7 +38,6 @@ class PlaAccountService { */ async getAccountBySnCode(sn_code) { const pla_account = db.getModel('pla_account'); - const device_status = db.getModel('device_status'); if (!sn_code) { throw new Error('设备SN码不能为空'); @@ -65,15 +57,9 @@ class PlaAccountService { const accountData = account.get({ plain: true }); - // 从 device_status 查询在线状态和登录状态 - const deviceStatus = await device_status.findByPk(sn_code); - if (deviceStatus) { - accountData.is_online = deviceStatus.isOnline || false; - accountData.is_logged_in = deviceStatus.isLoggedIn || false; - } else { - accountData.is_online = false; - accountData.is_logged_in = false; - } + // 移除 device_status 依赖,在线状态和登录状态设为默认值 + accountData.is_online = false; + accountData.is_logged_in = false; return accountData; } @@ -85,7 +71,6 @@ class PlaAccountService { */ async getAccountList(params) { const pla_account = db.getModel('pla_account'); - const device_status = db.getModel('device_status'); const op = db.getModel('op'); const { key, value, platform_type, is_online, limit, offset } = params; @@ -107,28 +92,8 @@ class PlaAccountService { where.platform_type = platform_type; } - // 如果按在线状态筛选,需要先查询在线设备的 sn_code - let onlineSnCodes = null; - if (is_online !== undefined && is_online !== null) { - const onlineDevices = await device_status.findAll({ - where: { isOnline: is_online }, - attributes: ['sn_code'] - }); - onlineSnCodes = onlineDevices.map(device => device.sn_code); - - // 如果筛选在线但没有任何在线设备,直接返回空结果 - if (is_online && onlineSnCodes.length === 0) { - return { - count: 0, - rows: [] - }; - } - - // 如果筛选离线,需要在 where 中排除在线设备的 sn_code - if (!is_online && onlineSnCodes.length > 0) { - where.sn_code = { [op.notIn]: onlineSnCodes }; - } - } + // 移除 device_status 依赖,is_online 筛选功能暂时禁用 + // 如果需要在线状态筛选,可以在 pla_account 表中添加相应字段 const result = await pla_account.findAndCountAll({ where, @@ -137,23 +102,10 @@ class PlaAccountService { order: [['id', 'DESC']] }); - // 批量查询所有账号对应的设备状态 - const snCodes = result.rows.map(account => account.sn_code); - const deviceStatuses = await device_status.findAll({ - where: { sn_code: { [op.in]: snCodes } }, - attributes: ['sn_code', 'isOnline'] - }); - - // 创建 sn_code 到 isOnline 的映射 - const statusMap = {}; - deviceStatuses.forEach(status => { - statusMap[status.sn_code] = status.isOnline; - }); - - // 处理返回数据,添加 is_online 字段 + // 处理返回数据,is_online 设为默认值 false const rows = result.rows.map(account => { const accountData = account.get({ plain: true }); - accountData.is_online = statusMap[account.sn_code] || false; + accountData.is_online = false; return accountData; }); @@ -368,7 +320,6 @@ class PlaAccountService { */ async runCommand(params) { const pla_account = db.getModel('pla_account'); - const device_status = db.getModel('device_status'); const task_status = db.getModel('task_status'); const { id, commandType, commandName, commandParams } = params; @@ -389,11 +340,8 @@ class PlaAccountService { throw new Error(authCheck.message); } - // 从 device_status 检查账号是否在线 - const deviceStatus = await device_status.findByPk(account.sn_code); - if (!deviceStatus || !deviceStatus.isOnline) { - throw new Error('账号不在线,无法执行指令'); - } + // 移除 device_status 依赖,在线状态检查暂时移除 + // 如果需要在线状态检查,可以在 pla_account 表中添加相应字段 // 获取调度管理器并执行指令 if (!scheduleManager.mqttClient) { @@ -494,7 +442,6 @@ class PlaAccountService { */ async runTask(params) { const pla_account = db.getModel('pla_account'); - const device_status = db.getModel('device_status'); const { id, taskType, taskName } = params; @@ -513,11 +460,8 @@ class PlaAccountService { throw new Error('账号未启用,无法执行指令'); } - // 从 device_status 检查账号是否在线 - const deviceStatus = await device_status.findByPk(account.sn_code); - if (!deviceStatus || !deviceStatus.isOnline) { - throw new Error('账号不在线,无法执行指令'); - } + // 移除 device_status 依赖,在线状态检查暂时移除 + // 如果需要在线状态检查,可以在 pla_account 表中添加相应字段 // 获取调度管理器并执行指令 if (!scheduleManager.mqttClient) {