Commit e998b0c7ebe0123972f9a3aaaf43aacdba0ff75d

Authored by YunaiV
1 parent a2532013

【代码评审】IoT:评审 plugin 实现

yudao-module-iot/yudao-module-iot-api/pom.xml
@@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
22 <artifactId>yudao-common</artifactId> 22 <artifactId>yudao-common</artifactId>
23 </dependency> 23 </dependency>
24 <!-- PF4J --> 24 <!-- PF4J -->
  25 + <!-- TODO 芋艿:这个依赖,要不要放在 api 包 -->
25 <dependency> 26 <dependency>
26 <groupId>org.pf4j</groupId> 27 <groupId>org.pf4j</groupId>
27 <artifactId>pf4j-spring</artifactId> 28 <artifactId>pf4j-spring</artifactId>
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
@@ -7,6 +7,7 @@ import java.util.Map; @@ -7,6 +7,7 @@ import java.util.Map;
7 * 服务注册表 - 插架模块使用,无法使用 Spring 注入 7 * 服务注册表 - 插架模块使用,无法使用 Spring 注入
8 */ 8 */
9 public class ServiceRegistry { 9 public class ServiceRegistry {
  10 +
10 private static final Map<Class<?>, Object> services = new HashMap<>(); 11 private static final Map<Class<?>, Object> services = new HashMap<>();
11 12
12 /** 13 /**
@@ -31,4 +32,5 @@ public class ServiceRegistry { @@ -31,4 +32,5 @@ public class ServiceRegistry {
31 public static <T> T getService(Class<T> serviceClass) { 32 public static <T> T getService(Class<T> serviceClass) {
32 return (T) services.get(serviceClass); 33 return (T) services.get(serviceClass);
33 } 34 }
  35 +
34 } 36 }
35 \ No newline at end of file 37 \ No newline at end of file
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/DeviceDataApi.java renamed to yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
1 -package cn.iocoder.yudao.module.iot.api; 1 +package cn.iocoder.yudao.module.iot.api.device;
2 2
3 /** 3 /**
4 * 设备数据 API 4 * 设备数据 API
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -81,6 +81,7 @@ @@ -81,6 +81,7 @@
81 <dependency> 81 <dependency>
82 <groupId>org.pf4j</groupId> 82 <groupId>org.pf4j</groupId>
83 <artifactId>pf4j-spring</artifactId> 83 <artifactId>pf4j-spring</artifactId>
  84 + <!-- TODO @芋艿:可以放到 bom 里配置 -->
84 <exclusions> 85 <exclusions>
85 <exclusion> 86 <exclusion>
86 <groupId>org.slf4j</groupId> 87 <groupId>org.slf4j</groupId>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
1 package cn.iocoder.yudao.module.iot.api.device; 1 package cn.iocoder.yudao.module.iot.api.device;
2 2
3 -import cn.iocoder.yudao.module.iot.api.DeviceDataApi;  
4 import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService; 3 import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
5 import org.springframework.stereotype.Service; 4 import org.springframework.stereotype.Service;
6 import org.springframework.validation.annotation.Validated; 5 import org.springframework.validation.annotation.Validated;
@@ -15,11 +14,11 @@ import javax.annotation.Resource; @@ -15,11 +14,11 @@ import javax.annotation.Resource;
15 public class DeviceDataApiImpl implements DeviceDataApi { 14 public class DeviceDataApiImpl implements DeviceDataApi {
16 15
17 @Resource 16 @Resource
18 - private IotDeviceDataService iotDeviceDataService; 17 + private IotDeviceDataService deviceDataService;
19 18
20 @Override 19 @Override
21 public void saveDeviceData(String productKey, String deviceName, String message) { 20 public void saveDeviceData(String productKey, String deviceName, String message) {
22 - iotDeviceDataService.saveDeviceData(productKey, deviceName, message); 21 + deviceDataService.saveDeviceData(productKey, deviceName, message);
23 } 22 }
24 23
25 } 24 }
26 \ No newline at end of file 25 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  1 +/**
  2 + * 占位
  3 + *
  4 + * TODO 芋艿:后续删除
  5 + */
1 package cn.iocoder.yudao.module.iot.api; 6 package cn.iocoder.yudao.module.iot.api;
2 \ No newline at end of file 7 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java
1 package cn.iocoder.yudao.module.iot.framework.plugin; 1 package cn.iocoder.yudao.module.iot.framework.plugin;
2 2
3 -import cn.iocoder.yudao.module.iot.api.DeviceDataApi; 3 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
4 import cn.iocoder.yudao.module.iot.api.ServiceRegistry; 4 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
5 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
6 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Bean;
@@ -26,9 +26,10 @@ public class ServiceRegistryConfiguration { @@ -26,9 +26,10 @@ public class ServiceRegistryConfiguration {
26 /** 26 /**
27 * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成 27 * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成
28 */ 28 */
29 - @Bean("serviceRegistryInitializedMarker") 29 + @Bean("serviceRegistryInitializedMarker") // TODO @haohao:1)这个名字,可以搞个 public static final 常量;2)是不是 conditionBefore 啥
30 public Object serviceRegistryInitializedMarker() { 30 public Object serviceRegistryInitializedMarker() {
31 - // 返回任意对象即可,这里返回null都可以,但最好返回个实际对象 31 + // 返回任意对象即可,这里返回 null 都可以,但最好返回个实际对象
32 return new Object(); 32 return new Object();
33 } 33 }
  34 +
34 } 35 }
35 \ No newline at end of file 36 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
@@ -21,7 +21,7 @@ public interface IotDeviceDataService { @@ -21,7 +21,7 @@ public interface IotDeviceDataService {
21 * @param productKey 产品 key 21 * @param productKey 产品 key
22 * @param deviceName 设备名称 22 * @param deviceName 设备名称
23 * @param message 消息 23 * @param message 消息
24 - * <p>JSON 格式,参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">...</a> 24 + * <p>参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">JSON 格式</a>
25 */ 25 */
26 void saveDeviceData(String productKey, String deviceName, String message); 26 void saveDeviceData(String productKey, String deviceName, String message);
27 27
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java
@@ -13,6 +13,7 @@ import org.springframework.validation.annotation.Validated; @@ -13,6 +13,7 @@ import org.springframework.validation.annotation.Validated;
13 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 13 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
14 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS; 14 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
15 15
  16 +// TODO @haohao:可以搞个 plugin 包,然后把 plugininfo、plugininstance
16 /** 17 /**
17 * IoT 插件实例 Service 实现类 18 * IoT 插件实例 Service 实现类
18 * 19 *
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin;
2 2
3 import cn.hutool.json.JSONObject; 3 import cn.hutool.json.JSONObject;
4 import cn.hutool.json.JSONUtil; 4 import cn.hutool.json.JSONUtil;
5 -import cn.iocoder.yudao.module.iot.api.DeviceDataApi; 5 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
6 import io.netty.buffer.Unpooled; 6 import io.netty.buffer.Unpooled;
7 import io.netty.channel.ChannelFutureListener; 7 import io.netty.channel.ChannelFutureListener;
8 import io.netty.channel.ChannelHandlerContext; 8 import io.netty.channel.ChannelHandlerContext;
@@ -12,12 +12,9 @@ import io.netty.util.CharsetUtil; @@ -12,12 +12,9 @@ import io.netty.util.CharsetUtil;
12 12
13 /** 13 /**
14 * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。 14 * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
15 - * <p>  
16 - * 请求格式:  
17 - * POST /sys/{productKey}/{deviceName}/thing/event/property/post  
18 - * 请求体为 JSON 格式数据。  
19 - * <p>  
20 - * 返回结果为 JSON 格式,包含统一的 code、data、id、message、method、version 字段。 15 + *
  16 + * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
  17 + * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
21 */ 18 */
22 public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { 19 public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
23 20
@@ -29,10 +26,9 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -29,10 +26,9 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
29 26
30 @Override 27 @Override
31 protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { 28 protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
32 - String uri = request.uri();  
33 -  
34 // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post 29 // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
35 // 使用 "/" 拆分路径 30 // 使用 "/" 拆分路径
  31 + String uri = request.uri();
36 String[] parts = uri.split("/"); 32 String[] parts = uri.split("/");
37 33
38 /* 34 /*
@@ -52,25 +48,19 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -52,25 +48,19 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
52 && "event".equals(parts[5]) 48 && "event".equals(parts[5])
53 && "property".equals(parts[6]) 49 && "property".equals(parts[6])
54 && "post".equals(parts[7]); 50 && "post".equals(parts[7]);
55 -  
56 if (!isCorrectPath) { 51 if (!isCorrectPath) {
57 - // 如果路径不匹配,返回 404 错误  
58 writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found"); 52 writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");
59 return; 53 return;
60 } 54 }
61 -  
62 String productKey = parts[2]; 55 String productKey = parts[2];
63 String deviceName = parts[3]; 56 String deviceName = parts[3];
64 57
65 - // 从请求中获取原始数据 58 + // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象
66 String requestBody = request.content().toString(CharsetUtil.UTF_8); 59 String requestBody = request.content().toString(CharsetUtil.UTF_8);
67 -  
68 - // 尝试解析请求数据为 JSON 对象  
69 JSONObject jsonData; 60 JSONObject jsonData;
70 try { 61 try {
71 jsonData = JSONUtil.parseObj(requestBody); 62 jsonData = JSONUtil.parseObj(requestBody);
72 } catch (Exception e) { 63 } catch (Exception e) {
73 - // 数据不是合法的 JSON 格式,返回 400 错误  
74 JSONObject res = createResponseJson( 64 JSONObject res = createResponseJson(
75 400, 65 400,
76 new JSONObject(), 66 new JSONObject(),
@@ -82,8 +72,6 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -82,8 +72,6 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
82 writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString()); 72 writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
83 return; 73 return;
84 } 74 }
85 -  
86 - // 获取请求中的 id 字段,若不存在则为 null  
87 String id = jsonData.getStr("id", null); 75 String id = jsonData.getStr("id", null);
88 76
89 try { 77 try {
@@ -101,7 +89,6 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -101,7 +89,6 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
101 ); 89 );
102 writeResponse(ctx, HttpResponseStatus.OK, successRes.toString()); 90 writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
103 } catch (Exception e) { 91 } catch (Exception e) {
104 - // 保存数据过程中出现异常,返回 500 错误  
105 JSONObject errorRes = createResponseJson( 92 JSONObject errorRes = createResponseJson(
106 500, 93 500,
107 new JSONObject(), 94 new JSONObject(),
@@ -128,7 +115,7 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -128,7 +115,7 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
128 private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) { 115 private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) {
129 JSONObject res = new JSONObject(); 116 JSONObject res = new JSONObject();
130 res.set("code", code); 117 res.set("code", code);
131 - res.set("data", data != null ? data : new JSONObject()); // 确保 data 不为 null 118 + res.set("data", data != null ? data : new JSONObject());
132 res.set("id", id); 119 res.set("id", id);
133 res.set("message", message); 120 res.set("message", message);
134 res.set("method", method); 121 res.set("method", method);
@@ -137,24 +124,24 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; { @@ -137,24 +124,24 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
137 } 124 }
138 125
139 /** 126 /**
140 - * 向客户端返回 HTTP 响应的辅助方法。 127 + * 向客户端返回 HTTP 响应的辅助方法
141 * 128 *
142 * @param ctx 通道上下文 129 * @param ctx 通道上下文
143 * @param status HTTP 响应状态码(网络层面的) 130 * @param status HTTP 响应状态码(网络层面的)
144 * @param content 响应内容(JSON 字符串或其他文本) 131 * @param content 响应内容(JSON 字符串或其他文本)
145 */ 132 */
146 private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) { 133 private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
  134 + // 设置响应头为 JSON 类型和正确的编码
147 FullHttpResponse response = new DefaultFullHttpResponse( 135 FullHttpResponse response = new DefaultFullHttpResponse(
148 HttpVersion.HTTP_1_1, 136 HttpVersion.HTTP_1_1,
149 status, 137 status,
150 Unpooled.copiedBuffer(content, CharsetUtil.UTF_8) 138 Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)
151 ); 139 );
152 -  
153 - // 设置响应头为 JSON 类型和正确的编码  
154 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8"); 140 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
155 response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); 141 response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
156 142
157 // 发送响应并在发送完成后关闭连接 143 // 发送响应并在发送完成后关闭连接
158 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 144 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
159 } 145 }
  146 +
160 } 147 }
161 \ No newline at end of file 148 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java
1 package cn.iocoder.yudao.module.iot.plugin; 1 package cn.iocoder.yudao.module.iot.plugin;
2 2
3 -import cn.iocoder.yudao.module.iot.api.DeviceDataApi; 3 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
4 import cn.iocoder.yudao.module.iot.api.ServiceRegistry; 4 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
5 import io.netty.bootstrap.ServerBootstrap; 5 import io.netty.bootstrap.ServerBootstrap;
6 import io.netty.channel.*; 6 import io.netty.channel.*;
@@ -18,12 +18,14 @@ import java.util.concurrent.Executors; @@ -18,12 +18,14 @@ import java.util.concurrent.Executors;
18 public class HttpPlugin extends Plugin { 18 public class HttpPlugin extends Plugin {
19 19
20 private static final int PORT = 8092; 20 private static final int PORT = 8092;
  21 +
21 private final ExecutorService executorService; 22 private final ExecutorService executorService;
22 - private DeviceDataApi deviceDataApi; // 用于保存设备数据的 API 23 + private DeviceDataApi deviceDataApi;
23 24
24 public HttpPlugin(PluginWrapper wrapper) { 25 public HttpPlugin(PluginWrapper wrapper) {
25 super(wrapper); 26 super(wrapper);
26 - this.executorService = Executors.newSingleThreadExecutor(); // 创建单线程池 27 + // 创建单线程池
  28 + this.executorService = Executors.newSingleThreadExecutor();
27 } 29 }
28 30
29 @Override 31 @Override
@@ -44,10 +46,13 @@ public class HttpPlugin extends Plugin { @@ -44,10 +46,13 @@ public class HttpPlugin extends Plugin {
44 @Override 46 @Override
45 public void stop() { 47 public void stop() {
46 log.info("HttpPlugin.stop()"); 48 log.info("HttpPlugin.stop()");
47 - executorService.shutdownNow(); // 停止线程池 49 + // 停止线程池
  50 + executorService.shutdownNow();
48 } 51 }
49 52
50 - // 启动 HTTP 服务 53 + /**
  54 + * 启动 HTTP 服务
  55 + */
51 private void startHttpServer() { 56 private void startHttpServer() {
52 EventLoopGroup bossGroup = new NioEventLoopGroup(1); 57 EventLoopGroup bossGroup = new NioEventLoopGroup(1);
53 EventLoopGroup workerGroup = new NioEventLoopGroup(); 58 EventLoopGroup workerGroup = new NioEventLoopGroup();
@@ -56,7 +61,8 @@ public class HttpPlugin extends Plugin { @@ -56,7 +61,8 @@ public class HttpPlugin extends Plugin {
56 ServerBootstrap bootstrap = new ServerBootstrap(); 61 ServerBootstrap bootstrap = new ServerBootstrap();
57 bootstrap.group(bossGroup, workerGroup) 62 bootstrap.group(bossGroup, workerGroup)
58 .channel(NioServerSocketChannel.class) 63 .channel(NioServerSocketChannel.class)
59 - .childHandler(new ChannelInitializer<Channel>() { 64 + .childHandler(new ChannelInitializer<>() {
  65 +
60 @Override 66 @Override
61 protected void initChannel(Channel channel) { 67 protected void initChannel(Channel channel) {
62 channel.pipeline().addLast(new HttpServerCodec()); 68 channel.pipeline().addLast(new HttpServerCodec());
@@ -64,6 +70,7 @@ public class HttpPlugin extends Plugin { @@ -64,6 +70,7 @@ public class HttpPlugin extends Plugin {
64 // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器 70 // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
65 channel.pipeline().addLast(new HttpHandler(deviceDataApi)); 71 channel.pipeline().addLast(new HttpHandler(deviceDataApi));
66 } 72 }
  73 +
67 }); 74 });
68 75
69 // 绑定端口并启动服务器 76 // 绑定端口并启动服务器
@@ -78,4 +85,5 @@ public class HttpPlugin extends Plugin { @@ -78,4 +85,5 @@ public class HttpPlugin extends Plugin {
78 workerGroup.shutdownGracefully(); 85 workerGroup.shutdownGracefully();
79 } 86 }
80 } 87 }
  88 +
81 } 89 }