DeviceQuery.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. package com.genersoft.iot.vmp.gb28181.controller;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.genersoft.iot.vmp.conf.DynamicTask;
  4. import com.genersoft.iot.vmp.conf.exception.ControllerException;
  5. import com.genersoft.iot.vmp.conf.security.JwtUtils;
  6. import com.genersoft.iot.vmp.gb28181.bean.Device;
  7. import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  8. import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
  9. import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
  10. import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
  11. import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
  12. import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
  13. import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
  14. import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
  15. import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  16. import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
  17. import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  18. import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
  19. import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
  20. import com.github.pagehelper.PageInfo;
  21. import io.swagger.v3.oas.annotations.Operation;
  22. import io.swagger.v3.oas.annotations.Parameter;
  23. import io.swagger.v3.oas.annotations.security.SecurityRequirement;
  24. import io.swagger.v3.oas.annotations.tags.Tag;
  25. import lombok.extern.slf4j.Slf4j;
  26. import org.apache.commons.compress.utils.IOUtils;
  27. import org.apache.ibatis.annotations.Options;
  28. import org.springframework.beans.factory.annotation.Autowired;
  29. import org.springframework.http.HttpStatus;
  30. import org.springframework.http.MediaType;
  31. import org.springframework.http.ResponseEntity;
  32. import org.springframework.util.Assert;
  33. import org.springframework.util.ObjectUtils;
  34. import org.springframework.web.bind.annotation.*;
  35. import org.springframework.web.context.request.async.DeferredResult;
  36. import javax.servlet.ServletOutputStream;
  37. import javax.servlet.http.HttpServletResponse;
  38. import javax.sip.InvalidArgumentException;
  39. import javax.sip.SipException;
  40. import java.io.File;
  41. import java.io.IOException;
  42. import java.io.InputStream;
  43. import java.nio.file.Files;
  44. import java.text.ParseException;
  45. import java.util.HashMap;
  46. import java.util.Map;
  47. import java.util.Set;
  48. import java.util.UUID;
  49. @Tag(name = "国标设备查询", description = "国标设备查询")
  50. @SuppressWarnings("rawtypes")
  51. @Slf4j
  52. @RestController
  53. @RequestMapping("/api/device/query")
  54. public class DeviceQuery {
  55. @Autowired
  56. private IDeviceChannelService deviceChannelService;
  57. @Autowired
  58. private IInviteStreamService inviteStreamService;
  59. @Autowired
  60. private SIPCommander cmder;
  61. @Autowired
  62. private DeferredResultHolder resultHolder;
  63. @Autowired
  64. private IDeviceService deviceService;
  65. @Autowired
  66. private DynamicTask dynamicTask;
  67. /**
  68. * 使用ID查询国标设备
  69. * @param deviceId 国标ID
  70. * @return 国标设备
  71. */
  72. @Operation(summary = "查询国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER))
  73. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  74. @GetMapping("/devices/{deviceId}")
  75. public Device devices(@PathVariable String deviceId){
  76. return deviceService.getDeviceByDeviceId(deviceId);
  77. }
  78. /**
  79. * 分页查询国标设备
  80. * @param page 当前页
  81. * @param count 每页查询数量
  82. * @return 分页国标列表
  83. */
  84. @Operation(summary = "分页查询国标设备", security = @SecurityRequirement(name = JwtUtils.HEADER))
  85. @Parameter(name = "page", description = "当前页", required = true)
  86. @Parameter(name = "count", description = "每页查询数量", required = true)
  87. @Parameter(name = "query", description = "搜索", required = false)
  88. @Parameter(name = "status", description = "状态", required = false)
  89. @GetMapping("/devices")
  90. @Options()
  91. public PageInfo<Device> devices(int page, int count, String query, Boolean status){
  92. if (ObjectUtils.isEmpty(query)){
  93. query = null;
  94. }
  95. return deviceService.getAll(page, count, query, status);
  96. }
  97. /**
  98. * 分页查询通道数
  99. */
  100. @GetMapping("/devices/{deviceId}/channels")
  101. @Operation(summary = "分页查询通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
  102. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  103. @Parameter(name = "page", description = "当前页", required = true)
  104. @Parameter(name = "count", description = "每页查询数量", required = true)
  105. @Parameter(name = "query", description = "查询内容")
  106. @Parameter(name = "online", description = "是否在线")
  107. @Parameter(name = "channelType", description = "设备/子目录-> false/true")
  108. public PageInfo<DeviceChannel> channels(@PathVariable String deviceId,
  109. int page, int count,
  110. @RequestParam(required = false) String query,
  111. @RequestParam(required = false) Boolean online,
  112. @RequestParam(required = false) Boolean channelType) {
  113. if (ObjectUtils.isEmpty(query)) {
  114. query = null;
  115. }
  116. return deviceChannelService.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count);
  117. }
  118. /**
  119. * 同步设备通道
  120. */
  121. @Operation(summary = "同步设备通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
  122. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  123. @GetMapping("/devices/{deviceId}/sync")
  124. public WVPResult<SyncStatus> devicesSync(@PathVariable String deviceId){
  125. if (log.isDebugEnabled()) {
  126. log.debug("设备通道信息同步API调用,deviceId:" + deviceId);
  127. }
  128. Device device = deviceService.getDeviceByDeviceId(deviceId);
  129. boolean status = deviceService.isSyncRunning(deviceId);
  130. // 已存在则返回进度
  131. if (deviceService.isSyncRunning(deviceId)) {
  132. SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId);
  133. WVPResult wvpResult = new WVPResult();
  134. if (channelSyncStatus.getErrorMsg() != null) {
  135. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  136. wvpResult.setMsg(channelSyncStatus.getErrorMsg());
  137. }else if (channelSyncStatus.getTotal() == null || channelSyncStatus.getTotal() == 0){
  138. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  139. wvpResult.setMsg("等待通道信息...");
  140. }else {
  141. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  142. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  143. wvpResult.setData(channelSyncStatus);
  144. }
  145. return wvpResult;
  146. }
  147. deviceService.sync(device);
  148. WVPResult<SyncStatus> wvpResult = new WVPResult<>();
  149. wvpResult.setCode(0);
  150. wvpResult.setMsg("开始同步");
  151. return wvpResult;
  152. }
  153. /**
  154. * 移除设备
  155. * @param deviceId 设备id
  156. * @return
  157. */
  158. @Operation(summary = "移除设备", security = @SecurityRequirement(name = JwtUtils.HEADER))
  159. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  160. @DeleteMapping("/devices/{deviceId}/delete")
  161. public String delete(@PathVariable String deviceId){
  162. if (log.isDebugEnabled()) {
  163. log.debug("设备信息删除API调用,deviceId:" + deviceId);
  164. }
  165. // 清除redis记录
  166. boolean isSuccess = deviceService.delete(deviceId);
  167. if (isSuccess) {
  168. inviteStreamService.clearInviteInfo(deviceId);
  169. // 停止此设备的订阅更新
  170. Set<String> allKeys = dynamicTask.getAllKeys();
  171. for (String key : allKeys) {
  172. if (key.startsWith(deviceId)) {
  173. Runnable runnable = dynamicTask.get(key);
  174. if (runnable instanceof ISubscribeTask) {
  175. ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
  176. subscribeTask.stop(null);
  177. }
  178. dynamicTask.stop(key);
  179. }
  180. }
  181. JSONObject json = new JSONObject();
  182. json.put("deviceId", deviceId);
  183. return json.toString();
  184. } else {
  185. log.warn("设备信息删除API调用失败!");
  186. throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备信息删除API调用失败!");
  187. }
  188. }
  189. /**
  190. * 分页查询子目录通道
  191. * @param deviceId 通道id
  192. * @param channelId 通道id
  193. * @param page 当前页
  194. * @param count 每页条数
  195. * @param query 查询内容
  196. * @param online 是否在线
  197. * @param channelType 通道类型
  198. * @return 子通道列表
  199. */
  200. @Operation(summary = "分页查询子目录通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
  201. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  202. @Parameter(name = "channelId", description = "通道国标编号", required = true)
  203. @Parameter(name = "page", description = "当前页", required = true)
  204. @Parameter(name = "count", description = "每页查询数量", required = true)
  205. @Parameter(name = "query", description = "查询内容")
  206. @Parameter(name = "online", description = "是否在线")
  207. @Parameter(name = "channelType", description = "设备/子目录-> false/true")
  208. @GetMapping("/sub_channels/{deviceId}/{channelId}/channels")
  209. public PageInfo<DeviceChannel> subChannels(@PathVariable String deviceId,
  210. @PathVariable String channelId,
  211. int page,
  212. int count,
  213. @RequestParam(required = false) String query,
  214. @RequestParam(required = false) Boolean online,
  215. @RequestParam(required = false) Boolean channelType){
  216. DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId,channelId);
  217. if (deviceChannel == null) {
  218. PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>();
  219. return deviceChannelPageResult;
  220. }
  221. return deviceChannelService.getSubChannels(deviceChannel.getDeviceDbId(), channelId, query, channelType, online, page, count);
  222. }
  223. @Operation(summary = "开启/关闭通道的音频", security = @SecurityRequirement(name = JwtUtils.HEADER))
  224. @Parameter(name = "channelId", description = "通道的数据库ID", required = true)
  225. @Parameter(name = "audio", description = "开启/关闭音频", required = true)
  226. @PostMapping("/channel/audio")
  227. public void changeAudio(Integer channelId, Boolean audio){
  228. Assert.notNull(channelId, "通道的数据库ID不可为NULL");
  229. Assert.notNull(audio, "开启/关闭音频不可为NULL");
  230. deviceChannelService.changeAudio(channelId, audio);
  231. }
  232. @Operation(summary = "修改通道的码流类型", security = @SecurityRequirement(name = JwtUtils.HEADER))
  233. @PostMapping("/channel/stream/identification/update/")
  234. public void updateChannelStreamIdentification(DeviceChannel channel){
  235. deviceChannelService.updateChannelStreamIdentification(channel);
  236. }
  237. /**
  238. * 修改数据流传输模式
  239. * @param deviceId 设备id
  240. * @param streamMode 数据流传输模式
  241. * @return
  242. */
  243. @Operation(summary = "修改数据流传输模式", security = @SecurityRequirement(name = JwtUtils.HEADER))
  244. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  245. @Parameter(name = "streamMode", description = "数据流传输模式, 取值:" +
  246. "UDP(udp传输),TCP-ACTIVE(tcp主动模式,暂不支持),TCP-PASSIVE(tcp被动模式)", required = true)
  247. @PostMapping("/transport/{deviceId}/{streamMode}")
  248. public void updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){
  249. Device device = deviceService.getDeviceByDeviceId(deviceId);
  250. device.setStreamMode(streamMode);
  251. deviceService.updateCustomDevice(device);
  252. }
  253. /**
  254. * 添加设备信息
  255. * @param device 设备信息
  256. * @return
  257. */
  258. @Operation(summary = "添加设备信息", security = @SecurityRequirement(name = JwtUtils.HEADER))
  259. @Parameter(name = "device", description = "设备", required = true)
  260. @PostMapping("/device/add/")
  261. public void addDevice(Device device){
  262. if (device == null || device.getDeviceId() == null) {
  263. throw new ControllerException(ErrorCode.ERROR400);
  264. }
  265. // 查看deviceId是否存在
  266. boolean exist = deviceService.isExist(device.getDeviceId());
  267. if (exist) {
  268. throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备编号已存在");
  269. }
  270. deviceService.addDevice(device);
  271. }
  272. /**
  273. * 更新设备信息
  274. * @param device 设备信息
  275. * @return
  276. */
  277. @Operation(summary = "更新设备信息", security = @SecurityRequirement(name = JwtUtils.HEADER))
  278. @Parameter(name = "device", description = "设备", required = true)
  279. @PostMapping("/device/update/")
  280. public void updateDevice(Device device){
  281. if (device == null || device.getDeviceId() == null || device.getId() <= 0) {
  282. throw new ControllerException(ErrorCode.ERROR400);
  283. }
  284. deviceService.updateCustomDevice(device);
  285. }
  286. /**
  287. * 设备状态查询请求API接口
  288. *
  289. * @param deviceId 设备id
  290. */
  291. @Operation(summary = "设备状态查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
  292. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  293. @GetMapping("/devices/{deviceId}/status")
  294. public DeferredResult<ResponseEntity<String>> deviceStatusApi(@PathVariable String deviceId) {
  295. if (log.isDebugEnabled()) {
  296. log.debug("设备状态查询API调用");
  297. }
  298. Device device = deviceService.getDeviceByDeviceId(deviceId);
  299. String uuid = UUID.randomUUID().toString();
  300. String key = DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId;
  301. DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
  302. if(device == null) {
  303. result.setResult(new ResponseEntity(String.format("设备%s不存在", deviceId),HttpStatus.OK));
  304. return result;
  305. }
  306. try {
  307. cmder.deviceStatusQuery(device, event -> {
  308. RequestMessage msg = new RequestMessage();
  309. msg.setId(uuid);
  310. msg.setKey(key);
  311. msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg));
  312. resultHolder.invokeResult(msg);
  313. });
  314. } catch (InvalidArgumentException | SipException | ParseException e) {
  315. log.error("[命令发送失败] 获取设备状态: {}", e.getMessage());
  316. throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
  317. }
  318. result.onTimeout(()->{
  319. log.warn(String.format("获取设备状态超时"));
  320. // 释放rtpserver
  321. RequestMessage msg = new RequestMessage();
  322. msg.setId(uuid);
  323. msg.setKey(key);
  324. msg.setData("Timeout. Device did not response to this command.");
  325. resultHolder.invokeResult(msg);
  326. });
  327. resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId, uuid, result);
  328. return result;
  329. }
  330. /**
  331. * 设备报警查询请求API接口
  332. * @param deviceId 设备id
  333. * @param startPriority 报警起始级别(可选)
  334. * @param endPriority 报警终止级别(可选)
  335. * @param alarmMethod 报警方式条件(可选)
  336. * @param alarmType 报警类型
  337. * @param startTime 报警发生起始时间(可选)
  338. * @param endTime 报警发生终止时间(可选)
  339. * @return true = 命令发送成功
  340. */
  341. @Operation(summary = "设备报警查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
  342. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  343. @Parameter(name = "startPriority", description = "报警起始级别")
  344. @Parameter(name = "endPriority", description = "报警终止级别")
  345. @Parameter(name = "alarmMethod", description = "报警方式条件")
  346. @Parameter(name = "alarmType", description = "报警类型")
  347. @Parameter(name = "startTime", description = "报警发生起始时间")
  348. @Parameter(name = "endTime", description = "报警发生终止时间")
  349. @GetMapping("/alarm/{deviceId}")
  350. public DeferredResult<ResponseEntity<String>> alarmApi(@PathVariable String deviceId,
  351. @RequestParam(required = false) String startPriority,
  352. @RequestParam(required = false) String endPriority,
  353. @RequestParam(required = false) String alarmMethod,
  354. @RequestParam(required = false) String alarmType,
  355. @RequestParam(required = false) String startTime,
  356. @RequestParam(required = false) String endTime) {
  357. if (log.isDebugEnabled()) {
  358. log.debug("设备报警查询API调用");
  359. }
  360. Device device = deviceService.getDeviceByDeviceId(deviceId);
  361. String key = DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId;
  362. String uuid = UUID.randomUUID().toString();
  363. try {
  364. cmder.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, event -> {
  365. RequestMessage msg = new RequestMessage();
  366. msg.setId(uuid);
  367. msg.setKey(key);
  368. msg.setData(String.format("设备报警查询失败,错误码: %s, %s",event.statusCode, event.msg));
  369. resultHolder.invokeResult(msg);
  370. });
  371. } catch (InvalidArgumentException | SipException | ParseException e) {
  372. log.error("[命令发送失败] 设备报警查询: {}", e.getMessage());
  373. throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
  374. }
  375. DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
  376. result.onTimeout(()->{
  377. log.warn(String.format("设备报警查询超时"));
  378. // 释放rtpserver
  379. RequestMessage msg = new RequestMessage();
  380. msg.setId(uuid);
  381. msg.setKey(key);
  382. msg.setData("设备报警查询超时");
  383. resultHolder.invokeResult(msg);
  384. });
  385. resultHolder.put(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId, uuid, result);
  386. return result;
  387. }
  388. @GetMapping("/{deviceId}/sync_status")
  389. @Operation(summary = "获取通道同步进度", security = @SecurityRequirement(name = JwtUtils.HEADER))
  390. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  391. public WVPResult<SyncStatus> getSyncStatus(@PathVariable String deviceId) {
  392. SyncStatus channelSyncStatus = deviceService.getChannelSyncStatus(deviceId);
  393. WVPResult<SyncStatus> wvpResult = new WVPResult<>();
  394. if (channelSyncStatus == null) {
  395. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  396. wvpResult.setMsg("同步不存在");
  397. }else if (channelSyncStatus.getErrorMsg() != null) {
  398. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  399. wvpResult.setMsg(channelSyncStatus.getErrorMsg());
  400. }else if (channelSyncStatus.getTotal() == null || channelSyncStatus.getTotal() == 0){
  401. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  402. wvpResult.setMsg("等待通道信息...");
  403. }else {
  404. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  405. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  406. wvpResult.setData(channelSyncStatus);
  407. }
  408. return wvpResult;
  409. }
  410. @GetMapping("/{deviceId}/subscribe_info")
  411. @Operation(summary = "获取设备的订阅状态", security = @SecurityRequirement(name = JwtUtils.HEADER))
  412. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  413. public WVPResult<Map<String, Integer>> getSubscribeInfo(@PathVariable String deviceId) {
  414. Set<String> allKeys = dynamicTask.getAllKeys();
  415. Map<String, Integer> dialogStateMap = new HashMap<>();
  416. for (String key : allKeys) {
  417. if (key.startsWith(deviceId)) {
  418. ISubscribeTask subscribeTask = (ISubscribeTask)dynamicTask.get(key);
  419. if (subscribeTask instanceof CatalogSubscribeTask) {
  420. dialogStateMap.put("catalog", 1);
  421. }else if (subscribeTask instanceof MobilePositionSubscribeTask) {
  422. dialogStateMap.put("mobilePosition", 1);
  423. }
  424. }
  425. }
  426. WVPResult<Map<String, Integer>> wvpResult = new WVPResult<>();
  427. wvpResult.setCode(0);
  428. wvpResult.setData(dialogStateMap);
  429. return wvpResult;
  430. }
  431. @GetMapping("/snap/{deviceId}/{channelId}")
  432. @Operation(summary = "请求截图")
  433. @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  434. @Parameter(name = "channelId", description = "通道国标编号", required = true)
  435. @Parameter(name = "mark", description = "标识", required = false)
  436. public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId, @RequestParam(required = false) String mark) {
  437. try {
  438. final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + (mark == null? ".jpg": ("_" + mark + ".jpg"))).toPath());
  439. resp.setContentType(MediaType.IMAGE_PNG_VALUE);
  440. ServletOutputStream outputStream = resp.getOutputStream();
  441. IOUtils.copy(in, resp.getOutputStream());
  442. in.close();
  443. outputStream.close();
  444. } catch (IOException e) {
  445. resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
  446. }
  447. }
  448. @GetMapping("/channel/raw")
  449. @Operation(summary = "国标通道编辑时的数据回显")
  450. @Parameter(name = "id", description = "通道的Id", required = true)
  451. public DeviceChannel getRawChannel(int id) {
  452. return deviceChannelService.getRawChannel(id);
  453. }
  454. }