From 72cb43c493531089e8ce43c235fff7869b6bb14e Mon Sep 17 00:00:00 2001 From: haoliang <821644@qq.com> Date: Wed, 6 May 2026 22:01:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A8=A1=E6=8B=9F=E9=87=87=E9=9B=86?= =?UTF-8?q?=E9=9B=86=E6=88=90=E2=80=94=E2=80=94=E5=90=8E=E7=AB=AFSimulator?= =?UTF-8?q?Controller(22=E7=AB=AF=E7=82=B9=E4=BB=A3=E7=90=86=E8=BD=AC?= =?UTF-8?q?=E5=8F=91)+=E5=89=8D=E7=AB=AF=E6=80=BB=E8=A7=88/=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5+=E8=B7=AF=E7=94=B1+=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E8=8F=9C=E5=8D=95+Mock=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/mock/simulator.ts | 139 +++++++ frontend/src/api/simulator.ts | 223 +++++++++++ frontend/src/layouts/AdminLayout.vue | 4 + frontend/src/router/index.ts | 4 + .../views/simulator/SimulatorDetailPage.vue | 284 ++++++++++++++ .../src/views/simulator/SimulatorPage.vue | 253 ++++++++++++ .../Controllers/SimulatorController.cs | 367 ++++++++++++++++++ .../Infrastructure/ServiceResolver.cs | 2 + src/CncWebApi/Web.config | 2 + 9 files changed, 1278 insertions(+) create mode 100644 frontend/mock/simulator.ts create mode 100644 frontend/src/api/simulator.ts create mode 100644 frontend/src/views/simulator/SimulatorDetailPage.vue create mode 100644 frontend/src/views/simulator/SimulatorPage.vue create mode 100644 src/CncWebApi/Controllers/SimulatorController.cs diff --git a/frontend/mock/simulator.ts b/frontend/mock/simulator.ts new file mode 100644 index 0000000..d950d8d --- /dev/null +++ b/frontend/mock/simulator.ts @@ -0,0 +1,139 @@ +import type { MockMethod, MockRequest } from './types' + +// 模拟采集地址数据 +const mockAddresses = [ + { + dbId: 1, + name: 'FANUC-1号', + url: 'http://localhost:9001/', + machineCount: 32, + machines: Array.from({ length: 32 }, (_, i) => ({ + id: i + 1, + deviceCode: `fanake_1.${i + 2}`, + name: `西-1.${i + 2}` + })), + isRunning: true, + runningPort: 9001 + }, + { + dbId: 2, + name: 'FANUC-2号', + url: 'http://localhost:9002/', + machineCount: 16, + machines: Array.from({ length: 16 }, (_, i) => ({ + id: i + 33, + deviceCode: `fanake_2.${i + 1}`, + name: `东-2.${i + 1}` + })), + isRunning: false, + runningPort: 0 + } +] + +// 模拟状态汇总 +const mockStatusList = [ + { + dbAddressId: 1, + name: 'FANUC-1号模拟', + port: 9001, + isRunning: true, + totalDevices: 32, + onlineDevices: 28, + requestCount: 1560, + dataChangeInterval: 10, + totalParts: 128 + } +] + +// 模拟设备状态 +const mockDevices = [ + { deviceCode: 'fanake_1.2', desc: '西-1.2', scenario: 'machining', isOnline: true, programName: 'O504', partCount: 14, runStatus: 3, operateMode: 10, spindleSpeedSet: 3000, spindleSpeedActual: 2980, feedSpeedSet: 500, feedSpeedActual: 490, spindleLoad: 65, machiningStatus: 'cutting', scenarioTick: 45, scenarioDuration: 120 }, + { deviceCode: 'fanake_1.3', desc: '西-1.3', scenario: 'idle', isOnline: true, programName: 'O1', partCount: 53, runStatus: 1, operateMode: 10, spindleSpeedSet: 0, spindleSpeedActual: 0, feedSpeedSet: 0, feedSpeedActual: 0, spindleLoad: 5, machiningStatus: 'idle', scenarioTick: 12, scenarioDuration: 60 }, + { deviceCode: 'fanake_1.4', desc: '西-1.4', scenario: 'offline', isOnline: false, programName: 'O200', partCount: 0, runStatus: 0, operateMode: 0, spindleSpeedSet: 0, spindleSpeedActual: 0, feedSpeedSet: 0, feedSpeedActual: 0, spindleLoad: 0, machiningStatus: 'offline', scenarioTick: 0, scenarioDuration: 0 } +] + +// 模拟请求日志 +const mockLogs = Array.from({ length: 10 }, (_, i) => ({ + index: 10 - i, + timestamp: `${String(14 + Math.floor(i / 6)).padStart(2, '0')}:${String(30 - i * 2).padStart(2, '0')}:${String(15 + i).padStart(2, '0')}`, + deviceCount: 28 + Math.floor(Math.random() * 5), + keyData: `fanake_1.2(P=14,Prog=O504,Run=3) fanake_1.3(P=53,Prog=O1,Run=1)`, + duration: 12 + Math.floor(Math.random() * 20), + fullJson: `[{"device":"fanake_1.2","desc":"西-1.2","tags":[{"id":"Tag5","value":"O504"}]}]` +})) + +const mocks: MockMethod[] = [ + // 探测模拟器 + { url: '/api/admin/simulator/ping', method: 'get', response: () => ({ code: 0, message: 'success', data: { running: true } }) }, + + // 获取采集地址列表 + { url: '/api/admin/simulator/addresses', method: 'get', response: () => ({ code: 0, message: 'success', data: mockAddresses }) }, + + // 获取模拟状态汇总 + { url: '/api/admin/simulator/status', method: 'get', response: () => ({ code: 0, message: 'success', data: mockStatusList }) }, + + // 启动模拟 + { url: '/api/admin/simulator/start', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true, port: 9001 } }) }, + + // 停止模拟 + { url: '/api/admin/simulator/stop', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + + // 全部启动 + { url: '/api/admin/simulator/start-all', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + + // 全部停止 + { url: '/api/admin/simulator/stop-all', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + + // 重新加载 + { url: '/api/admin/simulator/reload', method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true, count: 2 } }) }, + + // 单地址状态(匹配 /address/{port}/status) + { url: /\/api\/admin\/simulator\/address\/\d+\/status$/, method: 'get', response: () => ({ + code: 0, message: 'success', data: { + name: 'FANUC-1号模拟', port: 9001, isRunning: true, + requestCount: 1560, successCount: 1540, failCount: 20, + totalDevices: 32, onlineDevices: 28, dataChangeInterval: 10, + scenarioMode: 'auto', networkError: 'normal', + startTime: '2026-05-06 10:00:00', uptime: '04:32:15', + devices: mockDevices + } + })}, + + // 单地址启动/停止/事件/设置(POST类,统返回ok) + { url: /\/api\/admin\/simulator\/address\/\d+\/start$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/stop$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/event$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/interval$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/network$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/mode$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/add-device$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + { url: /\/api\/admin\/simulator\/address\/\d+\/remove-device$/, method: 'post', response: () => ({ code: 0, message: 'success', data: { ok: true } }) }, + + // 日志 + { url: /\/api\/admin\/simulator\/address\/\d+\/logs$/, method: 'get', response: () => ({ code: 0, message: 'success', data: mockLogs }) }, + + // 统计 + { url: /\/api\/admin\/simulator\/address\/\d+\/stats$/, method: 'get', response: () => ({ + code: 0, message: 'success', data: { + totalDevices: 32, onlineDevices: 28, totalParts: 128, + partsByDevice: { + 'fanake_1.2': { desc: '西-1.2', totalParts: 14, currentProgram: 'O504', currentPartCount: 14, programs: { 'O504': 14 } }, + 'fanake_1.3': { desc: '西-1.3', totalParts: 53, currentProgram: 'O1', currentPartCount: 53, programs: { 'O1': 53 } } + } + } + })}, + + // 事件历史 + { url: /\/api\/admin\/simulator\/address\/\d+\/event-history$/, method: 'get', response: () => ({ code: 0, message: 'success', data: [ + { timestamp: '2026-05-06 14:30:00', deviceCode: 'fanake_1.2', eventType: 'change_program', oldProgram: 'O200', newProgram: 'O504', partCountBefore: 10, partCountAfter: 14, detail: '程序切换' }, + { timestamp: '2026-05-06 14:25:00', deviceCode: 'fanake_1.3', eventType: 'part_count_increase', oldProgram: 'O1', newProgram: 'O1', partCountBefore: 52, partCountAfter: 53, detail: '零件数+1' } + ] })}, + + // 完整汇总 + { url: /\/api\/admin\/simulator\/address\/\d+\/full-summary$/, method: 'get', response: () => ({ code: 0, message: 'success', data: { exportTime: '2026-05-06 14:35:00', addressName: 'FANUC-1号模拟', port: 9001, totalDevices: 32, onlineDevices: 28, totalParts: 128 } }) }, + + // 异常日志 + { url: /\/api\/admin\/simulator\/address\/\d+\/error-log$/, method: 'get', response: () => ({ code: 0, message: 'success', data: [] }) }, +] + +export default mocks diff --git a/frontend/src/api/simulator.ts b/frontend/src/api/simulator.ts new file mode 100644 index 0000000..4448c9c --- /dev/null +++ b/frontend/src/api/simulator.ts @@ -0,0 +1,223 @@ +import request from '@/utils/request' +import type { ApiResponse } from '@/types' + +// --- 模拟器数据模型 --- + +/** 模拟器连接状态 */ +export interface SimulatorPing { + running: boolean +} + +/** 数据库采集地址(模拟器返回) */ +export interface SimulatorAddress { + dbId: number + name: string + url: string + machineCount: number + machines: { id: number; deviceCode: string; name: string }[] + isRunning: boolean + runningPort: number +} + +/** 模拟状态汇总 */ +export interface SimulatorStatus { + dbAddressId: number + name: string + port: number + isRunning: boolean + totalDevices: number + onlineDevices: number + requestCount: number + dataChangeInterval: number + totalParts: number +} + +/** 设备状态 */ +export interface DeviceStatus { + deviceCode: string + desc: string + scenario: string + isOnline: boolean + programName: string + partCount: number + runStatus: number + operateMode: number + spindleSpeedSet: number + spindleSpeedActual: number + feedSpeedSet: number + feedSpeedActual: number + spindleLoad: number + machiningStatus: string + scenarioTick: number + scenarioDuration: number +} + +/** 单地址详情状态 */ +export interface AddressStatus { + name: string + port: number + isRunning: boolean + requestCount: number + successCount: number + failCount: number + totalDevices: number + onlineDevices: number + dataChangeInterval: number + scenarioMode: string + networkError: string + startTime: string + uptime: string + devices: DeviceStatus[] +} + +/** 零件统计 */ +export interface AddressStats { + totalDevices: number + onlineDevices: number + totalParts: number + partsByDevice: Record + }> +} + +/** 请求日志 */ +export interface SimulatorLog { + index: number + timestamp: string + deviceCount: number + keyData: string + duration: number + fullJson: string +} + +/** 事件历史 */ +export interface EventHistory { + timestamp: string + deviceCode: string + eventType: string + oldProgram: string + newProgram: string + partCountBefore: number + partCountAfter: number + detail: string +} + +// --- 网关API --- + +/** 探测模拟器是否运行 */ +export function pingSimulator() { + return request.get('/admin/simulator/ping') +} + +/** 获取数据库采集地址列表 */ +export function fetchSimulatorAddresses() { + return request.get('/admin/simulator/addresses') +} + +/** 获取所有模拟状态汇总 */ +export function fetchSimulatorStatus() { + return request.get('/admin/simulator/status') +} + +/** 启动指定地址的模拟 */ +export function startSimulator(data: { dbAddressId: number; deviceCodes?: string[] }) { + return request.post('/admin/simulator/start', data) +} + +/** 停止指定地址的模拟 */ +export function stopSimulator(data: { dbAddressId: number }) { + return request.post('/admin/simulator/stop', data) +} + +/** 启动所有地址的模拟 */ +export function startAllSimulators() { + return request.post('/admin/simulator/start-all') +} + +/** 停止所有地址的模拟 */ +export function stopAllSimulators() { + return request.post('/admin/simulator/stop-all') +} + +/** 重新加载数据库配置 */ +export function reloadSimulator() { + return request.post('/admin/simulator/reload') +} + +// --- 单地址API --- + +/** 获取单地址状态 */ +export function fetchAddressStatus(port: number) { + return request.get(`/admin/simulator/address/${port}/status`) +} + +/** 启动单地址数据模拟 */ +export function startAddressSimulation(port: number) { + return request.post(`/admin/simulator/address/${port}/start`) +} + +/** 停止单地址数据模拟 */ +export function stopAddressSimulation(port: number) { + return request.post(`/admin/simulator/address/${port}/stop`) +} + +/** 触发设备事件 */ +export function triggerDeviceEvent(port: number, data: { deviceId: string; eventType: string }) { + return request.post(`/admin/simulator/address/${port}/event`, data) +} + +/** 修改数据变化频率 */ +export function setAddressInterval(port: number, data: { value: number }) { + return request.post(`/admin/simulator/address/${port}/interval`, data) +} + +/** 设置网络异常类型 */ +export function setNetworkError(port: number, data: { type: string }) { + return request.post(`/admin/simulator/address/${port}/network`, data) +} + +/** 切换剧本模式 */ +export function setScenarioMode(port: number, data: { mode: string }) { + return request.post(`/admin/simulator/address/${port}/mode`, data) +} + +/** 获取请求日志 */ +export function fetchAddressLogs(port: number) { + return request.get(`/admin/simulator/address/${port}/logs`) +} + +/** 获取零件统计 */ +export function fetchAddressStats(port: number) { + return request.get(`/admin/simulator/address/${port}/stats`) +} + +/** 添加设备 */ +export function addDevice(port: number, data: { deviceCode: string; desc: string }) { + return request.post(`/admin/simulator/address/${port}/add-device`, data) +} + +/** 移除设备 */ +export function removeDevice(port: number, data: { deviceCode: string }) { + return request.post(`/admin/simulator/address/${port}/remove-device`, data) +} + +/** 获取事件历史 */ +export function fetchEventHistory(port: number) { + return request.get(`/admin/simulator/address/${port}/event-history`) +} + +/** 获取完整汇总 */ +export function fetchFullSummary(port: number) { + return request.get(`/admin/simulator/address/${port}/full-summary`) +} + +/** 获取异常日志 */ +export function fetchErrorLog(port: number) { + return request.get(`/admin/simulator/address/${port}/error-log`) +} + +export default {} diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue index 584f775..2bf8e3f 100644 --- a/frontend/src/layouts/AdminLayout.vue +++ b/frontend/src/layouts/AdminLayout.vue @@ -71,6 +71,10 @@ + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 615dbf4..9477dba 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -27,6 +27,8 @@ const LogPage = () => import('@/views/log/LogPage.vue') const ScreenConfigPage = () => import('@/views/screen-config/ScreenConfigPage.vue') const ScreenPage = () => import('@/views/screen/ScreenPage.vue') const CollectLogPage = () => import('@/views/collect-log/CollectLogPage.vue') +const SimulatorPage = () => import('@/views/simulator/SimulatorPage.vue') +const SimulatorDetailPage = () => import('@/views/simulator/SimulatorDetailPage.vue') // 正常路由 const normalRoutes: RouteRecordRaw[] = [ @@ -52,6 +54,8 @@ const normalRoutes: RouteRecordRaw[] = [ { path: 'settings', name: 'Settings', component: SettingsPage, meta: { title: '系统设置' } }, { path: 'log', name: 'Log', component: LogPage, meta: { title: '操作日志' } }, { path: 'screen-config', name: 'ScreenConfig', component: ScreenConfigPage, meta: { title: '大屏配置' } }, + { path: 'simulator', name: 'Simulator', component: SimulatorPage, meta: { title: '模拟采集' } }, + { path: 'simulator/:port', name: 'SimulatorDetail', component: SimulatorDetailPage, meta: { title: '模拟详情' } }, ], }, { diff --git a/frontend/src/views/simulator/SimulatorDetailPage.vue b/frontend/src/views/simulator/SimulatorDetailPage.vue new file mode 100644 index 0000000..61df756 --- /dev/null +++ b/frontend/src/views/simulator/SimulatorDetailPage.vue @@ -0,0 +1,284 @@ + + + diff --git a/frontend/src/views/simulator/SimulatorPage.vue b/frontend/src/views/simulator/SimulatorPage.vue new file mode 100644 index 0000000..963c7c8 --- /dev/null +++ b/frontend/src/views/simulator/SimulatorPage.vue @@ -0,0 +1,253 @@ + + + diff --git a/src/CncWebApi/Controllers/SimulatorController.cs b/src/CncWebApi/Controllers/SimulatorController.cs new file mode 100644 index 0000000..2c67edb --- /dev/null +++ b/src/CncWebApi/Controllers/SimulatorController.cs @@ -0,0 +1,367 @@ +using System; +using System.Configuration; +using System.Net.Http; +using System.Text; +using System.Web.Http; +using CncModels.Dto; +using CncWebApi.Infrastructure; +using Newtonsoft.Json; + +namespace CncWebApi.Controllers +{ + /// + /// 模拟采集服务控制器。 + /// 将所有请求转发到 CncSimulator(localhost:9000网关 + 动态端口单地址)。 + /// + [RoutePrefix("api/admin/simulator")] + [JwtAuthFilter] + public class SimulatorController : ApiController + { + private static readonly HttpClient _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; + private static readonly string _gatewayUrl = ConfigurationManager.AppSettings["SimulatorGatewayUrl"] ?? "http://localhost:9000"; + + #region 网关API(→ localhost:9000) + + /// + /// 探测模拟器是否运行 + /// GET /api/admin/simulator/ping + /// + [HttpGet] + [Route("ping")] + public IHttpActionResult Ping() + { + try + { + var response = _httpClient.GetAsync($"{_gatewayUrl}/admin/api/status").Result; + return Ok(ApiResponse.Success(new { running = response.IsSuccessStatusCode })); + } + catch + { + return Ok(ApiResponse.Success(new { running = false })); + } + } + + /// + /// 获取数据库采集地址列表 + /// GET /api/admin/simulator/addresses + /// + [HttpGet] + [Route("addresses")] + public IHttpActionResult GetAddresses() + { + return ForwardToGateway("/admin/api/db-addresses"); + } + + /// + /// 获取所有模拟状态汇总 + /// GET /api/admin/simulator/status + /// + [HttpGet] + [Route("status")] + public IHttpActionResult GetStatus() + { + return ForwardToGateway("/admin/api/status"); + } + + /// + /// 启动指定地址的模拟 + /// POST /api/admin/simulator/start + /// + [HttpPost] + [Route("start")] + public IHttpActionResult Start() + { + return ForwardToGatewayPost("/admin/api/start-address"); + } + + /// + /// 停止指定地址的模拟 + /// POST /api/admin/simulator/stop + /// + [HttpPost] + [Route("stop")] + public IHttpActionResult Stop() + { + return ForwardToGatewayPost("/admin/api/stop-address"); + } + + /// + /// 启动所有地址的模拟 + /// POST /api/admin/simulator/start-all + /// + [HttpPost] + [Route("start-all")] + public IHttpActionResult StartAll() + { + return ForwardToGatewayPost("/admin/api/start-all"); + } + + /// + /// 停止所有地址的模拟 + /// POST /api/admin/simulator/stop-all + /// + [HttpPost] + [Route("stop-all")] + public IHttpActionResult StopAll() + { + return ForwardToGatewayPost("/admin/api/stop-all"); + } + + /// + /// 重新加载数据库配置 + /// POST /api/admin/simulator/reload + /// + [HttpPost] + [Route("reload")] + public IHttpActionResult Reload() + { + return ForwardToGatewayPost("/admin/api/reload-db"); + } + + #endregion + + #region 单地址API(→ localhost:{port}) + + /// + /// 获取单地址状态 + /// GET /api/admin/simulator/address/{port}/status + /// + [HttpGet] + [Route("address/{port}/status")] + public IHttpActionResult GetAddressStatus(int port) + { + return ForwardToAddress(port, "/admin/api/status"); + } + + /// + /// 启动单地址数据模拟 + /// POST /api/admin/simulator/address/{port}/start + /// + [HttpPost] + [Route("address/{port}/start")] + public IHttpActionResult StartAddress(int port) + { + return ForwardToAddressPost(port, "/admin/api/start"); + } + + /// + /// 停止单地址数据模拟 + /// POST /api/admin/simulator/address/{port}/stop + /// + [HttpPost] + [Route("address/{port}/stop")] + public IHttpActionResult StopAddress(int port) + { + return ForwardToAddressPost(port, "/admin/api/stop"); + } + + /// + /// 触发设备事件 + /// POST /api/admin/simulator/address/{port}/event + /// + [HttpPost] + [Route("address/{port}/event")] + public IHttpActionResult TriggerEvent(int port) + { + return ForwardToAddressPost(port, "/admin/api/event"); + } + + /// + /// 修改数据变化频率 + /// POST /api/admin/simulator/address/{port}/interval + /// + [HttpPost] + [Route("address/{port}/interval")] + public IHttpActionResult SetInterval(int port) + { + return ForwardToAddressPost(port, "/admin/api/interval"); + } + + /// + /// 设置网络异常类型 + /// POST /api/admin/simulator/address/{port}/network + /// + [HttpPost] + [Route("address/{port}/network")] + public IHttpActionResult SetNetwork(int port) + { + return ForwardToAddressPost(port, "/admin/api/network"); + } + + /// + /// 切换剧本模式 + /// POST /api/admin/simulator/address/{port}/mode + /// + [HttpPost] + [Route("address/{port}/mode")] + public IHttpActionResult SetMode(int port) + { + return ForwardToAddressPost(port, "/admin/api/mode"); + } + + /// + /// 获取请求日志 + /// GET /api/admin/simulator/address/{port}/logs + /// + [HttpGet] + [Route("address/{port}/logs")] + public IHttpActionResult GetLogs(int port) + { + return ForwardToAddress(port, "/admin/api/logs"); + } + + /// + /// 获取零件统计 + /// GET /api/admin/simulator/address/{port}/stats + /// + [HttpGet] + [Route("address/{port}/stats")] + public IHttpActionResult GetStats(int port) + { + return ForwardToAddress(port, "/admin/api/stats"); + } + + /// + /// 添加设备 + /// POST /api/admin/simulator/address/{port}/add-device + /// + [HttpPost] + [Route("address/{port}/add-device")] + public IHttpActionResult AddDevice(int port) + { + return ForwardToAddressPost(port, "/admin/api/add-device"); + } + + /// + /// 移除设备 + /// POST /api/admin/simulator/address/{port}/remove-device + /// + [HttpPost] + [Route("address/{port}/remove-device")] + public IHttpActionResult RemoveDevice(int port) + { + return ForwardToAddressPost(port, "/admin/api/remove-device"); + } + + /// + /// 获取事件历史 + /// GET /api/admin/simulator/address/{port}/event-history + /// + [HttpGet] + [Route("address/{port}/event-history")] + public IHttpActionResult GetEventHistory(int port) + { + return ForwardToAddress(port, "/admin/api/event-history"); + } + + /// + /// 获取完整汇总 + /// GET /api/admin/simulator/address/{port}/full-summary + /// + [HttpGet] + [Route("address/{port}/full-summary")] + public IHttpActionResult GetFullSummary(int port) + { + return ForwardToAddress(port, "/admin/api/full-summary"); + } + + /// + /// 获取异常日志 + /// GET /api/admin/simulator/address/{port}/error-log + /// + [HttpGet] + [Route("address/{port}/error-log")] + public IHttpActionResult GetErrorLog(int port) + { + return ForwardToAddress(port, "/admin/api/error-log"); + } + + #endregion + + #region 转发辅助方法 + + /// + /// GET转发到网关(9000端口) + /// + private IHttpActionResult ForwardToGateway(string path) + { + try + { + var response = _httpClient.GetAsync($"{_gatewayUrl}{path}").Result; + var body = response.Content.ReadAsStringAsync().Result; + var data = JsonConvert.DeserializeObject(body); + return Ok(ApiResponse.Success(data)); + } + catch (Exception ex) + { + return Ok(ApiResponse.Fail(50001, $"模拟器连接失败: {ex.Message}")); + } + } + + /// + /// POST转发到网关(9000端口),透传请求体 + /// + private IHttpActionResult ForwardToGatewayPost(string path) + { + try + { + var body = Request.Content.ReadAsStringAsync().Result; + var request = new HttpRequestMessage(HttpMethod.Post, $"{_gatewayUrl}{path}") + { + Content = new StringContent(body, Encoding.UTF8, "application/json") + }; + var response = _httpClient.SendAsync(request).Result; + var responseBody = response.Content.ReadAsStringAsync().Result; + var data = JsonConvert.DeserializeObject(responseBody); + return Ok(ApiResponse.Success(data)); + } + catch (Exception ex) + { + return Ok(ApiResponse.Fail(50001, $"模拟器连接失败: {ex.Message}")); + } + } + + /// + /// GET转发到单地址(动态端口) + /// + private IHttpActionResult ForwardToAddress(int port, string path) + { + try + { + var response = _httpClient.GetAsync($"http://localhost:{port}{path}").Result; + var body = response.Content.ReadAsStringAsync().Result; + var data = JsonConvert.DeserializeObject(body); + return Ok(ApiResponse.Success(data)); + } + catch (Exception ex) + { + return Ok(ApiResponse.Fail(50001, $"模拟地址(端口{port})连接失败: {ex.Message}")); + } + } + + /// + /// POST转发到单地址(动态端口),透传请求体 + /// + private IHttpActionResult ForwardToAddressPost(int port, string path) + { + try + { + var body = Request.Content.ReadAsStringAsync().Result; + var request = new HttpRequestMessage(HttpMethod.Post, $"http://localhost:{port}{path}") + { + Content = new StringContent(body, Encoding.UTF8, "application/json") + }; + var response = _httpClient.SendAsync(request).Result; + var responseBody = response.Content.ReadAsStringAsync().Result; + var data = JsonConvert.DeserializeObject(responseBody); + return Ok(ApiResponse.Success(data)); + } + catch (Exception ex) + { + return Ok(ApiResponse.Fail(50001, $"模拟地址(端口{port})连接失败: {ex.Message}")); + } + } + + #endregion + } +} diff --git a/src/CncWebApi/Infrastructure/ServiceResolver.cs b/src/CncWebApi/Infrastructure/ServiceResolver.cs index e521321..1467858 100644 --- a/src/CncWebApi/Infrastructure/ServiceResolver.cs +++ b/src/CncWebApi/Infrastructure/ServiceResolver.cs @@ -85,6 +85,8 @@ namespace CncWebApi.Infrastructure return new Controllers.CollectLogController( ResolveCollectLogService(), new CncRepository.Impl.Log.CollectRawRepository(_logConn)); + if (serviceType == typeof(Controllers.SimulatorController)) + return new Controllers.SimulatorController(); return null; } diff --git a/src/CncWebApi/Web.config b/src/CncWebApi/Web.config index c655eb3..5f7333d 100644 --- a/src/CncWebApi/Web.config +++ b/src/CncWebApi/Web.config @@ -20,6 +20,8 @@ + +