501 lines
16 KiB
JavaScript
501 lines
16 KiB
JavaScript
|
|
/**
|
|||
|
|
* 写入类 API 路由 · 语言驱动操作系统写入能力层
|
|||
|
|
*
|
|||
|
|
* POST /api/tickets/create — 创建工单
|
|||
|
|
* PATCH /api/tickets/:ticketId — 更新工单状态
|
|||
|
|
* POST /api/syslog/submit — 提交 SYSLOG
|
|||
|
|
* POST /api/broadcasts/create — 创建广播
|
|||
|
|
* POST /api/deploy/preview — 触发预览站部署
|
|||
|
|
* POST /api/deploy/production — 触发正式站部署(需二次确认)
|
|||
|
|
* PATCH /api/agents/:agentId — 更新 Agent 状态
|
|||
|
|
* POST /api/maintenance/log — 写入维护日志
|
|||
|
|
* POST /api/receipts/submit — 提交指令回执
|
|||
|
|
* PATCH /api/dev/:devId/status — 更新开发者状态
|
|||
|
|
*
|
|||
|
|
* 安全规则:所有写入接口需要身份验证 + 权限检查 + 审计日志
|
|||
|
|
* 版权:国作登字-2026-A-00037559
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
var express = require('express');
|
|||
|
|
var router = express.Router();
|
|||
|
|
var notionService = require('../services/notion');
|
|||
|
|
var githubService = require('../services/github');
|
|||
|
|
var dbConfig = require('../config/databases');
|
|||
|
|
var authMiddleware = require('../middleware/auth');
|
|||
|
|
var sandboxMiddleware = require('../middleware/sandbox');
|
|||
|
|
var auditMiddleware = require('../middleware/audit');
|
|||
|
|
|
|||
|
|
// 所有写入接口需要身份验证 + 审计 + 沙箱检查
|
|||
|
|
router.use(authMiddleware.requireAuth);
|
|||
|
|
router.use(auditMiddleware.auditLog);
|
|||
|
|
router.use(sandboxMiddleware.sandboxGuard);
|
|||
|
|
|
|||
|
|
// 频率限制:每个开发者每分钟最多10次写入
|
|||
|
|
var rateLimitMap = new Map();
|
|||
|
|
var RATE_LIMIT = 10;
|
|||
|
|
var RATE_WINDOW = 60 * 1000;
|
|||
|
|
|
|||
|
|
function rateLimit(req, res, next) {
|
|||
|
|
var key = req.user.devId;
|
|||
|
|
var now = Date.now();
|
|||
|
|
var entry = rateLimitMap.get(key);
|
|||
|
|
|
|||
|
|
if (!entry || now - entry.start >= RATE_WINDOW) {
|
|||
|
|
rateLimitMap.set(key, { start: now, count: 1 });
|
|||
|
|
return next();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (entry.count >= RATE_LIMIT) {
|
|||
|
|
return res.status(429).json({
|
|||
|
|
error: true,
|
|||
|
|
code: 'RATE_LIMITED',
|
|||
|
|
message: '写入频率超限(每分钟最多 ' + RATE_LIMIT + ' 次)',
|
|||
|
|
reply: '⏳ 操作太频繁了,请稍等一分钟再试。'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
entry.count++;
|
|||
|
|
next();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
router.use(rateLimit);
|
|||
|
|
|
|||
|
|
// 模块路径归一化(确保带末尾斜杠)
|
|||
|
|
function normalizeModule(mod) {
|
|||
|
|
if (!mod) return '';
|
|||
|
|
mod = mod.trim();
|
|||
|
|
if (mod !== '*' && !mod.endsWith('/')) mod += '/';
|
|||
|
|
return mod;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查开发者是否拥有指定模块
|
|||
|
|
function hasModuleAccess(userModules, module) {
|
|||
|
|
if (!userModules || !module) return false;
|
|||
|
|
if (userModules.includes('*')) return true;
|
|||
|
|
var normalized = normalizeModule(module);
|
|||
|
|
for (var i = 0; i < userModules.length; i++) {
|
|||
|
|
if (normalizeModule(userModules[i]) === normalized) return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取实际数据库 ID(沙箱环境使用沙箱 DB)
|
|||
|
|
function getDbId(req, configKey) {
|
|||
|
|
if (req.sandbox && req.sandboxDbIds && req.sandboxDbIds[configKey]) {
|
|||
|
|
return req.sandboxDbIds[configKey];
|
|||
|
|
}
|
|||
|
|
return dbConfig[configKey];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ====== 创建工单 ======
|
|||
|
|
router.post('/tickets/create',
|
|||
|
|
authMiddleware.checkPermission('ticket:create'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var title = req.body.title;
|
|||
|
|
var description = req.body.description || '';
|
|||
|
|
var priority = req.body.priority || 'P2';
|
|||
|
|
var module = req.body.module || '';
|
|||
|
|
var devId = req.user.devId;
|
|||
|
|
|
|||
|
|
if (!title) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
error: true,
|
|||
|
|
code: 'MISSING_TITLE',
|
|||
|
|
message: '工单标题不能为空'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var ticketDbId = getDbId(req, 'ticketBook');
|
|||
|
|
if (!ticketDbId) {
|
|||
|
|
return res.status(503).json({
|
|||
|
|
error: true,
|
|||
|
|
code: 'DB_NOT_CONFIGURED',
|
|||
|
|
message: '工单数据库未配置'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var properties = {
|
|||
|
|
'工单标题': { title: [{ text: { content: title } }] },
|
|||
|
|
'描述': { rich_text: [{ text: { content: description } }] },
|
|||
|
|
'优先级': { select: { name: priority } },
|
|||
|
|
'提交人': { rich_text: [{ text: { content: devId } }] },
|
|||
|
|
'状态': { select: { name: '待处理' } }
|
|||
|
|
};
|
|||
|
|
if (module) {
|
|||
|
|
properties['模块'] = { select: { name: module } };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var result = await notionService.writeToDB(ticketDbId, properties);
|
|||
|
|
var prefix = req.sandbox ? '[沙箱] ' : '';
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
message: prefix + '工单已创建',
|
|||
|
|
ticketUrl: result.url || '',
|
|||
|
|
reply: '✅ ' + prefix + '工单「' + title + '」已创建,优先级 ' + priority + ',状态:待处理。'
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'CREATE_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 更新工单状态 ======
|
|||
|
|
router.patch('/tickets/:ticketId',
|
|||
|
|
authMiddleware.checkPermission('ticket:update'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var ticketId = req.params.ticketId;
|
|||
|
|
var status = req.body.status;
|
|||
|
|
|
|||
|
|
if (!status) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用 Notion page update API
|
|||
|
|
if (!notionService.notion) {
|
|||
|
|
return res.status(503).json({ error: true, code: 'NOTION_UNAVAILABLE', message: 'Notion 未连接' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await notionService.notion.pages.update({
|
|||
|
|
page_id: ticketId,
|
|||
|
|
properties: {
|
|||
|
|
'状态': { select: { name: status } }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '✅ 工单状态已更新为:' + status
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 提交 SYSLOG ======
|
|||
|
|
router.post('/syslog/submit',
|
|||
|
|
authMiddleware.checkPermission('syslog:submit'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var content = req.body.content;
|
|||
|
|
var type = req.body.type || '日常';
|
|||
|
|
var devId = req.user.devId;
|
|||
|
|
|
|||
|
|
if (!content) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_CONTENT', message: 'SYSLOG 内容不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var syslogDbId = getDbId(req, 'syslogInbox');
|
|||
|
|
if (!syslogDbId) {
|
|||
|
|
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: 'SYSLOG 数据库未配置' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await notionService.writeToDB(syslogDbId, {
|
|||
|
|
'SYSLOG内容': { title: [{ text: { content: content } }] },
|
|||
|
|
'类型': { select: { name: type } },
|
|||
|
|
'开发者编号': { rich_text: [{ text: { content: devId } }] }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
var prefix = req.sandbox ? '[沙箱] ' : '';
|
|||
|
|
var summary = content.length > 50 ? content.substring(0, 50) + '...' : content;
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '✅ ' + prefix + 'SYSLOG 已提交:「' + summary + '」'
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'SUBMIT_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 创建广播 ======
|
|||
|
|
router.post('/broadcasts/create',
|
|||
|
|
authMiddleware.checkPermission('broadcast:create'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var title = req.body.title;
|
|||
|
|
var content = req.body.content;
|
|||
|
|
var target = req.body.target || 'ALL';
|
|||
|
|
var devId = req.user.devId;
|
|||
|
|
|
|||
|
|
if (!title || !content) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_FIELDS', message: '广播标题和内容不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var broadcastId = 'BC-' + Date.now();
|
|||
|
|
var broadcastData = {
|
|||
|
|
broadcast_id: broadcastId,
|
|||
|
|
title: title,
|
|||
|
|
content: content,
|
|||
|
|
target: target,
|
|||
|
|
created_by: devId,
|
|||
|
|
created_at: new Date().toISOString(),
|
|||
|
|
status: 'active'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 写入 GitHub 广播文件
|
|||
|
|
var filePath = '.github/broadcasts/' + broadcastId + '.json';
|
|||
|
|
await githubService.createFile(
|
|||
|
|
filePath,
|
|||
|
|
JSON.stringify(broadcastData, null, 2),
|
|||
|
|
'📢 新广播: ' + title + ' · by ' + devId
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
broadcast_id: broadcastId,
|
|||
|
|
reply: '📢 广播「' + title + '」已创建并发送,编号:' + broadcastId
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'CREATE_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 触发预览站部署 ======
|
|||
|
|
router.post('/deploy/preview',
|
|||
|
|
authMiddleware.checkPermission('deploy:preview'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var module = req.body.module;
|
|||
|
|
var devId = req.user.devId;
|
|||
|
|
|
|||
|
|
if (!module) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_MODULE', message: '需要指定部署模块' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查开发者是否拥有该模块
|
|||
|
|
if (!hasModuleAccess(req.user.modules, module)) {
|
|||
|
|
return res.status(403).json({
|
|||
|
|
error: true,
|
|||
|
|
code: 'MODULE_DENIED',
|
|||
|
|
message: '你没有该模块的部署权限',
|
|||
|
|
reply: '❌ 你没有 ' + module + ' 模块的部署权限。你的模块是:' + req.user.modules.join(', ')
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await githubService.triggerWorkflow('preview-deploy.yml', {
|
|||
|
|
module: module,
|
|||
|
|
dev_id: devId,
|
|||
|
|
target: 'preview'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '🚀 已触发 ' + module + ' 模块的预览站部署。稍等几分钟,部署完成后我会通知你。'
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'DEPLOY_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 触发正式站部署(需二次确认)======
|
|||
|
|
router.post('/deploy/production',
|
|||
|
|
authMiddleware.checkPermission('deploy:production'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var module = req.body.module;
|
|||
|
|
var confirmToken = req.body.confirmToken;
|
|||
|
|
var devId = req.user.devId;
|
|||
|
|
|
|||
|
|
if (!module) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_MODULE', message: '需要指定部署模块' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查模块权限
|
|||
|
|
if (!hasModuleAccess(req.user.modules, module)) {
|
|||
|
|
return res.status(403).json({
|
|||
|
|
error: true,
|
|||
|
|
code: 'MODULE_DENIED',
|
|||
|
|
reply: '❌ 你没有 ' + module + ' 模块的部署权限。'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 二次确认
|
|||
|
|
if (!confirmToken) {
|
|||
|
|
return res.json({
|
|||
|
|
success: false,
|
|||
|
|
requireConfirmation: true,
|
|||
|
|
reply: '⚠️ 你正在请求部署 ' + module + ' 到**正式站(guanghulab.com)**。\n这会影响所有用户。\n\n请说「确认部署 ' + module + '」来确认。'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await githubService.triggerWorkflow('deploy-to-server.yml', {
|
|||
|
|
module: module,
|
|||
|
|
dev_id: devId,
|
|||
|
|
target: 'production'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '🚀 已触发 ' + module + ' 模块的**正式站部署**。部署日志会同步到维护日志中。'
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'DEPLOY_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 更新 Agent 状态 ======
|
|||
|
|
router.patch('/agents/:agentId',
|
|||
|
|
authMiddleware.checkPermission('agent:update'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var agentId = req.params.agentId;
|
|||
|
|
var status = req.body.status;
|
|||
|
|
|
|||
|
|
if (!status) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!notionService.notion) {
|
|||
|
|
return res.status(503).json({ error: true, code: 'NOTION_UNAVAILABLE', message: 'Notion 未连接' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await notionService.notion.pages.update({
|
|||
|
|
page_id: agentId,
|
|||
|
|
properties: {
|
|||
|
|
'状态': { select: { name: status } }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '✅ Agent ' + agentId + ' 状态已更新为:' + status
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 写入维护日志 ======
|
|||
|
|
router.post('/maintenance/log',
|
|||
|
|
authMiddleware.checkPermission('maintenance:log'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var title = req.body.title;
|
|||
|
|
var type = req.body.type || '常规维护';
|
|||
|
|
var devId = req.user.devId;
|
|||
|
|
|
|||
|
|
if (!title) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_TITLE', message: '维护日志标题不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var maintenanceDbId = getDbId(req, 'maintenanceLog');
|
|||
|
|
if (!maintenanceDbId) {
|
|||
|
|
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '维护日志数据库未配置' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await notionService.writeToDB(maintenanceDbId, {
|
|||
|
|
'标题': { title: [{ text: { content: title } }] },
|
|||
|
|
'类型': { select: { name: type } },
|
|||
|
|
'操作者': { rich_text: [{ text: { content: devId } }] }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '✅ 维护日志已记录:' + title
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'LOG_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 提交指令回执 ======
|
|||
|
|
router.post('/receipts/submit',
|
|||
|
|
authMiddleware.checkPermission('syslog:submit'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var instructionId = req.body.instruction_id;
|
|||
|
|
var status = req.body.status || '已完成';
|
|||
|
|
var receipt = req.body.receipt || '';
|
|||
|
|
|
|||
|
|
if (!instructionId) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_ID', message: '指令编号不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!dbConfig.receiptTracker) {
|
|||
|
|
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '回执数据库未配置' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await notionService.writeToDB(dbConfig.receiptTracker, {
|
|||
|
|
'指令编号': { rich_text: [{ text: { content: instructionId } }] },
|
|||
|
|
'执行状态': { select: { name: status } },
|
|||
|
|
'铸渊回执': { rich_text: [{ text: { content: receipt } }] },
|
|||
|
|
'回执时间': { date: { start: new Date().toISOString() } }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '✅ 指令 ' + instructionId + ' 回执已提交'
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'SUBMIT_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ====== 更新开发者状态 ======
|
|||
|
|
router.patch('/dev/:devId/status',
|
|||
|
|
authMiddleware.checkPermission('dev:update_self'),
|
|||
|
|
async function(req, res) {
|
|||
|
|
try {
|
|||
|
|
var devId = req.params.devId;
|
|||
|
|
var status = req.body.status;
|
|||
|
|
|
|||
|
|
// 验证 devId 格式
|
|||
|
|
if (!/^DEV-\d{3}$/.test(devId) && !/^TCS-\d{4}$/.test(devId)) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'INVALID_DEV_ID', message: '无效的开发者编号' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 开发者只能更新自己的状态(管理者除外)
|
|||
|
|
if (req.user.devId !== devId && !req.user.permissions.includes('dev:update_all')) {
|
|||
|
|
return res.status(403).json({
|
|||
|
|
error: true,
|
|||
|
|
code: 'SELF_ONLY',
|
|||
|
|
message: '只能更新自己的状态',
|
|||
|
|
reply: '🔒 你只能更新自己的状态,不能修改其他开发者的信息。'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!status) {
|
|||
|
|
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新 Notion 主控台中的状态
|
|||
|
|
if (dbConfig.controlPanel) {
|
|||
|
|
var result = await notionService.queryDB(dbConfig.controlPanel, {
|
|||
|
|
property: 'DEV编号',
|
|||
|
|
rich_text: { equals: devId }
|
|||
|
|
}, null, 1);
|
|||
|
|
|
|||
|
|
if (result.results && result.results.length > 0) {
|
|||
|
|
await notionService.notion.pages.update({
|
|||
|
|
page_id: result.results[0].id,
|
|||
|
|
properties: {
|
|||
|
|
'状态': { rich_text: [{ text: { content: status } }] }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reply: '✅ ' + devId + ' 的状态已更新为:' + status
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
module.exports = router;
|