diff --git a/src/main/java/com/iflytop/colortitration/app/common/annotation/CommandDebugMapping.java b/src/main/java/com/iflytop/colortitration/app/common/annotation/CommandDebugMapping.java new file mode 100644 index 0000000..89703fd --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/common/annotation/CommandDebugMapping.java @@ -0,0 +1,12 @@ +package com.iflytop.colortitration.app.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface CommandDebugMapping { + String value(); +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/colortitration/app/controller/CmdController.java b/src/main/java/com/iflytop/colortitration/app/controller/CmdController.java new file mode 100644 index 0000000..b64f652 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/controller/CmdController.java @@ -0,0 +1,67 @@ +package com.iflytop.colortitration.app.controller; + +import cn.hutool.json.JSONUtil; +import com.iflytop.colortitration.app.common.constant.CommandStatus; +import com.iflytop.colortitration.app.core.command.CommandHandler; +import com.iflytop.colortitration.app.core.command.CommandHandlerRegistry; +import com.iflytop.colortitration.app.model.dto.CommandDTO; +import com.iflytop.colortitration.app.websocket.server.DebugGenerator; +import com.iflytop.colortitration.app.websocket.server.WebSocketSender; +import com.iflytop.colortitration.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.CompletableFuture; + +@Tag(name = "前端业务指令") +@RestController +@RequestMapping("/api/cmd") +@RequiredArgsConstructor +@Slf4j +public class CmdController { + private final CommandHandlerRegistry registry; + private final WebSocketSender webSocketService; + + @Operation(summary = "前端统一调用一个接口") + @PostMapping + public Result controlMethod(@Valid @RequestBody CommandDTO commandDTO){ + String commandId = commandDTO.getCommandId(); + String command = commandDTO.getCommand(); + try { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到业务指令请求,开始处理")); + CommandHandler commandHandler = registry.getCommandHandler(command); + if (commandHandler == null) { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的业务指令")); + log.error("未找到对应的业务指令"); + return Result.failed(); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "业务指令开始执行")); + log.info("业务指令开始执行"); + CompletableFuture future = commandHandler.handle(commandDTO); + future.whenComplete((v, ex) -> { + if (ex != null) { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行业务指令发生异常", ex.getMessage())); + log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), ex); + } else { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "业务指令执行成功")); + log.info("业务指令执行成功"); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "业务指令执行结束")); + log.info("业务指令执行结束"); + }); + } catch (Exception e) { + log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), e); + return Result.failed(e.getMessage()); + } + return Result.success(); + } + + +} diff --git a/src/main/java/com/iflytop/colortitration/app/controller/CmdDebugController.java b/src/main/java/com/iflytop/colortitration/app/controller/CmdDebugController.java new file mode 100644 index 0000000..54ef5d8 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/controller/CmdDebugController.java @@ -0,0 +1,67 @@ +package com.iflytop.colortitration.app.controller; + +import cn.hutool.json.JSONUtil; +import com.iflytop.colortitration.app.common.constant.CommandStatus; +import com.iflytop.colortitration.app.core.command.CommandDebugHandlerRegistry; +import com.iflytop.colortitration.app.core.command.CommandHandler; +import com.iflytop.colortitration.app.model.dto.CommandDTO; +import com.iflytop.colortitration.app.websocket.server.DebugGenerator; +import com.iflytop.colortitration.app.websocket.server.WebSocketSender; +import com.iflytop.colortitration.common.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.CompletableFuture; + +@Tag(name = "前端调试指令") +@RestController +@RequestMapping("/api/debug/cmd") +@RequiredArgsConstructor +@Slf4j +public class CmdDebugController { + private final CommandDebugHandlerRegistry registry; + private final WebSocketSender webSocketService; + + @Operation(summary = "前端调试指令") + @PostMapping + public Result controlMethod(@Valid @RequestBody CommandDTO commandDTO) { + String commandId = commandDTO.getCommandId(); + String command = commandDTO.getCommand(); + try { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到调试指令请求,开始处理")); + CommandHandler commandHandler = registry.getCommandHandler(command); + if (commandHandler == null) { + webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的调试指令")); + log.error("未找到对应的调试指令"); + return Result.failed(); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "调试指令开始执行")); + log.info("调试指令开始执行"); + CompletableFuture future = commandHandler.handle(commandDTO); + future.whenComplete((v, ex) -> { + if (ex != null) { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行调试指令发生异常", ex.getMessage())); + log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), ex); + } else { + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "调试指令执行成功")); + log.info("调试指令执行成功"); + } + webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "调试指令执行结束")); + log.info("调试指令执行结束"); + }); + } catch (Exception e) { + log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), e); + return Result.failed(e.getMessage()); + } + return Result.success(); + } + + +} diff --git a/src/main/java/com/iflytop/colortitration/app/core/command/CommandDebugHandlerRegistry.java b/src/main/java/com/iflytop/colortitration/app/core/command/CommandDebugHandlerRegistry.java new file mode 100644 index 0000000..86fb850 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/core/command/CommandDebugHandlerRegistry.java @@ -0,0 +1,55 @@ +package com.iflytop.colortitration.app.core.command; + +import com.iflytop.colortitration.app.common.annotation.CommandDebugMapping; +import com.iflytop.colortitration.common.exception.AppException; +import com.iflytop.colortitration.common.result.ResultCode; +import io.micrometer.common.lang.NonNull; +import jakarta.annotation.PostConstruct; +import jakarta.validation.constraints.NotNull; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class CommandDebugHandlerRegistry implements ApplicationContextAware { + + private final Map handlerMap = new HashMap<>(); + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @PostConstruct + public void init() { + Map beans = applicationContext.getBeansWithAnnotation(CommandDebugMapping.class); + for (Object bean : beans.values()) { + // 获取实际目标类,而不是代理类 + Class targetClass = AopUtils.getTargetClass(bean); + CommandDebugMapping mapping = targetClass.getAnnotation(CommandDebugMapping.class); + if (mapping != null && bean instanceof CommandHandler) { + String mappingKey = mapping.value(); + handlerMap.put(mappingKey, (CommandHandler) bean); + } + } + } + + /** + * 通过模块名称和命令名称获取命令处理器 + * + * @param commandName 命令名称 + * @return 命令处理器 + */ + public CommandHandler getCommandHandler(@NotNull String commandName) { + if (!handlerMap.containsKey(commandName)) { + throw new AppException(ResultCode.COMMAND_NOT_FOUND); + } + return handlerMap.get(commandName); + } +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/colortitration/app/core/command/CommandHandlerRegistry.java b/src/main/java/com/iflytop/colortitration/app/core/command/CommandHandlerRegistry.java new file mode 100644 index 0000000..65b74e8 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/core/command/CommandHandlerRegistry.java @@ -0,0 +1,55 @@ +package com.iflytop.colortitration.app.core.command; + +import com.iflytop.colortitration.app.common.annotation.CommandMapping; +import com.iflytop.colortitration.common.exception.AppException; +import com.iflytop.colortitration.common.result.ResultCode; +import io.micrometer.common.lang.NonNull; +import jakarta.annotation.PostConstruct; +import jakarta.validation.constraints.NotNull; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class CommandHandlerRegistry implements ApplicationContextAware { + + private final Map handlerMap = new HashMap<>(); + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @PostConstruct + public void init() { + Map beans = applicationContext.getBeansWithAnnotation(CommandMapping.class); + for (Object bean : beans.values()) { + // 获取实际目标类,而不是代理类 + Class targetClass = AopUtils.getTargetClass(bean); + CommandMapping mapping = targetClass.getAnnotation(CommandMapping.class); + if (mapping != null && bean instanceof CommandHandler) { + String mappingKey = mapping.value(); + handlerMap.put(mappingKey, (CommandHandler) bean); + } + } + } + + /** + * 通过模块名称和命令名称获取命令处理器 + * + * @param commandName 命令名称 + * @return 命令处理器 + */ + public CommandHandler getCommandHandler(@NotNull String commandName){ + if (!handlerMap.containsKey(commandName)) { + throw new AppException(ResultCode.COMMAND_NOT_FOUND); + } + return handlerMap.get(commandName); + } +} \ No newline at end of file