From 6e5c35f144ac146423a0a4f7ce76518f0cc18d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Mon, 15 Dec 2025 18:36:20 +0800 Subject: [PATCH] 1 --- _script/add_authorization_fields.sql | 53 +++++ _script/add_authorization_fields_simple.sql | 12 + admin/config/index.js | 8 +- admin/src/api/profile/pla_account_server.js | 23 ++ admin/src/views/account/pla_account.vue | 214 ++++++++++++++++++ .../src/views/account/pla_account_detail.vue | 109 ++++++++- admin/src/views/account/pla_account_edit.vue | 105 ++++++++- api/controller_admin/pla_account.js | 152 +++++++++++++ api/controller_front/user.js | 36 ++- api/middleware/mqtt/mqttDispatcher.js | 12 + api/middleware/schedule/command.js | 7 + api/middleware/schedule/taskHandlers.js | 24 ++ api/model/pla_account.js | 33 +++ api/services/authorization_service.js | 80 +++++++ api/services/pla_account_service.js | 7 + 15 files changed, 867 insertions(+), 8 deletions(-) create mode 100644 _script/add_authorization_fields.sql create mode 100644 _script/add_authorization_fields_simple.sql create mode 100644 api/services/authorization_service.js diff --git a/_script/add_authorization_fields.sql b/_script/add_authorization_fields.sql new file mode 100644 index 0000000..448e0be --- /dev/null +++ b/_script/add_authorization_fields.sql @@ -0,0 +1,53 @@ +-- 为 pla_account 表添加授权相关字段 +-- 执行时间:2025-01-XX +-- 说明:添加授权日期和授权天数字段,用于控制账号的使用期限 + +-- 检查并添加授权日期字段 +SET @dbname = DATABASE(); +SET @tablename = 'pla_account'; +SET @columnname = 'authorization_date'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + 'SELECT 1', -- 字段已存在,不执行任何操作 + CONCAT('ALTER TABLE `', @tablename, '` ADD COLUMN `', @columnname, '` DATETIME NULL COMMENT ''授权日期(授权开始时间)'' AFTER `active_actions`;') +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + +-- 检查并添加授权天数字段 +SET @columnname = 'authorization_days'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + 'SELECT 1', -- 字段已存在,不执行任何操作 + CONCAT('ALTER TABLE `', @tablename, '` ADD COLUMN `', @columnname, '` INT(11) NOT NULL DEFAULT 0 COMMENT ''授权天数'' AFTER `authorization_date`;') +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + +-- 验证字段是否添加成功 +SELECT + COLUMN_NAME AS '字段名', + DATA_TYPE AS '数据类型', + IS_NULLABLE AS '允许NULL', + COLUMN_DEFAULT AS '默认值', + COLUMN_COMMENT AS '注释' +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'pla_account' + AND COLUMN_NAME IN ('authorization_date', 'authorization_days') +ORDER BY ORDINAL_POSITION; + diff --git a/_script/add_authorization_fields_simple.sql b/_script/add_authorization_fields_simple.sql new file mode 100644 index 0000000..fac7fb4 --- /dev/null +++ b/_script/add_authorization_fields_simple.sql @@ -0,0 +1,12 @@ +-- 为 pla_account 表添加授权相关字段(简化版) +-- 执行时间:2025-01-XX +-- 说明:如果字段已存在会报错,可以先执行上面的版本(带检查的版本) + +-- 添加授权日期字段 +ALTER TABLE `pla_account` +ADD COLUMN `authorization_date` DATETIME NULL COMMENT '授权日期(授权开始时间)' AFTER `active_actions`; + +-- 添加授权天数字段 +ALTER TABLE `pla_account` +ADD COLUMN `authorization_days` INT(11) NOT NULL DEFAULT 0 COMMENT '授权天数' AFTER `authorization_date`; + diff --git a/admin/config/index.js b/admin/config/index.js index 34f2d94..0bc5e47 100644 --- a/admin/config/index.js +++ b/admin/config/index.js @@ -26,11 +26,11 @@ const baseConfig = { // 开发环境配置 const developmentConfig = { ...baseConfig, - // apiUrl: 'http://localhost:9097/admin_api/', - // uploadUrl: 'http://localhost:9097/admin_api/upload', + apiUrl: 'http://localhost:9097/admin_api/', + uploadUrl: 'http://localhost:9097/admin_api/upload', - apiUrl: 'https://work.light120.com/admin_api/', - uploadUrl: 'https://work.light120.com/admin_api/upload', + // apiUrl: 'https://work.light120.com/admin_api/', + // uploadUrl: 'https://work.light120.com/admin_api/upload', // 开发环境显示更多调试信息 debug: true } diff --git a/admin/src/api/profile/pla_account_server.js b/admin/src/api/profile/pla_account_server.js index 4c5a3f8..c3af2ca 100644 --- a/admin/src/api/profile/pla_account_server.js +++ b/admin/src/api/profile/pla_account_server.js @@ -163,6 +163,29 @@ class PlaAccountServer { batchParseLocation(ids) { return window.framework.http.post('/pla_account/batchParseLocation', { ids }) } + + /** + * 检查账号授权状态 + * @param {Object} param - 参数对象 + * @param {Number|String} param.id - 账号ID(可选) + * @param {String} param.sn_code - 设备SN码(可选) + * @returns {Promise} + */ + checkAuthorization(param) { + return window.framework.http.post('/account/check-authorization', param) + } + + /** + * 更新账号授权信息 + * @param {Object} param - 参数对象 + * @param {Number|String} param.id - 账号ID + * @param {String} param.authorization_date - 授权日期(可选,不提供则使用当前时间) + * @param {Number} param.authorization_days - 授权天数 + * @returns {Promise} + */ + updateAuthorization(param) { + return window.framework.http.post('/account/update-authorization', param) + } } export default new PlaAccountServer() diff --git a/admin/src/views/account/pla_account.vue b/admin/src/views/account/pla_account.vue index d1a3624..8a6f54e 100644 --- a/admin/src/views/account/pla_account.vue +++ b/admin/src/views/account/pla_account.vue @@ -49,6 +49,56 @@ + + +
+ + + + + + + + + + +
+ +
+ + + + + +
+
+
+ + + {{ getExpireDate(authorizationModal.formData) }} + + +
+
+
@@ -253,6 +303,16 @@ export default { title: '在线简历详情', data: null }, + authorizationModal: { + visible: false, + formData: { + id: null, + accountName: '', + sn_code: '', + authorization_date: null, + authorization_days: 0 + } + }, gridOption: { param: { seachOption: { @@ -356,6 +416,51 @@ export default { }, params.row.auto_chat ? '开启' : '关闭') } }, + { + title: '剩余天数', + key: 'remaining_days', + minWidth: 100, + render: (h, params) => { + const remainingDays = params.row.remaining_days || 0 + let color = 'success' + if (remainingDays <= 0) { + color = 'error' + } else if (remainingDays <= 7) { + color = 'warning' + } + return h('Tag', { + props: { color: color } + }, remainingDays > 0 ? `${remainingDays} 天` : '已过期') + } + }, + { + title: '授权日期', + key: 'authorization_date', + minWidth: 150, + render: (h, params) => { + if (!params.row.authorization_date) { + return h('span', { style: { color: '#999' } }, '未授权') + } + const date = new Date(params.row.authorization_date) + return h('span', this.formatDate(date)) + } + }, + { + title: '过期时间', + key: 'expire_date', + minWidth: 150, + render: (h, params) => { + if (!params.row.authorization_date || !params.row.authorization_days) { + return h('span', { style: { color: '#999' } }, '未授权') + } + const authDate = new Date(params.row.authorization_date) + const expireDate = new Date(authDate.getTime() + params.row.authorization_days * 24 * 60 * 60 * 1000) + const remainingDays = params.row.remaining_days || 0 + return h('span', { + style: { color: remainingDays <= 0 ? '#ed4014' : remainingDays <= 7 ? '#ff9900' : '#515a6e' } + }, this.formatDate(expireDate)) + } + }, { title: '自动活跃', key: 'auto_active', @@ -418,6 +523,13 @@ export default { this.showEditWarp(params.row) }, }, + { + title: '设置授权', + type: 'warning', + click: () => { + this.showAuthorizationModal(params.row) + }, + }, { title: '解析位置', type: 'success', @@ -597,6 +709,108 @@ export default { return [] } }, + // 格式化日期 + formatDate(date) { + if (!date) return '-' + const d = new Date(date) + const year = d.getFullYear() + const month = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + }, + // 显示授权设置弹窗 + showAuthorizationModal(row) { + this.authorizationModal.formData = { + id: row.id, + accountName: row.name, + sn_code: row.sn_code, + // 如果有授权日期则使用,否则默认使用当天 + authorization_date: row.authorization_date ? new Date(row.authorization_date) : new Date(), + authorization_days: row.authorization_days || 0 + } + this.authorizationModal.visible = true + }, + // 设置快捷授权天数 + setQuickAuthorizationDays(days) { + this.authorizationModal.formData.authorization_days = days + // 如果没有设置授权日期,自动设置为当前日期 + if (!this.authorizationModal.formData.authorization_date) { + this.authorizationModal.formData.authorization_date = new Date() + } + }, + // 保存授权信息 + async handleSaveAuthorization() { + if (!this.authorizationModal.formData.id) { + this.$Message.error('账号ID不能为空') + return + } + + if (!this.authorizationModal.formData.authorization_days || this.authorizationModal.formData.authorization_days <= 0) { + this.$Message.error('请输入授权天数') + return + } + + try { + const param = { + id: this.authorizationModal.formData.id, + authorization_days: this.authorizationModal.formData.authorization_days + } + + // 如果设置了授权日期,添加到参数中 + if (this.authorizationModal.formData.authorization_date) { + param.authorization_date = this.authorizationModal.formData.authorization_date + } + + await plaAccountServer.updateAuthorization(param) + this.$Message.success('授权设置成功!') + this.authorizationModal.visible = false + // 刷新列表 + this.query(this.gridOption.param.pageOption.page || 1) + } catch (error) { + console.error('设置授权失败:', error) + this.$Message.error('设置授权失败:' + (error.message || '请稍后重试')) + } + }, + // 取消授权设置 + handleCancelAuthorization() { + this.authorizationModal.visible = false + this.authorizationModal.formData = { + id: null, + accountName: '', + sn_code: '', + authorization_date: null, + authorization_days: 0 + } + }, + // 获取过期时间 + getExpireDate(formData) { + if (!formData.authorization_date || !formData.authorization_days) { + return '未授权' + } + const authDate = new Date(formData.authorization_date) + const expireDate = new Date(authDate.getTime() + formData.authorization_days * 24 * 60 * 60 * 1000) + const year = expireDate.getFullYear() + const month = String(expireDate.getMonth() + 1).padStart(2, '0') + const day = String(expireDate.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + }, + // 获取过期时间颜色 + getExpireDateColor(formData) { + if (!formData.authorization_date || !formData.authorization_days) { + return '#999' + } + const authDate = new Date(formData.authorization_date) + const expireDate = new Date(authDate.getTime() + formData.authorization_days * 24 * 60 * 60 * 1000) + const now = new Date() + const remaining = Math.ceil((expireDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)) + if (remaining <= 0) { + return '#ed4014' + } else if (remaining <= 7) { + return '#ff9900' + } else { + return '#515a6e' + } + }, // 解析工作经历 parseWorkExp(workExp) { if (!workExp) return [] diff --git a/admin/src/views/account/pla_account_detail.vue b/admin/src/views/account/pla_account_detail.vue index 2c1c2ed..3d8a22f 100644 --- a/admin/src/views/account/pla_account_detail.vue +++ b/admin/src/views/account/pla_account_detail.vue @@ -92,6 +92,54 @@
+ + + + +
+ 授权日期: + {{ formatDateTime(accountInfo.authorization_date) || '未授权' }} +
+ + +
+ 授权天数: + {{ accountInfo.authorization_days || 0 }} 天 +
+ + +
+ 剩余天数: + + + {{ accountInfo.remaining_days || 0 }} 天 + + +
+ +
+ + +
+ 过期时间: + + {{ getExpireDate(accountInfo) }} + +
+ + +
+ 授权状态: + + + {{ getAuthorizationStatus(accountInfo) }} + + +
+ +
+
+ @@ -177,13 +225,13 @@ {{ deliverConfig.max_deliver || '-' }} - +
过滤关键词: {{ deliverConfig.filter_keywords || '-' }}
- +
排除关键词: {{ deliverConfig.exclude_keywords || '-' }} @@ -1371,6 +1419,63 @@ export default { this.$Message.warning('二维码图片加载失败,请刷新重试') }, + // 获取剩余天数颜色 + getRemainingDaysColor(remainingDays) { + if (!remainingDays || remainingDays <= 0) { + return 'error' + } else if (remainingDays <= 7) { + return 'warning' + } else { + return 'success' + } + }, + + // 获取过期时间 + getExpireDate(accountInfo) { + if (!accountInfo.authorization_date || !accountInfo.authorization_days) { + return '未授权' + } + const authDate = new Date(accountInfo.authorization_date) + const expireDate = new Date(authDate.getTime() + accountInfo.authorization_days * 24 * 60 * 60 * 1000) + return this.formatDateTime(expireDate) + }, + + // 判断是否过期 + isExpired(accountInfo) { + const remainingDays = accountInfo.remaining_days || 0 + return remainingDays <= 0 + }, + + // 获取授权状态 + getAuthorizationStatus(accountInfo) { + if (!accountInfo.authorization_date || !accountInfo.authorization_days) { + return '未授权' + } + const remainingDays = accountInfo.remaining_days || 0 + if (remainingDays <= 0) { + return '已过期' + } else if (remainingDays <= 7) { + return '即将过期' + } else { + return '正常' + } + }, + + // 获取授权状态颜色 + getAuthorizationStatusColor(accountInfo) { + if (!accountInfo.authorization_date || !accountInfo.authorization_days) { + return 'default' + } + const remainingDays = accountInfo.remaining_days || 0 + if (remainingDays <= 0) { + return 'error' + } else if (remainingDays <= 7) { + return 'warning' + } else { + return 'success' + } + }, + // 格式化日期时间 formatDateTime(datetime) { if (!datetime) return '-' diff --git a/admin/src/views/account/pla_account_edit.vue b/admin/src/views/account/pla_account_edit.vue index 1d7c4f9..64d92e7 100644 --- a/admin/src/views/account/pla_account_edit.vue +++ b/admin/src/views/account/pla_account_edit.vue @@ -56,6 +56,45 @@ + + + + + +
+ +
+ + + + + +
+
+
+ + + {{ formData.remaining_days || 0 }} 天 + + + + + {{ getExpireDate(formData) }} + + +
+
@@ -230,7 +269,11 @@ export default { chat_workdays_only: 1, auto_active: 0, active_interval: 60, - active_actions_json: '' + active_actions_json: '', + // 授权相关字段 + authorization_date: null, + authorization_days: 0, + remaining_days: 0 }, // 排序优先级列表 priorityList: [ @@ -284,6 +327,16 @@ export default { }, // 处理 JSON 配置字段 processJsonFields(row) { + // 处理授权信息 + if (row.authorization_date) { + this.formData.authorization_date = new Date(row.authorization_date) + } else { + // 如果没有授权日期,默认使用当天 + this.formData.authorization_date = new Date() + } + this.formData.authorization_days = row.authorization_days || 0 + this.formData.remaining_days = row.remaining_days || 0 + // is_salary_priority 配置 if (row.is_salary_priority) { const priorityConfig = typeof row.is_salary_priority === 'string' @@ -400,6 +453,56 @@ export default { } }) }, + // 获取过期时间 + getExpireDate(formData) { + if (!formData.authorization_date || !formData.authorization_days) { + return '未授权' + } + const authDate = new Date(formData.authorization_date) + const expireDate = new Date(authDate.getTime() + formData.authorization_days * 24 * 60 * 60 * 1000) + const year = expireDate.getFullYear() + const month = String(expireDate.getMonth() + 1).padStart(2, '0') + const day = String(expireDate.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + }, + // 获取过期时间颜色 + getExpireDateColor(formData) { + const remainingDays = formData.remaining_days || 0 + if (remainingDays <= 0) { + return '#ed4014' + } else if (remainingDays <= 7) { + return '#ff9900' + } else { + return '#515a6e' + } + }, + // 获取剩余天数颜色 + getRemainingDaysColor(remainingDays) { + if (!remainingDays || remainingDays <= 0) { + return '#ed4014' + } else if (remainingDays <= 7) { + return '#ff9900' + } else { + return '#19be6b' + } + }, + // 设置授权天数(快捷按钮) + setAuthorizationDays(days) { + this.formData.authorization_days = days + // 如果没有设置授权日期,自动设置为当前日期 + if (!this.formData.authorization_date) { + this.formData.authorization_date = new Date() + } + // 计算剩余天数(如果已有授权日期) + if (this.formData.authorization_date && days > 0) { + const authDate = new Date(this.formData.authorization_date) + const expireDate = new Date(authDate.getTime() + days * 24 * 60 * 60 * 1000) + const now = new Date() + const remaining = Math.ceil((expireDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)) + this.formData.remaining_days = Math.max(0, remaining) + } + this.$Message.success(`已设置授权天数为 ${days} 天`) + }, // 返回 handleBack() { this.$refs.floatPanel.hide() diff --git a/api/controller_admin/pla_account.js b/api/controller_admin/pla_account.js index 94d2eec..371f13d 100644 --- a/api/controller_admin/pla_account.js +++ b/api/controller_admin/pla_account.js @@ -509,6 +509,158 @@ module.exports = { const { ids } = body; const result = await plaAccountService.batchParseLocation(ids); return ctx.success(result); + }, + + /** + * @swagger + * /admin_api/account/check-authorization: + * post: + * summary: 检查账号授权状态 + * description: 根据账号ID或SN码检查账号的授权状态(剩余天数、是否过期等) + * tags: [后台-账号管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: integer + * description: 账号ID(可选,如果提供id则不需要sn_code) + * sn_code: + * type: string + * description: 设备SN码(可选,如果提供sn_code则不需要id) + * responses: + * 200: + * description: 检查成功 + */ + 'POST /account/check-authorization': async (ctx) => { + const { id, sn_code } = ctx.getBody(); + const models = await Framework.getModels(); + const { pla_account } = models; + const dayjs = require('dayjs'); + + if (!id && !sn_code) { + return ctx.fail('请提供账号ID或SN码'); + } + + const where = id ? { id } : { sn_code }; + const account = await pla_account.findOne({ where }); + + if (!account) { + return ctx.fail('账号不存在'); + } + + const accountData = account.toJSON(); + const authDate = accountData.authorization_date; + const authDays = accountData.authorization_days || 0; + + // 计算剩余天数 + let remaining_days = 0; + let is_expired = false; + let end_date = null; + + if (authDate && authDays > 0) { + const startDate = dayjs(authDate); + end_date = startDate.add(authDays, 'day').toDate(); + const now = dayjs(); + const remaining = dayjs(end_date).diff(now, 'day', true); + remaining_days = Math.max(0, Math.ceil(remaining)); + is_expired = remaining_days <= 0; + } + + return ctx.success({ + id: accountData.id, + sn_code: accountData.sn_code, + name: accountData.name, + authorization_date: authDate, + authorization_days: authDays, + remaining_days: remaining_days, + end_date: end_date, + is_expired: is_expired, + is_authorized: !is_expired && remaining_days > 0 + }); + }, + + /** + * @swagger + * /admin_api/account/update-authorization: + * post: + * summary: 更新账号授权信息 + * description: 更新账号的授权日期和授权天数 + * tags: [后台-账号管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - id + * properties: + * id: + * type: integer + * description: 账号ID + * authorization_date: + * type: string + * format: date-time + * description: 授权日期(可选,不提供则使用当前时间) + * authorization_days: + * type: integer + * description: 授权天数(必填) + * responses: + * 200: + * description: 更新成功 + */ + 'POST /account/update-authorization': async (ctx) => { + const { id, authorization_date, authorization_days } = ctx.getBody(); + const models = await Framework.getModels(); + const { pla_account } = models; + const dayjs = require('dayjs'); + + if (!id) { + return ctx.fail('账号ID不能为空'); + } + + if (authorization_days === undefined || authorization_days === null) { + return ctx.fail('授权天数不能为空'); + } + + if (authorization_days < 0) { + return ctx.fail('授权天数不能为负数'); + } + + const account = await pla_account.findOne({ where: { id } }); + + if (!account) { + return ctx.fail('账号不存在'); + } + + // 如果提供了授权日期,使用提供的日期;否则使用当前时间 + const authDate = authorization_date ? new Date(authorization_date) : new Date(); + + await account.update({ + authorization_date: authDate, + authorization_days: authorization_days + }); + + // 计算更新后的剩余天数 + const end_date = dayjs(authDate).add(authorization_days, 'day').toDate(); + const now = dayjs(); + const remaining = dayjs(end_date).diff(now, 'day', true); + const remaining_days = Math.max(0, Math.ceil(remaining)); + + return ctx.success({ + message: '授权信息更新成功', + data: { + id: account.id, + authorization_date: authDate, + authorization_days: authorization_days, + remaining_days: remaining_days, + end_date: end_date + } + }); } }; diff --git a/api/controller_front/user.js b/api/controller_front/user.js index d0ea90d..b34e890 100644 --- a/api/controller_front/user.js +++ b/api/controller_front/user.js @@ -69,6 +69,7 @@ module.exports = { "POST /user/login": async (ctx) => { const { sn_code, device_id } = ctx.getBody(); + const dayjs = require('dayjs'); const { pla_account,device_status} = await Framework.getModels(); @@ -79,6 +80,26 @@ module.exports = { return ctx.fail('用户不存在'); } + // 检查授权状态 + const userData = user.toJSON(); + const authDate = userData.authorization_date; + const authDays = userData.authorization_days || 0; + + if (authDate && authDays > 0) { + const startDate = dayjs(authDate); + const endDate = startDate.add(authDays, 'day'); + const now = dayjs(); + const remaining = endDate.diff(now, 'day', true); + const remaining_days = Math.max(0, Math.ceil(remaining)); + + if (remaining_days <= 0) { + return ctx.fail('账号授权已过期,请联系管理员续费'); + } + } else { + // 如果没有授权信息,检查是否允许登录(可以根据业务需求决定是否允许) + // 这里暂时允许登录,但可以添加配置项控制 + } + // 更新设备状态 const device = await device_status.findOne({ where: { sn_code } }); @@ -99,7 +120,20 @@ module.exports = { device_id: user.device_id }); - return ctx.success({ token, user: user.toJSON() }); + // 计算剩余天数并返回 + let remaining_days = 0; + if (authDate && authDays > 0) { + const startDate = dayjs(authDate); + const endDate = startDate.add(authDays, 'day'); + const now = dayjs(); + const remaining = endDate.diff(now, 'day', true); + remaining_days = Math.max(0, Math.ceil(remaining)); + } + + const userInfo = user.toJSON(); + userInfo.remaining_days = remaining_days; + + return ctx.success({ token, user: userInfo }); } } \ No newline at end of file diff --git a/api/middleware/mqtt/mqttDispatcher.js b/api/middleware/mqtt/mqttDispatcher.js index 0c93229..02ef788 100644 --- a/api/middleware/mqtt/mqttDispatcher.js +++ b/api/middleware/mqtt/mqttDispatcher.js @@ -97,6 +97,18 @@ class MqttDispatcher { }); } + // 检查授权状态(对于需要授权的操作) + const authorizationService = require('../../services/authorization_service'); + const authCheck = await authorizationService.checkAuthorization(deviceCode, 'sn_code'); + if (!authCheck.is_authorized) { + console.log(`[MQTT分发器] 设备 ${deviceCode} 授权检查失败: ${authCheck.message}`); + return this.sendMqttResponse(mqttClient, uuid, { + code: 403, + message: authCheck.message, + data: null + }); + } + // 获取对应的处理器 const handler = this.actionHandlers.get(action); if (!handler) { diff --git a/api/middleware/schedule/command.js b/api/middleware/schedule/command.js index 4ce2f58..49b9b4d 100644 --- a/api/middleware/schedule/command.js +++ b/api/middleware/schedule/command.js @@ -3,6 +3,7 @@ const db = require('../dbProxy'); const jobManager = require('../job/index'); const ScheduleUtils = require('./utils'); const ScheduleConfig = require('./config'); +const authorizationService = require('../../services/authorization_service'); /** @@ -91,6 +92,12 @@ class CommandManager { throw new Error(`任务不存在: ${task_id}`); } + // 1.5 检查账号授权状态 + const authCheck = await authorizationService.checkAuthorization(task.sn_code, 'sn_code'); + if (!authCheck.is_authorized) { + throw new Error(`授权检查失败: ${authCheck.message}`); + } + // 2. 获取指令信息 const command_name = command.command_name || command.name || '未知指令'; const command_type = command.command_type || command.type; diff --git a/api/middleware/schedule/taskHandlers.js b/api/middleware/schedule/taskHandlers.js index 7d74df7..69eca86 100644 --- a/api/middleware/schedule/taskHandlers.js +++ b/api/middleware/schedule/taskHandlers.js @@ -48,6 +48,18 @@ class TaskHandlers { console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code}, 关键词: ${keyword}`); + // 检查授权状态 + const authorizationService = require('../../services/authorization_service'); + const authCheck = await authorizationService.checkAuthorization(sn_code, 'sn_code'); + if (!authCheck.is_authorized) { + console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code} 授权检查失败: ${authCheck.message}`); + return { + success: false, + deliveredCount: 0, + message: authCheck.message + }; + } + deviceManager.recordTaskStart(sn_code, task); const startTime = Date.now(); @@ -543,6 +555,18 @@ class TaskHandlers { const { sn_code, taskParams } = task; console.log(`[任务处理器] 自动沟通任务 - 设备: ${sn_code}`); + // 检查授权状态 + const authorizationService = require('../../services/authorization_service'); + const authCheck = await authorizationService.checkAuthorization(sn_code, 'sn_code'); + if (!authCheck.is_authorized) { + console.log(`[任务处理器] 自动沟通任务 - 设备: ${sn_code} 授权检查失败: ${authCheck.message}`); + return { + success: false, + chatCount: 0, + message: authCheck.message + }; + } + deviceManager.recordTaskStart(sn_code, task); const startTime = Date.now(); diff --git a/api/model/pla_account.js b/api/model/pla_account.js index 9aaa4ba..f9d907f 100644 --- a/api/model/pla_account.js +++ b/api/model/pla_account.js @@ -264,6 +264,39 @@ module.exports = (db) => { }) }, + // 授权相关字段 + authorization_date: { + comment: '授权日期(授权开始时间)', + type: Sequelize.DATE, + allowNull: true, + defaultValue: null + }, + authorization_days: { + comment: '授权天数', + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + }, + remaining_days: { + comment: '剩余天数(虚拟字段,通过计算得出)', + type: Sequelize.VIRTUAL, + get: function () { + const authDate = this.getDataValue('authorization_date'); + const authDays = this.getDataValue('authorization_days') || 0; + + if (!authDate || authDays <= 0) { + return 0; + } + + const startDate = dayjs(authDate); + const endDate = startDate.add(authDays, 'day'); + const now = dayjs(); + + // 计算剩余天数 + const remaining = endDate.diff(now, 'day', true); + return Math.max(0, Math.ceil(remaining)); + } + } }); diff --git a/api/services/authorization_service.js b/api/services/authorization_service.js new file mode 100644 index 0000000..96c7384 --- /dev/null +++ b/api/services/authorization_service.js @@ -0,0 +1,80 @@ +/** + * 授权服务 + * 提供账号授权检查功能 + */ + +const db = require('../middleware/dbProxy'); +const dayjs = require('dayjs'); + +class AuthorizationService { + /** + * 检查账号授权状态 + * @param {string|number} identifier - 账号标识(sn_code 或 id) + * @param {string} identifierType - 标识类型:'sn_code' 或 'id' + * @returns {Promise} 授权检查结果 {is_authorized: boolean, remaining_days: number, message: string} + */ + async checkAuthorization(identifier, identifierType = 'sn_code') { + const pla_account = db.getModel('pla_account'); + + const where = {}; + where[identifierType] = identifier; + + const account = await pla_account.findOne({ where }); + + if (!account) { + return { + is_authorized: false, + remaining_days: 0, + message: '账号不存在' + }; + } + + const accountData = account.toJSON(); + const authDate = accountData.authorization_date; + const authDays = accountData.authorization_days || 0; + + // 如果没有授权信息,默认不允许 + if (!authDate || authDays <= 0) { + return { + is_authorized: false, + remaining_days: 0, + message: '账号未授权,请联系管理员' + }; + } + + // 计算剩余天数 + const startDate = dayjs(authDate); + const endDate = startDate.add(authDays, 'day'); + const now = dayjs(); + const remaining = endDate.diff(now, 'day', true); + const remaining_days = Math.max(0, Math.ceil(remaining)); + + if (remaining_days <= 0) { + return { + is_authorized: false, + remaining_days: 0, + message: '账号授权已过期,请联系管理员续费' + }; + } + + return { + is_authorized: true, + remaining_days: remaining_days, + message: `授权有效,剩余 ${remaining_days} 天` + }; + } + + /** + * 检查账号授权状态(快速检查,只返回是否授权) + * @param {string|number} identifier - 账号标识(sn_code 或 id) + * @param {string} identifierType - 标识类型:'sn_code' 或 'id' + * @returns {Promise} 是否授权 + */ + async isAuthorized(identifier, identifierType = 'sn_code') { + const result = await this.checkAuthorization(identifier, identifierType); + return result.is_authorized; + } +} + +module.exports = new AuthorizationService(); + diff --git a/api/services/pla_account_service.js b/api/services/pla_account_service.js index 69377fd..7549f6f 100644 --- a/api/services/pla_account_service.js +++ b/api/services/pla_account_service.js @@ -6,6 +6,7 @@ const db = require('../middleware/dbProxy'); const scheduleManager = require('../middleware/schedule/index.js'); const locationService = require('./locationService'); +const authorizationService = require('./authorization_service'); class PlaAccountService { /** @@ -382,6 +383,12 @@ class PlaAccountService { throw new Error('账号不存在'); } + // 检查授权状态 + const authCheck = await authorizationService.checkAuthorization(id, 'id'); + if (!authCheck.is_authorized) { + throw new Error(authCheck.message); + } + // 从 device_status 检查账号是否在线 const deviceStatus = await device_status.findByPk(account.sn_code); if (!deviceStatus || !deviceStatus.isOnline) {