浏览代码

初始化

欧阳劲驰 9 月之前
当前提交
c3cf9ecb0e
共有 85 个文件被更改,包括 4482 次插入0 次删除
  1. 41 0
      .gitignore
  2. 60 0
      iot-common/iot-common-auth/pom.xml
  3. 30 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/AuthenticationMetadata.java
  4. 58 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/AuthenticationProperties.java
  5. 46 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/AuthenticationUtil.java
  6. 51 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/CaptchaController.java
  7. 61 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/CaptchaFilter.java
  8. 98 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/CaptchaService.java
  9. 53 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/JwtToken.java
  10. 32 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/LoginVo.java
  11. 63 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PasswordAuthenticationFilter.java
  12. 79 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PasswordAuthenticationProvider.java
  13. 70 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PasswordToken.java
  14. 62 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PhoneAuthenticationFilter.java
  15. 78 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PhoneAuthenticationProvider.java
  16. 79 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PhoneToken.java
  17. 238 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/SecurityConfig.java
  18. 75 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/TokenFilter.java
  19. 103 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/TokenService.java
  20. 107 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/UserDetailsDto.java
  21. 40 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/UserDetailsServiceImpl.java
  22. 24 0
      iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/mapper/UserDetailsMapper.java
  23. 63 0
      iot-common/iot-common-core/pom.xml
  24. 38 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/config/InfluxDBConfig.java
  25. 18 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/InfluxdbMetadata.java
  26. 19 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/ModbusMetadata.java
  27. 16 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/NettyMetadata.java
  28. 14 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/TemplateMetadata.java
  29. 25 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/base/BaseVo.java
  30. 116 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/base/Result.java
  31. 30 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Command.java
  32. 40 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/CommandModbus.java
  33. 28 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/DataInfo.java
  34. 35 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/DataValue.java
  35. 77 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Destination.java
  36. 71 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Equipment.java
  37. 59 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/PointRule.java
  38. 33 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Protocol.java
  39. 43 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Template.java
  40. 34 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/EndiannessEnum.java
  41. 21 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/ProtocolEnum.java
  42. 37 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/RwFlagEnum.java
  43. 35 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/ValueTypeEnum.java
  44. 33 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/properties/InfluxdbDBProperties.java
  45. 35 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/BeanUtil.java
  46. 48 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/HexUtil.java
  47. 67 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/InfluxDBUtil.java
  48. 109 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/JsonUtil.java
  49. 275 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/ModbusUtil.java
  50. 54 0
      iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/ResponseUtil.java
  51. 32 0
      iot-common/iot-common-driver/pom.xml
  52. 21 0
      iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/context/HandlerContext.java
  53. 29 0
      iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/handle/DecoderHandler.java
  54. 24 0
      iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/handle/EncoderHandler.java
  55. 44 0
      iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/handle/InboundHandler.java
  56. 94 0
      iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/service/DriverService.java
  57. 25 0
      iot-common/pom.xml
  58. 24 0
      iot-driver/iot-driver-http/pom.xml
  59. 10 0
      iot-driver/iot-driver-http/src/main/java/com/shkpr/iot/driver/http/DriverHttpProperties.java
  60. 29 0
      iot-driver/iot-driver-mqtt/pom.xml
  61. 16 0
      iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/common/config/MqttConfig.java
  62. 43 0
      iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/common/context/MqttHandlerContext.java
  63. 18 0
      iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/common/properties/MqttProperties.java
  64. 48 0
      iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/handler/MqttDecoderHandler.java
  65. 86 0
      iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/handler/MqttInboundHandler.java
  66. 301 0
      iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/service/MqttService.java
  67. 36 0
      iot-driver/pom.xml
  68. 20 0
      iot-modules/pom.xml
  69. 51 0
      iot-server/iot-server-data/pom.xml
  70. 48 0
      iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/DriverInitialRunner.java
  71. 23 0
      iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/IotServerDataApplication.java
  72. 19 0
      iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/DataService.java
  73. 20 0
      iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/EquipmentService.java
  74. 58 0
      iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/impl/DataServiceImpl.java
  75. 73 0
      iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/impl/EquipmentServiceImpl.java
  76. 11 0
      iot-server/iot-server-data/src/main/resources/application-baokang.yml
  77. 11 0
      iot-server/iot-server-data/src/main/resources/application-local.yml
  78. 8 0
      iot-server/iot-server-data/src/main/resources/application.yml
  79. 35 0
      iot-server/iot-server-data/src/main/resources/logback-spring.xml
  80. 22 0
      iot-server/iot-server-data/src/test/java/TestApplication.java
  81. 29 0
      iot-server/iot-server-manager/pom.xml
  82. 18 0
      iot-server/iot-server-manager/src/main/java/com/shkpr/iot/server/manager/IotServerManagerApplication.java
  83. 2 0
      iot-server/iot-server-manager/src/main/resources/application.yml
  84. 38 0
      iot-server/pom.xml
  85. 95 0
      pom.xml

+ 41 - 0
.gitignore

@@ -0,0 +1,41 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### maven ###
+.mvn
+mvnw
+Mvnw.cmd
+
+### 日志 ###
+logs/

+ 60 - 0
iot-common/iot-common-auth/pom.xml

@@ -0,0 +1,60 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-common</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+
+    <!--项目名-->
+    <artifactId>iot-common-auth</artifactId>
+    <!--项目显示名-->
+    <name>iot-common-auth</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-通用认证模块</description>
+    <!--打包类型-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--核心包-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-common-core</artifactId>
+            <version>${iot.common.version}</version>
+        </dependency>
+        <!--spring-security-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <!--mybatis-->
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>${mybatis.spring.version}</version>
+        </dependency>
+        <!--jjwt-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <!--commons-rng-->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-rng-simple</artifactId>
+            <version>${commons.rng.version}</version>
+        </dependency>
+        <!--easy-captcha-->
+        <dependency>
+            <groupId>com.pig4cloud.plugin</groupId>
+            <artifactId>captcha-core</artifactId>
+            <version>2.2.2</version>
+        </dependency>
+    </dependencies>
+</project>

+ 30 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/AuthenticationMetadata.java

@@ -0,0 +1,30 @@
+package com.shkpr.iot.common.auth;
+
+/**
+ * 常量
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface AuthenticationMetadata {
+    /**
+     * 认证主体
+     */
+    String PRINCIPAL = "principal";
+    /**
+     * 获取验证码
+     */
+    String CAPTCHA_GET_URI = "/auth/captcha-get";
+    /**
+     * 密码登录
+     */
+    String PASSWORD_LOGIN_URI = "/auth/password";
+    /**
+     * 手机登录
+     */
+    String PHONE_LOGIN_URI = "/auth/phone";
+    /**
+     * 登出
+     */
+    String LOGOUT_URI = "/auth/logout";
+}

+ 58 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/AuthenticationProperties.java

@@ -0,0 +1,58 @@
+package com.shkpr.iot.common.auth;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 认证属性
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "auth")
+public class AuthenticationProperties {
+    /**
+     * 公开的url,ant格式
+     */
+    private List<String> publicUrl = Arrays.asList(AuthenticationMetadata.CAPTCHA_GET_URI,AuthenticationMetadata.PASSWORD_LOGIN_URI,
+            AuthenticationMetadata.PHONE_LOGIN_URI, AuthenticationMetadata.LOGOUT_URI);
+    /**
+     * 管理员权限url,ant格式
+     */
+    private List<String> adminUrl;
+    /**
+     * token名
+     */
+    private String tokenName = "Authorization";
+    /**
+     * token密钥
+     */
+    private String tokenKey = "TRICP_ALAM_DMA";
+    /**
+     * 验证码名
+     */
+    private String captchaName = "captcha";
+    /**
+     * 验证码key
+     */
+    private String captchaKey = "captchaKey";
+    /**
+     * token过期时间
+     */
+    private Duration tokenTimeout = Duration.ofDays(30);
+    /**
+     * 验证码过期时间
+     */
+    private Duration captchaTimeout = Duration.ofMinutes(30);
+    /**
+     * token续期时间
+     */
+    private Duration tokenRenewalTime;
+}

+ 46 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/AuthenticationUtil.java

@@ -0,0 +1,46 @@
+package com.shkpr.iot.common.auth;
+
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+/**
+ * Security工具类
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class AuthenticationUtil {
+    /**
+     * 设置认证信息
+     *
+     * @param authentication 认证信息
+     */
+    public static void setAuthentication(Authentication authentication) {
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+    }
+
+    /**
+     * 获取当前登陆的用户
+     *
+     * @return 用户
+     */
+    public static UserDetailsDto getUser() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof UserDetailsDto) {
+            return (UserDetailsDto) authentication.getPrincipal();
+        }
+        return null;
+    }
+
+    /**
+     * 获取当前登陆的用户的id
+     *
+     * @return 用户
+     */
+    public static Long getUserId() {
+        UserDetailsDto user = getUser();
+        //如用户不为空,则返回组织id
+        return (user != null) ? user.getId() : null;
+    }
+}

+ 51 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/CaptchaController.java

@@ -0,0 +1,51 @@
+package com.shkpr.iot.common.auth;
+
+import com.shkpr.iot.common.core.domain.base.Result;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.UUID;
+
+/**
+ * 验证码controller
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Controller
+public class CaptchaController {
+    final
+    CaptchaService captchaService;
+
+    public CaptchaController(CaptchaService captchaService) {
+        this.captchaService = captchaService;
+    }
+
+    /**
+     * 获取验证码
+     *
+     * @param captchaKey 验证码key
+     * @return 验证码图和key
+     */
+    @GetMapping(AuthenticationMetadata.CAPTCHA_GET_URI)
+    @ResponseBody
+    public Result<LoginVo> getCaptcha(@RequestParam(value = "captchaKey", required = false) String captchaKey) {
+        //如原验证码key不为空,则删除原验证码,避免无用验证码
+        if (StringUtils.isNotBlank(captchaKey)) captchaService.deleteCaptcha(captchaKey);
+
+        //生成key
+        String key = UUID.randomUUID().toString();
+        //生成验证码
+        String base64 = captchaService.generateCaptcha(key);
+
+        //构建dto
+        LoginVo loginVo = new LoginVo();
+        loginVo.setCaptchaKey(key);
+        loginVo.setBase64(base64);
+
+        return Result.ok(loginVo);
+    }
+}

+ 61 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/CaptchaFilter.java

@@ -0,0 +1,61 @@
+package com.shkpr.iot.common.auth;
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.shkpr.iot.common.core.domain.base.Result;
+import org.springframework.http.MediaType;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 验证码过滤器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class CaptchaFilter extends OncePerRequestFilter {
+    final
+    AuthenticationProperties properties;
+    final
+    CaptchaService captchaService;
+
+    public CaptchaFilter(AuthenticationProperties properties, CaptchaService captchaService) {
+        this.properties = properties;
+        this.captchaService = captchaService;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        //拦截登陆请求
+        if (new AntPathMatcher().match(String.format("%s/auth/password", request.getContextPath()), request.getRequestURI())) {
+            //验证验证码
+            Boolean state = captchaService.verifyCaptcha(request.getParameter(properties.getCaptchaKey()),
+                    request.getParameter(properties.getCaptchaName()));
+            //如验证失败,则返回错误信息,并return
+            if (state != Boolean.TRUE) {
+                //返回类型UTF_8,json
+                response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+                //根据状态选择错误信息
+                String message = "";
+                if (state == null) message = "验证码已失效";
+                if (state == Boolean.FALSE) message = "验证码错误";
+                //返回错误信息
+                response.getWriter().write(new ObjectMapper().writeValueAsString(Result.fail(message)));
+                return;
+            }
+            captchaService.deleteCaptcha(request.getParameter(properties.getCaptchaKey()));
+        }
+        doFilter(request, response, filterChain);
+    }
+}

+ 98 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/CaptchaService.java

@@ -0,0 +1,98 @@
+package com.shkpr.iot.common.auth;
+
+import com.pig4cloud.captcha.ArithmeticCaptcha;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.rng.simple.RandomSource;
+import org.springframework.stereotype.Component;
+
+import java.awt.*;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 图形验证码服务
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Component
+@Slf4j
+public class CaptchaService {
+    //缓存
+    private final static Map<String, String> cache = new ConcurrentHashMap<>();
+    //线程池
+    private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
+
+    final
+    AuthenticationProperties properties;
+
+    public CaptchaService(AuthenticationProperties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * 生成 图形验证码
+     *
+     * @param key key
+     * @return 图形验证码base64
+     */
+    public String generateCaptcha(String key) {
+        //创建验证码图片
+        ArithmeticCaptcha captcha = new ArithmeticCaptcha(103, 38);
+        //加减乘
+        captcha.supportAlgorithmSign(4);
+        //难度
+        captcha.setDifficulty(20);
+        //设置字体样式,随机1-10
+        int i = RandomSource.MT.create().nextInt(10);
+        //排除2和7
+        try {
+            captcha.setFont(i == 2 || i == 7 ? 1 : i);
+        } catch (IOException | FontFormatException e) {
+            log.error("设置字体失败{}", e.getMessage());
+        }
+
+        //todo 删掉
+        System.out.println(key);
+        System.out.println(captcha.text());
+
+        //值存入缓存,超时10分钟
+        cache.put(key, captcha.text());
+        scheduler.schedule(() -> cache.remove(key)
+                , properties.getCaptchaTimeout().toMillis(), TimeUnit.MILLISECONDS);
+
+        return captcha.toBase64();
+    }
+
+    /**
+     * 验证图形验证码
+     *
+     * @param key   key
+     * @param value 验证码值
+     * @return 验证状态
+     */
+    public Boolean verifyCaptcha(String key, String value) {
+        if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) return null;
+
+        //从缓存中获取验证码
+        String cacheValue = cache.get(key);
+        if (StringUtils.isBlank(cacheValue)) return null;
+
+        //判断验证码
+        return cacheValue.equalsIgnoreCase(value);
+    }
+
+    /**
+     * 删除图形验证码
+     *
+     * @param key key
+     */
+    public void deleteCaptcha(String key) {
+        cache.remove(key);
+    }
+}

+ 53 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/JwtToken.java

@@ -0,0 +1,53 @@
+package com.shkpr.iot.common.auth;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * jwtToken
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class JwtToken extends AbstractAuthenticationToken implements Serializable {
+    /**
+     * 用户信息
+     */
+    private final Object principal;
+
+    public JwtToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
+        super(authorities);
+        this.principal = principal;
+        setAuthenticated(true);
+    }
+
+    /**
+     * 已认证
+     *
+     * @param principal   用户
+     * @param authorities 权限
+     * @return 已认证的token
+     */
+    public static JwtToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
+        return new JwtToken(principal, authorities);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object getCredentials() {
+        return "";
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object getPrincipal() {
+        return principal;
+    }
+}

+ 32 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/LoginVo.java

@@ -0,0 +1,32 @@
+package com.shkpr.iot.common.auth;
+
+import lombok.Data;
+
+/**
+ * 登录vo
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@Data
+public class LoginVo {
+    /**
+     * 验证码图片base64
+     */
+    private String base64;
+    /**
+     * 验证码Key
+     */
+    private String captchaKey;
+    /**
+     * token
+     */
+    private String token;
+
+    public LoginVo() {
+    }
+
+    public LoginVo(String token) {
+        this.token = token;
+    }
+}

+ 63 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PasswordAuthenticationFilter.java

@@ -0,0 +1,63 @@
+package com.shkpr.iot.common.auth;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 手机号认证过滤器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+public class PasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
+    /**
+     * 用户名
+     */
+    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
+    /**
+     * 密码
+     */
+    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
+    /**
+     * 认证路径
+     */
+    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(
+            AuthenticationMetadata.PASSWORD_LOGIN_URI, "POST");
+
+    public PasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
+        super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+        //方法判断
+        if (!request.getMethod().equals("POST")) {
+            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
+        }
+        //获取手机号并对null处理
+        String username = request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
+        username = username == null ? "" : username.trim();
+        //获取验证码并对null处理
+        String password = request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
+        password = password == null ? "" : password;
+        //封装token
+        PasswordToken authRequest = PasswordToken.unauthenticated(username, password);
+        setDetails(request, authRequest);
+        return this.getAuthenticationManager().authenticate(authRequest);
+    }
+
+    protected void setDetails(HttpServletRequest request, PasswordToken authRequest) {
+        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
+    }
+}

+ 79 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PasswordAuthenticationProvider.java

@@ -0,0 +1,79 @@
+package com.shkpr.iot.common.auth;
+
+import com.shkpr.iot.common.core.util.BeanUtil;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+
+/**
+ * 密码认证器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class PasswordAuthenticationProvider implements AuthenticationProvider {
+
+    final UserDetailsService userDetailsService;
+
+    public PasswordAuthenticationProvider(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    /**
+     * 验证
+     *
+     * @param authentication 验证前的token
+     * @return 验证后的token
+     * @throws AuthenticationException 认证异常
+     */
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        PasswordToken token = (PasswordToken) authentication;
+        if (ObjectUtils.anyNull(authentication.getDetails(), authentication.getCredentials())) {
+            throw new BadCredentialsException("参数不能为空");
+        }
+        //获取手机号和验证码
+        String username = token.getPrincipal().toString();
+        String password = token.getCredentials().toString();
+        //查询用户信息
+        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+        //判断用户是否存在
+        if (ObjectUtils.isEmpty(userDetails)) {
+            throw new UsernameNotFoundException("用户不存在");
+        }
+        //检查用户状态
+        AccountStatusUserDetailsChecker accountStatusUserDetailsChecker = new AccountStatusUserDetailsChecker();
+        accountStatusUserDetailsChecker.check(userDetails);
+        //判断密码是否正确
+        if (!PasswordEncoderFactories.createDelegatingPasswordEncoder().matches(password, userDetails.getPassword())) {
+            throw new BadCredentialsException("用户名密码错误");
+        }
+        //删除密码和权限
+        UserDetailsDto userDetailsDto = BeanUtil.copy(userDetails, UserDetailsDto.class);
+        if (userDetailsDto != null) {
+            userDetailsDto.setPassword(null);
+        }
+        //封装token
+        PasswordToken authenticated = PasswordToken.authenticated(userDetailsDto, userDetails.getAuthorities());
+        authenticated.setDetails(authentication.getDetails());
+        return authenticated;
+    }
+
+    /**
+     * 认证支持标识
+     *
+     * @param authentication 认证
+     * @return 是否支持
+     */
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return (PasswordToken.class.isAssignableFrom(authentication));
+    }
+}

+ 70 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PasswordToken.java

@@ -0,0 +1,70 @@
+package com.shkpr.iot.common.auth;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * 手机号认证token
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+public class PasswordToken extends AbstractAuthenticationToken implements Serializable {
+    /**
+     * 认证信息
+     */
+    private final Object principal;
+    /**
+     * 密钥
+     */
+    private final Object credentials;
+
+    public PasswordToken(Object principal, Object credentials) {
+        super(null);
+        this.principal = principal;
+        this.credentials = credentials;
+        setAuthenticated(false);
+    }
+
+    public PasswordToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
+        super(authorities);
+        this.principal = principal;
+        this.credentials = credentials;
+        super.setAuthenticated(true); // must use super, as we override
+    }
+
+    /**
+     * 未认证
+     *
+     * @param principal   用户名
+     * @param credentials 密码
+     * @return 未认证的token
+     */
+    public static PasswordToken unauthenticated(Object principal, Object credentials) {
+        return new PasswordToken(principal, credentials);
+    }
+
+    /**
+     * 已认证
+     *
+     * @param principal   用户
+     * @param authorities 权限
+     * @return 已认证的token
+     */
+    public static PasswordToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
+        return new PasswordToken(principal, null, authorities);
+    }
+
+    @Override
+    public Object getCredentials() {
+        return this.credentials;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return this.principal;
+    }
+}

+ 62 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PhoneAuthenticationFilter.java

@@ -0,0 +1,62 @@
+package com.shkpr.iot.common.auth;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 手机号认证过滤器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class PhoneAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
+    /**
+     * 手机号
+     */
+    public static final String SPRING_SECURITY_FORM_PHONE_NUMBER_KEY = "phoneNumber";
+    /**
+     * 验证码
+     */
+    public static final String SPRING_SECURITY_FORM_VERIFICATION_CODE_KEY = "verificationCode";
+    /**
+     * 认证路径
+     */
+    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(
+            AuthenticationMetadata.PHONE_LOGIN_URI, "POST");
+
+    public PhoneAuthenticationFilter(AuthenticationManager authenticationManager) {
+        super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+        //方法判断
+        if (!request.getMethod().equals("POST"))
+            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
+        //获取手机号并对null处理
+        String phoneNumber = request.getParameter(SPRING_SECURITY_FORM_PHONE_NUMBER_KEY);
+        phoneNumber = phoneNumber == null ? "" : phoneNumber.trim();
+        //获取验证码并对null处理
+        String verificationCode = request.getParameter(SPRING_SECURITY_FORM_VERIFICATION_CODE_KEY);
+        verificationCode = verificationCode == null ? "" : verificationCode;
+        //封装token
+        PhoneToken authRequest = PhoneToken.unauthenticated(phoneNumber, verificationCode);
+        setDetails(request, authRequest);
+        return this.getAuthenticationManager().authenticate(authRequest);
+    }
+
+    protected void setDetails(HttpServletRequest request, PhoneToken authRequest) {
+        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
+    }
+}

+ 78 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PhoneAuthenticationProvider.java

@@ -0,0 +1,78 @@
+package com.shkpr.iot.common.auth;
+
+import com.shkpr.iot.common.core.util.BeanUtil;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.security.authentication.AccountExpiredException;
+import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * 手机号认证器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class PhoneAuthenticationProvider implements AuthenticationProvider {
+
+    final UserDetailsService userDetailsService;
+
+    public PhoneAuthenticationProvider(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    /**
+     * 验证
+     *
+     * @param authentication 验证前的token
+     * @return 验证后的token
+     * @throws AuthenticationException 认证异常
+     */
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        PhoneToken token = (PhoneToken) authentication;
+        //获取手机号和验证码
+        String phoneNumber = token.getPhoneNumber();
+        String verificationCode = token.getVerificationCode();
+        //todo 校验验证码
+        Boolean state = false;
+        if (state != Boolean.TRUE) {
+            if (state == null) throw new AccountExpiredException("验证码已经过期,请重新发送验证码");
+            if (state == Boolean.FALSE) throw new BadCredentialsException("验证码不正确");
+        }
+        //查询用户信息
+        UserDetails userDetails = userDetailsService.loadUserByUsername(phoneNumber);
+        //判断用户是否存在
+        if (ObjectUtils.isEmpty(userDetails)) {
+            throw new UsernameNotFoundException("用户不存在");
+        }
+        //检查用户状态
+        AccountStatusUserDetailsChecker accountStatusUserDetailsChecker = new AccountStatusUserDetailsChecker();
+        accountStatusUserDetailsChecker.check(userDetails);
+        //删除密码和权限
+        UserDetailsDto userDetailsDto = BeanUtil.copy(userDetails, UserDetailsDto.class);
+        if (userDetailsDto != null) {
+            userDetailsDto.setPassword(null);
+        }
+        //封装token
+        PhoneToken authenticated = PhoneToken.authenticated(userDetails, userDetails.getAuthorities());
+        authenticated.setDetails(authentication.getDetails());
+        return authenticated;
+    }
+
+    /**
+     * 认证支持标识
+     *
+     * @param authentication 认证
+     * @return 是否支持
+     */
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return (PhoneToken.class.isAssignableFrom(authentication));
+    }
+}

+ 79 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/PhoneToken.java

@@ -0,0 +1,79 @@
+package com.shkpr.iot.common.auth;
+
+import lombok.Getter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * 手机号认证token
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+public class PhoneToken extends AbstractAuthenticationToken implements Serializable {
+    /**
+     * 手机号
+     */
+    @Getter
+    private final String phoneNumber;
+    /**
+     * 验证码
+     */
+    @Getter
+    private final String verificationCode;
+    /**
+     * 用户信息
+     */
+    private final Object principal;
+
+    public PhoneToken(String phoneNumber, String verificationCode, Object principal) {
+        super(null);
+        this.phoneNumber = phoneNumber;
+        this.verificationCode = verificationCode;
+        this.principal = principal;
+        setAuthenticated(false);
+    }
+
+    public PhoneToken(String phoneNumber, String verificationCode, Object principal, Collection<? extends GrantedAuthority> authorities) {
+        super(authorities);
+        this.phoneNumber = phoneNumber;
+        this.verificationCode = verificationCode;
+        this.principal = principal;
+        super.setAuthenticated(true);
+    }
+
+    /**
+     * 未认证
+     *
+     * @param phoneNumber      手机号
+     * @param verificationCode 验证码
+     * @return 未认证的token
+     */
+    public static PhoneToken unauthenticated(String phoneNumber, String verificationCode) {
+        return new PhoneToken(phoneNumber, verificationCode, null);
+    }
+
+    /**
+     * 已认证
+     *
+     * @param principal   用户
+     * @param authorities 权限
+     * @return 已认证的token
+     */
+    public static PhoneToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
+        return new PhoneToken(null, null, principal, authorities);
+    }
+
+    @Override
+    public Object getCredentials() {
+        return "";
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return principal;
+    }
+}

+ 238 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/SecurityConfig.java

@@ -0,0 +1,238 @@
+package com.shkpr.iot.common.auth;
+
+import com.shkpr.iot.common.core.domain.base.Result;
+import com.shkpr.iot.common.core.util.ResponseUtil;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Security配置
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@EnableWebSecurity
+@EnableMethodSecurity
+@Configuration
+@EnableConfigurationProperties(AuthenticationProperties.class)
+public class SecurityConfig {
+    final
+    AuthenticationProperties properties;
+    final
+    CaptchaService captchaService;
+    final
+    TokenService tokenService;
+    final
+    UserDetailsService userDetailsService;
+
+    public SecurityConfig(AuthenticationProperties properties, TokenService tokenService, CaptchaService captchaService
+            , UserDetailsService userDetailsService) {
+        this.properties = properties;
+        this.captchaService = captchaService;
+        this.tokenService = tokenService;
+        this.userDetailsService = userDetailsService;
+    }
+
+
+    /**
+     * 国际化配置
+     *
+     * @return 消息策略
+     */
+    @Bean
+    MessageSource messageSource() {
+        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
+        messageSource.addBasenames("classpath:org/springframework/security/messages_zh_CN");
+        return messageSource;
+    }
+
+    /**
+     * 密码编码器配置
+     * <p>目前是委托spring处理密码编码器</p>
+     *
+     * @return 密码编码器
+     */
+    @Bean
+    PasswordEncoder passwordEncoder() {
+        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+    }
+
+    /**
+     * 跨域配置
+     *
+     * @return 跨域配置
+     */
+    private CorsConfigurationSource corsConfigurationSource() {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration corsConfiguration = new CorsConfiguration();
+        //跨域方法
+        corsConfiguration.addAllowedMethod("GET");
+        corsConfiguration.addAllowedMethod("POST");
+        //公开返回头,便于刷新token
+        corsConfiguration.addExposedHeader(properties.getTokenName());
+        //同源配置
+        corsConfiguration.addAllowedOrigin("*");
+        //header配置
+        corsConfiguration.addAllowedHeader("*");
+        //跨域路径
+        source.registerCorsConfiguration("/**", corsConfiguration);
+        return source;
+    }
+
+    /**
+     * 认证管理器配置
+     * <p>使用的提供器管理,后续Provider都可以修改该方法</p>
+     *
+     * @return 认证管理器
+     */
+    public AuthenticationManager authenticationManager() {
+        return new ProviderManager(Arrays.asList(
+                new PasswordAuthenticationProvider(userDetailsService),
+                new PhoneAuthenticationProvider(userDetailsService)
+        ));
+    }
+
+    /**
+     * 密码认证过滤器配置
+     */
+    PasswordAuthenticationFilter passwordAuthenticationFilter() {
+        PasswordAuthenticationFilter passwordAuthenticationFilter = new PasswordAuthenticationFilter(authenticationManager());
+        //认证成功
+        passwordAuthenticationFilter.setAuthenticationSuccessHandler(this::authSuccessHandler);
+        //认证失败
+        passwordAuthenticationFilter.setAuthenticationFailureHandler(this::authFailureHandler);
+        return passwordAuthenticationFilter;
+    }
+
+    /**
+     * 手机认证过滤器配置
+     */
+    PhoneAuthenticationFilter phoneAuthenticationFilter() {
+        PhoneAuthenticationFilter phoneAuthenticationFilter = new PhoneAuthenticationFilter(authenticationManager());
+        //认证成功
+        phoneAuthenticationFilter.setAuthenticationSuccessHandler(this::authSuccessHandler);
+        //认证失败
+        phoneAuthenticationFilter.setAuthenticationFailureHandler(this::authFailureHandler);
+        return phoneAuthenticationFilter;
+    }
+
+    /**
+     * Security过滤链配置
+     *
+     * @param http http
+     * @return Security过滤链
+     * @throws Exception exception
+     */
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+        http
+                //过滤器配置
+                .addFilterBefore(new CaptchaFilter(properties, captchaService), UsernamePasswordAuthenticationFilter.class)
+                .addFilterAt(phoneAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
+                .addFilterAt(passwordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
+                .addFilterBefore(new TokenFilter(properties, tokenService), AnonymousAuthenticationFilter.class)
+                //认证配置
+                .authenticationProvider(new PasswordAuthenticationProvider(userDetailsService))
+                .authenticationProvider(new PhoneAuthenticationProvider(userDetailsService))
+                //权限配置
+                .authorizeHttpRequests(authorize -> authorize
+                        .requestMatchers(properties.getPublicUrl().stream()
+                                .map(AntPathRequestMatcher::new)
+                                .toArray(RequestMatcher[]::new)).permitAll()
+                        .anyRequest().authenticated()
+                )
+                //跨域防伪配置
+                .csrf(AbstractHttpConfigurer::disable)
+                //跨域配置
+                .cors(cors -> cors
+                        .configurationSource(corsConfigurationSource()))
+                //session认证配置
+                .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer
+                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                //header配置
+                .headers(header -> header
+                        .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
+                //认证配置
+                .formLogin(AbstractHttpConfigurer::disable)
+                //登出处理器
+                .logout(logout -> logout
+                        .logoutUrl(AuthenticationMetadata.LOGOUT_URI)
+                        .clearAuthentication(true)
+                        .addLogoutHandler((request, response, authentication) -> {
+
+                        })
+                        .logoutSuccessHandler((request, response, authentication) ->
+                                ResponseUtil.writeJson(response, Result.okMsg("退出成功"))
+                        ).permitAll()
+                )
+                //异常处理器
+                .exceptionHandling(exception -> exception
+                        //访问拒绝
+                        .accessDeniedHandler((request, response, accessDeniedException) ->
+                                ResponseUtil.writeJson(response, new Result<>(HttpStatus.FORBIDDEN.value(),
+                                        accessDeniedException.getMessage())))
+                        //未认证
+                        .authenticationEntryPoint((request, response, authenticationException) ->
+                                ResponseUtil.writeJson(response, new Result<>(HttpStatus.UNAUTHORIZED.value(),
+                                        authenticationException.getMessage())))
+                );
+        return http.build();
+    }
+
+    /**
+     * 认证成功处理器
+     *
+     * @param request        request
+     * @param response       response
+     * @param authentication 认证信息
+     * @throws IOException io异常
+     */
+    private void authSuccessHandler(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+        //生成token
+        String token = tokenService.generateToken(authentication);
+        ResponseUtil.writeJson(response, Result.ok("认证成功", new LoginVo(token)));
+    }
+
+    /**
+     * 认证失败处理器
+     *
+     * @param request  request
+     * @param response response
+     * @throws IOException io异常
+     */
+    private void authFailureHandler(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
+        ResponseUtil.writeJson(response, new Result<>(HttpStatus.FORBIDDEN.value(), exception.getMessage()));
+    }
+}

+ 75 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/TokenFilter.java

@@ -0,0 +1,75 @@
+package com.shkpr.iot.common.auth;
+
+import com.shkpr.iot.common.core.domain.base.Result;
+import com.shkpr.iot.common.core.util.ResponseUtil;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * token过滤器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+public class TokenFilter extends OncePerRequestFilter {
+    final
+    AuthenticationProperties properties;
+    final
+    TokenService tokenService;
+
+    public TokenFilter(AuthenticationProperties properties, TokenService tokenService) {
+        this.properties = properties;
+        this.tokenService = tokenService;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        //如果忽略列表包含该url,则直接放行
+        if (properties.getPublicUrl().stream()
+                .anyMatch(s -> new AntPathMatcher().match(String.format("%s%s", request.getContextPath(), s), request.getRequestURI()))) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+        //从header获取token
+        String token = StringUtils.defaultIfBlank(request.getHeader(properties.getTokenName()),
+                request.getParameter(properties.getTokenName()));
+        try {
+            //token为空
+            if (StringUtils.isBlank(token)) {
+                ResponseUtil.writeJson(response, Result.unauthorized("认证信息不存在"));
+                return;
+            }
+            //解析token
+            Claims claims = tokenService.parseToken(token);
+            //获取token携带认证主体
+            UserDetailsDto userDetails = claims.get(AuthenticationMetadata.PRINCIPAL, UserDetailsDto.class);
+            //如主体为空,则返回失效
+            if (ObjectUtils.isEmpty(userDetails)) {
+                ResponseUtil.writeJson(response, Result.unauthorized("认证信息失效"));
+                return;
+            }
+            //将令牌存入上下文
+            AuthenticationUtil.setAuthentication(JwtToken.authenticated(userDetails, userDetails.getAuthorities()));
+        } catch (Exception e) {
+            log.error("token解析失败:{}", e.getMessage());
+            ResponseUtil.writeJson(response, Result.unauthorized(e instanceof ExpiredJwtException ? "认证信息已过期" : "token异常"));
+            return;
+        }
+        filterChain.doFilter(request, response);
+    }
+}

+ 103 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/TokenService.java

@@ -0,0 +1,103 @@
+package com.shkpr.iot.common.auth;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * token服务
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Component
+@Slf4j
+public class TokenService {
+    final
+    AuthenticationProperties properties;
+    final
+    ObjectMapper objectMapper;
+
+    public TokenService(AuthenticationProperties properties, ObjectMapper objectMapper) {
+        this.properties = properties;
+        this.objectMapper = objectMapper;
+    }
+
+    /**
+     * 生成token
+     *
+     * @param authentication 认证令牌
+     * @return token
+     */
+    public String generateToken(Authentication authentication) {
+        //获取用户对象
+        UserDetailsDto user = (UserDetailsDto) authentication.getPrincipal();
+        //获取当前时间
+        Date now = new Date();
+        //获取超时时的时间
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(now);
+        cal.add(Calendar.SECOND, (int) properties.getTokenTimeout().getSeconds());
+        //构建token
+        return Jwts.builder()
+                //用户id
+                .setHeaderParam("userId", user.getId())
+                //密钥
+                .signWith(SignatureAlgorithm.HS256, properties.getTokenKey().getBytes(StandardCharsets.UTF_8))
+                //签发时间
+                .setIssuedAt(now)
+                //超时时间
+                .setExpiration(cal.getTime())
+                //用户信息
+                .claim(AuthenticationMetadata.PRINCIPAL, user)
+                .compact();
+    }
+
+    /**
+     * 解析head
+     *
+     * @param token token
+     * @return head
+     */
+    public Map<String, Object> parseHead(String token) {
+        Map<String, Object> map = null;
+        try {
+            //截取head部分
+            String headerPart = token.split("\\.")[0];
+            // 对头部信息进行 Base64 解码,得到 JSON 字符串
+            String headerJson = new String(Base64.getUrlDecoder().decode(headerPart));
+            map = objectMapper.readValue(headerJson, new TypeReference<Map<String, Object>>() {
+            });
+        } catch (JsonProcessingException e) {
+            //异常处理
+            log.error("tokenHead解析失败,抛出异常:{}", e.getMessage());
+        }
+        return map;
+    }
+
+    /**
+     * 解析token
+     *
+     * @param token token
+     * @return 参数
+     */
+    public Claims parseToken(String token) {
+        //根据密钥解析token
+        return Jwts.parser()
+                .setSigningKey(properties.getTokenKey().getBytes(StandardCharsets.UTF_8))
+                .parseClaimsJws(token)
+                .getBody();
+    }
+}

+ 107 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/UserDetailsDto.java

@@ -0,0 +1,107 @@
+package com.shkpr.iot.common.auth;
+
+import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * 用户
+ * <p>用于认证令牌</p>
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Data
+public class UserDetailsDto implements UserDetails {
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 账户未过期
+     */
+    private Boolean accountNonExpired;
+
+    /**
+     * 账户未锁定
+     */
+    private Boolean accountNonLocked;
+
+    /**
+     * 认证未过期
+     */
+    private Boolean credentialsNonExpired;
+
+    /**
+     * 激活状态
+     */
+    private Boolean enabled;
+
+    /**
+     * 管理员
+     */
+    private Boolean admin;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 邮箱
+     */
+    private String email;
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        if (this.admin != null && this.admin) return Collections.singletonList(new SimpleGrantedAuthority("admin"));
+        return null;
+    }
+
+    /**
+     * @return 账户过期状态
+     */
+    @Override
+    public boolean isAccountNonExpired() {
+        return this.accountNonExpired;
+    }
+
+    /**
+     * @return 账户锁定状态
+     */
+    @Override
+    public boolean isAccountNonLocked() {
+        return this.accountNonLocked;
+    }
+
+    /**
+     * @return 认证过期状态
+     */
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return this.credentialsNonExpired;
+    }
+
+    /**
+     * @return 激活状态
+     */
+    @Override
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+}

+ 40 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/UserDetailsServiceImpl.java

@@ -0,0 +1,40 @@
+package com.shkpr.iot.common.auth;
+
+import com.shkpr.iot.common.auth.mapper.UserDetailsMapper;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * 用户Service实现
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+    final
+    UserDetailsMapper userDetailsMapper;
+
+    public UserDetailsServiceImpl(UserDetailsMapper userDetailsMapper) {
+        this.userDetailsMapper = userDetailsMapper;
+    }
+
+    /**
+     * 加载用户
+     * <P>该方法重写spring用户获取方法</P>
+     *
+     * @param username 用户名
+     * @return 用户
+     * @throws UsernameNotFoundException 用户未找到
+     */
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        //查询用户
+        UserDetailsDto userDetails = userDetailsMapper.selectByLoginName(username);
+        if (ObjectUtils.isEmpty(userDetails)) throw new UsernameNotFoundException("用户不存在");
+        return userDetails;
+    }
+}

+ 24 - 0
iot-common/iot-common-auth/src/main/java/com/shkpr/iot/common/auth/mapper/UserDetailsMapper.java

@@ -0,0 +1,24 @@
+package com.shkpr.iot.common.auth.mapper;
+
+import com.shkpr.iot.common.auth.UserDetailsDto;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 用户mapper
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Mapper
+public interface UserDetailsMapper {
+    /**
+     * 根据登录名查询用户
+     *
+     * @param loginName 登录名
+     * @return 用户
+     */
+    @Select("select * from \"user\" where (username = #{loginName} or phone = #{loginName} or email = #{loginName})")
+    UserDetailsDto selectByLoginName(@Param("loginName") String loginName);
+}

+ 63 - 0
iot-common/iot-common-core/pom.xml

@@ -0,0 +1,63 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-common</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-common-core</artifactId>
+    <!--项目显示名-->
+    <name>iot-common-core</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-通用核心模块</description>
+    <!--打包方式-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <dependencies>
+        <!--spring-boot-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <!--spring-web-->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <!--tomcat-embed-->
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-core</artifactId>
+        </dependency>
+        <!--jackson-->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <!--commons-->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>${commons.collections4.version}</version>
+        </dependency>
+        <!--netty-common-->
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-common</artifactId>
+        </dependency>
+        <!--influxdb-->
+        <dependency>
+            <groupId>org.influxdb</groupId>
+            <artifactId>influxdb-java</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 38 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/config/InfluxDBConfig.java

@@ -0,0 +1,38 @@
+package com.shkpr.iot.common.core.config;
+
+import com.shkpr.iot.common.core.constants.InfluxdbMetadata;
+import com.shkpr.iot.common.core.properties.InfluxdbDBProperties;
+import org.influxdb.InfluxDB;
+import org.influxdb.InfluxDBFactory;
+import org.influxdb.dto.Query;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * influxdb配置
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Configuration
+@EnableConfigurationProperties(InfluxdbDBProperties.class)
+public class InfluxDBConfig {
+
+    /**
+     * influxdb客户端
+     *
+     * @param properties 属性
+     * @return influxdb客户端
+     */
+    @Bean
+    public InfluxDB influxDB(InfluxdbDBProperties properties) {
+        //连接数据库
+        InfluxDB influxDB = InfluxDBFactory.connect(properties.getUrl(), properties.getUsername(), properties.getPassword());
+        //创建数据库
+        influxDB.query(new Query(InfluxdbMetadata.CREATE_DATABASE + properties.getDatabase()));
+        //设置要使用的数据库
+        influxDB.setDatabase(properties.getDatabase());
+        return influxDB;
+    }
+}

+ 18 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/InfluxdbMetadata.java

@@ -0,0 +1,18 @@
+package com.shkpr.iot.common.core.constants;
+
+/**
+ * influxdb元数据
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface InfluxdbMetadata {
+    /**
+     * 创建数据库指令
+     */
+    String CREATE_DATABASE = "CREATE DATABASE ";
+    /**
+     * 设备编码表示
+     */
+    String FLAG_EQUIPMENT_CODE = "dev_id";
+}

+ 19 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/ModbusMetadata.java

@@ -0,0 +1,19 @@
+package com.shkpr.iot.common.core.constants;
+
+/**
+ * modbus常量
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface ModbusMetadata {
+    /**
+     * 读
+     */
+    Short READ = 3;
+
+    /**
+     * 写
+     */
+    Short WRITE = 6;
+}

+ 16 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/NettyMetadata.java

@@ -0,0 +1,16 @@
+package com.shkpr.iot.common.core.constants;
+
+import io.netty.util.AttributeKey;
+
+/**
+ * netty常量
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface NettyMetadata {
+    /**
+     * 设备id键
+     */
+    AttributeKey<Long> DEVICE_ID = AttributeKey.valueOf("device_id");
+}

+ 14 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/constants/TemplateMetadata.java

@@ -0,0 +1,14 @@
+package com.shkpr.iot.common.core.constants;
+
+/**
+ * 模版元数据
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface TemplateMetadata {
+    /**
+     * 数据索引分割符
+     */
+    String INDEX_SEPARATE = ":";
+}

+ 25 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/base/BaseVo.java

@@ -0,0 +1,25 @@
+package com.shkpr.iot.common.core.domain.base;
+
+import lombok.Data;
+
+/**
+ * 基本vo
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@Data
+public class BaseVo {
+    /**
+     * 当前页
+     */
+    private Integer page = 1;
+    /**
+     * 分页大小
+     */
+    private Integer limit = 10;
+    /**
+     * excel类型
+     */
+    private Integer excelType;
+}

+ 116 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/base/Result.java

@@ -0,0 +1,116 @@
+package com.shkpr.iot.common.core.domain.base;
+
+import lombok.Data;
+import org.springframework.http.HttpStatus;
+
+/**
+ * 返回
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@Data
+public class Result<T> {
+    /**
+     * 状态码
+     */
+    private int code = HttpStatus.OK.value();
+
+    /**
+     * 信息
+     */
+    private String msg;
+
+    /**
+     * 数据
+     */
+    private T data;
+    /**
+     * 数量
+     */
+    private Long count;
+
+
+    public Result() {
+    }
+
+    public Result(T data) {
+        this.data = data;
+    }
+
+    public Result(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    /**
+     * 成功
+     *
+     * @param data 数据
+     * @param <T>  数据类型
+     * @return ResponseEntity
+     */
+    public static <T> Result<T> ok(T data) {
+        return new Result<>(data);
+    }
+
+    /**
+     * 成功
+     *
+     * @param msg 信息
+     * @param <T> 数据类型
+     * @return ResponseEntity
+     */
+    public static <T> Result<T> okMsg(String msg) {
+        Result<T> result = new Result<>();
+        result.setMsg(msg);
+        return result;
+    }
+
+    /**
+     * 成功
+     *
+     * @param data 数据
+     * @param <T>  数据类型
+     * @return ResponseEntity
+     */
+    public static <T> Result<T> ok(Long count, T data) {
+        Result<T> result = new Result<>(data);
+        result.setCount(count);
+        return result;
+    }
+
+    /**
+     * 成功
+     *
+     * @param data 数据
+     * @param msg  信息
+     * @param <T>  数据类型
+     * @return ResponseEntity
+     */
+    public static <T> Result<T> ok(String msg, T data) {
+        Result<T> result = new Result<>(data);
+        result.setMsg(msg);
+        return result;
+    }
+
+    /**
+     * 失败
+     *
+     * @param msg 信息
+     * @return ResponseEntity
+     */
+    public static <T> Result<T> fail(String msg) {
+        return new Result<>(HttpStatus.NOT_IMPLEMENTED.value(), msg);
+    }
+
+    /**
+     * 未授权
+     *
+     * @param msg 信息
+     * @return ResponseEntity
+     */
+    public static <T> Result<T> unauthorized(String msg) {
+        return new Result<>(HttpStatus.UNAUTHORIZED.value(), msg);
+    }
+}

+ 30 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Command.java

@@ -0,0 +1,30 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import com.shkpr.iot.common.core.enums.EndiannessEnum;
+import lombok.Data;
+
+/**
+ * 指令规则
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Data
+public class Command {
+    /**
+     * 设备ID
+     */
+    private String deviceId;
+    /**
+     * 指令内容
+     */
+    private String content;
+    /**
+     * 16进制
+     */
+    private Boolean hex;
+    /**
+     * 字节序
+     */
+    private EndiannessEnum endianness;
+}

+ 40 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/CommandModbus.java

@@ -0,0 +1,40 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * modbus指令规则
+ *
+ * @author 欧阳劲驰
+ * @since JDK1.8
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class CommandModbus extends Command {
+    /**
+     * 设备地址
+     */
+    private Short address;
+
+    /**
+     * 指令(3读/6写)
+     */
+    private Short command;
+
+    /**
+     * 寄存器
+     */
+    private Short register;
+
+    /**
+     * 寄存器数量
+     */
+    private Short registerNumber;
+
+    /**
+     * 写入值
+     * <p>大端模式,无符号</p>
+     */
+    private Integer value;
+}

+ 28 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/DataInfo.java

@@ -0,0 +1,28 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 数据信息
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Data
+public class DataInfo {
+    /**
+     * 设备号
+     */
+    private String equipmentCode;
+    /**
+     * 值集
+     */
+    private List<DataValue> dataValues;
+    /**
+     * 时间
+     */
+    private LocalDateTime dateTime;
+}

+ 35 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/DataValue.java

@@ -0,0 +1,35 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * 数据值
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class DataValue{
+    /**
+     * 设备编码
+     */
+    private String equipmentCode;
+    /**
+     * 位号key
+     */
+    private String key;
+    /**
+     * 值
+     */
+    private Object value;
+    /**
+     * 时间
+     */
+    private LocalDateTime dateTime;
+}

+ 77 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Destination.java

@@ -0,0 +1,77 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.Data;
+import org.springframework.http.HttpMethod;
+
+/**
+ * 目标
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@Data
+public class Destination {
+    /**
+     * 主健
+     */
+    private Long id;
+    /**
+     * 驱动编码
+     */
+    private String driverCode;
+    /**
+     * 设备编码
+     */
+    private String equipmentCode;
+    /**
+     * 地址
+     */
+    private String host;
+    /**
+     * 端口
+     */
+    private Integer port;
+    /**
+     * ssl
+     */
+    private Boolean ssl;
+    /**
+     * http方法
+     */
+    private HttpMethod httpMethod;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 主题
+     */
+    private String topic;
+    /**
+     * 定制code
+     */
+    private String customCode;
+    /**
+     * 定时的
+     */
+    private Boolean Timed;
+
+    @Override
+    public final boolean equals(Object o) {
+        if (!(o instanceof Destination)) return false;
+
+        Destination that = (Destination) o;
+        return getHost().equals(that.getHost()) && getPort().equals(that.getPort());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getHost().hashCode();
+        result = 31 * result + getPort().hashCode();
+        return result;
+    }
+}

+ 71 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Equipment.java

@@ -0,0 +1,71 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 设备表
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@Data
+public class Equipment {
+    /**
+     * id
+     */
+    private int id;
+    /**
+     * 驱动编码
+     */
+    private String driverCode;
+    /**
+     * 设备编号
+     */
+    private String equipmentCode;
+    /**
+     * 设备名称
+     */
+    private String equipmentName;
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 启用
+     */
+    private Boolean enable;
+    /**
+     * 是否在线
+     */
+    private Boolean online;
+    /**
+     * 目标集合
+     */
+    private List<Destination> destinations;
+    /**
+     * 模版集合
+     */
+    private List<Template> templates;
+    /**
+     * 被动响应(不启动采集)
+     */
+    private Boolean passive;
+    /**
+     * 采集间隔(秒)
+     */
+    private Integer collectionInterval;
+    /**
+     * 轮询次数
+     */
+    private Integer pollTime;
+    /**
+     * 轮询间隔(秒)
+     */
+    private Integer pollInterval;
+    /**
+     * 点位延迟(毫秒)
+     */
+    private Integer pointDelay;
+}

+ 59 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/PointRule.java

@@ -0,0 +1,59 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import com.shkpr.iot.common.core.enums.EndiannessEnum;
+import com.shkpr.iot.common.core.enums.ValueTypeEnum;
+import lombok.Data;
+
+/**
+ * 位号规则
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Data
+public class PointRule {
+    /**
+     * 主键
+     */
+    private Long id;
+    /**
+     * 编码
+     */
+    private String code;
+    /**
+     * 模版编码
+     */
+    private String TemplateCode;
+    /**
+     * 读写code
+     */
+    private String RwCode;
+    /**
+     * 字节序
+     */
+    private EndiannessEnum endianness = EndiannessEnum.AB;
+    /**
+     * 位号key
+     */
+    private String key;
+    /**
+     * 标识索引(k,v/下标)
+     */
+    private String flagIndex;
+    /**
+     * 值索引(k/下标)
+     */
+    private String valueIndex;
+    /**
+     * 长度(数据段长度,kv结构留空)
+     */
+    private Integer length;
+    /**
+     * 值类型
+     */
+    private ValueTypeEnum valueType;
+    /**
+     * 小数点位(偏移量)
+     */
+    private Integer decimalPlace;
+}

+ 33 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Protocol.java

@@ -0,0 +1,33 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.Data;
+
+/**
+ * 协议
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Data
+public class Protocol {
+    /**
+     * 主健
+     */
+    private Long id;
+    /**
+     * code
+     */
+    private String code;
+    /**
+     * 协议名称
+     */
+    private String description;
+    /**
+     * 协议标识(唯一)
+     */
+    private String flag;
+    /**
+     * 16进制
+     */
+    private Boolean hex;
+}

+ 43 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/domain/po/Template.java

@@ -0,0 +1,43 @@
+package com.shkpr.iot.common.core.domain.po;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 模版
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Data
+public class Template {
+    /**
+     * 主键
+     */
+    private Long id;
+    /**
+     * 编码
+     */
+    private String code;
+    /**
+     * 模版名称
+     */
+    private String TemplateName;
+    /**
+     * 协议标识
+     */
+    private String protocolFlag;
+    /**
+     * 数据索引(key:key(冒号取决于层级)/地址/下标)
+     */
+    private String dataIndex;
+    /**
+     * 数据层级(树状结构使用)
+     */
+    private Short dataRank;
+    /**
+     * 规则集合
+     */
+    private List<PointRule> rules;
+}

+ 34 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/EndiannessEnum.java

@@ -0,0 +1,34 @@
+package com.shkpr.iot.common.core.enums;
+
+/**
+ * 字节序枚举
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public enum EndiannessEnum {
+    /**
+     * 大端,16位
+     */
+    AB,
+    /**
+     * 小端,16位
+     */
+    BA,
+    /**
+     * 大端,32位
+     */
+    ABCD,
+    /**
+     * 小端,32位
+     */
+    DCBA,
+    /**
+     * 中端,32位
+     */
+    CDAB,
+    /**
+     * 半字交换,32位
+     */
+    BADC
+}

+ 21 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/ProtocolEnum.java

@@ -0,0 +1,21 @@
+package com.shkpr.iot.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 协议枚举
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Getter
+@AllArgsConstructor
+public enum ProtocolEnum {
+    JSON( "json"),
+    MODBUS("modbus");
+    /**
+     * 标识
+     */
+    private final String flag;
+}

+ 37 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/RwFlagEnum.java

@@ -0,0 +1,37 @@
+package com.shkpr.iot.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 读写枚举
+ *
+ * @author 欧阳劲驰
+ * @since 0.1.0
+ */
+@Getter
+@AllArgsConstructor
+public enum RwFlagEnum {
+    R( "r", "只读"),
+    W("w", "只写"),
+    RW("rw", "读写");
+
+    private final String code;
+    private final String remark;
+
+    /**
+     * 从code获取
+     *
+     * @param code code
+     * @return 读写枚举
+     */
+    public static RwFlagEnum ofCode(String code) {
+        //根据code匹配
+        return Arrays.stream(values())
+                .filter((type) -> type.getCode().equals(code))
+                .findFirst()
+                .orElse(null);
+    }
+}

+ 35 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/enums/ValueTypeEnum.java

@@ -0,0 +1,35 @@
+package com.shkpr.iot.common.core.enums;
+
+/**
+ * 值类型枚举
+ *
+ * @author 欧阳劲驰
+ * @since 0.1.0
+ */
+public enum ValueTypeEnum {
+    /**
+     * 无符号整型
+     */
+    UNSIGNED_INT,
+    /**
+     * 有符号整型
+     */
+    SIGNED_INT,
+    /**
+     * 单经度浮点
+     */
+    SINGLE_FLOAT,
+    /**
+     * 双精度浮点
+     */
+    DOUBLE_FLOAT,
+    /**
+     * 字符串
+     */
+    ASCII,
+    /**
+     * 布尔(一般用不上)
+     */
+    @Deprecated
+    BOOLEAN,
+}

+ 33 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/properties/InfluxdbDBProperties.java

@@ -0,0 +1,33 @@
+package com.shkpr.iot.common.core.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * influxdb属性
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "influxdb")
+public class InfluxdbDBProperties {
+    /**
+     * 地址
+     */
+    private String url;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 数据库
+     */
+    private String database;
+}

+ 35 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/BeanUtil.java

@@ -0,0 +1,35 @@
+package com.shkpr.iot.common.core.util;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * bean工具类
+ *
+ * @author 欧阳劲驰
+ * @since 1.0.0
+ */
+public class BeanUtil {
+
+    /**
+     * 拷贝
+     *
+     * @param source     被拷贝对象
+     * @param targetType 类型
+     * @param <T>        类型
+     * @return 新对象
+     */
+    public static <T> T copy(Object source, Class<T> targetType) {
+        try {
+            if (!ObjectUtils.allNotNull(source, targetType)) {
+                return null;
+            }
+            //拷贝资源
+            T t = targetType.getDeclaredConstructor().newInstance();
+            BeanUtils.copyProperties(source, t);
+            return t;
+        } catch (ReflectiveOperationException e) {
+            return null;
+        }
+    }
+}

+ 48 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/HexUtil.java

@@ -0,0 +1,48 @@
+package com.shkpr.iot.common.core.util;
+
+/**
+ * 16进制工具类
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class HexUtil {
+    /**
+     * 字节数组转字符串
+     *
+     * @param bytes 字节数组
+     * @return 字符串
+     */
+    public static String encodeHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            String s = Integer.toHexString(b & 0xff);
+            sb.append(s.length() == 1 ? "0" + s : s);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 字符串转字节数组
+     *
+     * @param s 字符串
+     * @return 字节数组
+     */
+    public static byte[] decodeHex(String s) {
+        //非空判断
+        if (s == null || s.isEmpty()) return null;
+        //缓存数组
+        byte[] b = new byte[s.length() >> 1];
+        //遍历字符串,每次位移长度2
+        for (int i = 0; i < s.length(); i += 2) {
+            //高位
+            int high = (Character.digit(s.charAt(i), 16) << 4);
+            //低位
+            int low = Character.digit(s.charAt(i + 1), 16);
+            //合并高低位,并加入缓存数组
+            b[i >> 1] = (byte) (high | low);
+        }
+        //返回缓存数组
+        return b;
+    }
+}

+ 67 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/InfluxDBUtil.java

@@ -0,0 +1,67 @@
+package com.shkpr.iot.common.core.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.influxdb.InfluxDB;
+import org.influxdb.dto.BatchPoints;
+import org.influxdb.dto.Point;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PreDestroy;
+
+/**
+ * influxdb工具类
+ *
+ * @author 李兴
+ * @since 0.0.1-dev
+ **/
+@Component
+@Slf4j
+public class InfluxDBUtil {
+    final
+    InfluxDB influxDB;
+
+    public InfluxDBUtil(InfluxDB influxDB) {
+        this.influxDB = influxDB;
+    }
+
+    /**
+     * 插入
+     *
+     * @param point 点
+     * @return 插入状态
+     */
+    public Boolean insert(Point point) {
+        try {
+            influxDB.write(point);
+            return true;
+        } catch (Exception e) {
+            log.error("插入InfluxDB失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 批量插入
+     *
+     * @param points 批量点
+     * @return 插入状态
+     */
+    public Boolean insertBatch(BatchPoints points) {
+        try {
+            influxDB.write(points);
+            return true;
+        } catch (Exception e) {
+            log.error("插入InfluxDB失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 关闭
+     */
+    @PreDestroy
+    private void shutdown() {
+        influxDB.close();
+    }
+
+}

+ 109 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/JsonUtil.java

@@ -0,0 +1,109 @@
+package com.shkpr.iot.common.core.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.shkpr.iot.common.core.constants.TemplateMetadata;
+import com.shkpr.iot.common.core.domain.po.DataValue;
+import com.shkpr.iot.common.core.domain.po.PointRule;
+import com.shkpr.iot.common.core.domain.po.Template;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * json工具类
+ *
+ * @author 欧阳劲驰
+ * @since JDK1.8
+ */
+@Slf4j
+public class JsonUtil {
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 解码数据
+     *
+     * @param data          数据
+     * @param equipmentCode 设备编码
+     * @param templates     模版集合
+     * @return 数据值集
+     */
+    public static List<DataValue> decodeData(byte[] data, String equipmentCode, List<Template> templates) {
+        //非空判断
+        if (templates == null || templates.isEmpty() || data == null || data.length == 0)
+            return Collections.emptyList();
+        //todo 过滤模版,此次逻辑需再想想
+        Template template = templates.get(0);
+        //命令非空判断
+        if (template == null) return Collections.emptyList();
+        //数据索引
+        String[] dataIndexes = template.getDataIndex() == null ? new String[0] :
+                template.getDataIndex().split(TemplateMetadata.INDEX_SEPARATE);
+        if (template.getDataRank() > dataIndexes.length) return Collections.emptyList();
+        try {
+            //解析json
+            JsonNode jsonNode = objectMapper.readTree(data);
+            //如层级为0,直接解析节点
+            if (template.getDataRank() == 0)
+                return decodeNode(jsonNode, equipmentCode, template);
+            //遍历数据索引
+            for (int i = 0; i < dataIndexes.length; i++) {
+                String dataIndex = dataIndexes[i];
+                //获取数据所在节点
+                JsonNode current = jsonNode.get(dataIndex);
+                //解析节点
+                if ((i + 1) == template.getDataRank()) return decodeNode(current, equipmentCode, template);
+            }
+
+            return Collections.emptyList();
+        } catch (IOException e) {
+            log.error("解析json数据失败,设备编码:{}", equipmentCode, e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 解析节点
+     *
+     * @param jsonNode      json节点
+     * @param equipmentCode 设备编码
+     * @param template      模版
+     * @return 数据值集
+     */
+    private static List<DataValue> decodeNode(JsonNode jsonNode, String equipmentCode, Template template) {
+        //数据时间
+        LocalDateTime dateTime = LocalDateTime.now();
+        //规则集合
+        List<PointRule> rules = template.getRules();
+        //集合解析
+        if (jsonNode.isArray()) {
+            //数据集合
+            List<DataValue> dataValues = new ArrayList<>();
+            //遍历节点
+            for (JsonNode node : jsonNode) {
+                //获取当前规则
+                PointRule rule = rules.stream().filter(it -> {
+                    String[] split = it.getFlagIndex().split(",");
+                    return split.length == 2 &&
+                            Objects.equals(objectMapper.convertValue(node.get(split[0]),String.class), split[1]);
+                }).findFirst().orElse(null);
+                if (rule == null) continue;
+                //获取值
+                Object value = objectMapper.convertValue(node.get(rule.getValueIndex()), Object.class);
+                //存入值对象
+                dataValues.add(new DataValue(equipmentCode, rule.getKey(), value, dateTime));
+            }
+            return dataValues;
+        }
+        //todo 对象解析
+        if (jsonNode.isObject()) {
+
+        }
+        return Collections.emptyList();
+    }
+}

+ 275 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/ModbusUtil.java

@@ -0,0 +1,275 @@
+package com.shkpr.iot.common.core.util;
+
+import com.shkpr.iot.common.core.constants.ModbusMetadata;
+import com.shkpr.iot.common.core.domain.po.CommandModbus;
+import com.shkpr.iot.common.core.domain.po.DataValue;
+import com.shkpr.iot.common.core.domain.po.PointRule;
+import com.shkpr.iot.common.core.domain.po.Template;
+import com.shkpr.iot.common.core.enums.EndiannessEnum;
+import com.shkpr.iot.common.core.enums.ValueTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.ByteBuffer;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Modbus工具类
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@Slf4j
+public class ModbusUtil {
+    /**
+     * 计算crc
+     *
+     * @param data 数据
+     * @return crc
+     */
+    public static int calcCRC(byte[] data) {
+        //初始化crc
+        int crc = 0xffff;
+        //遍历数据
+        for (byte b : data)
+            //八次crc计算
+            crc = IntStream.range(0, 8).reduce(crc ^ (b & 0xff), (c, i) -> (c & 1) == 1 ? (c >> 1) ^ 0xa001 : c >> 1);
+        //交换高地位
+        return (crc & 0xFF00) >> 8 | (crc & 0x00FF) << 8;
+    }
+
+    /**
+     * 计算异或
+     *
+     * @param data 数据
+     * @return 异或
+     */
+    public static int calcXOR(byte[] data) {
+        //从第二位开始遍历
+        return IntStream.range(1, data.length)
+                //无符号整数
+                .map(i -> data[i] & 0xFF)
+                //异或
+                .reduce(0, (acc, b) -> acc ^ b);
+    }
+
+    /**
+     * 添加crc
+     *
+     * @param data 数据
+     * @return 添加crc的数据
+     */
+    public static byte[] addCRC(byte[] data) {
+        //计算crc
+        int crc = calcCRC(data);
+        //添加至数组
+        return ArrayUtils.addAll(data, (byte) ((crc >> 8) & 0xFF), (byte) (crc & 0xFF));
+    }
+
+    /**
+     * 验证crc
+     *
+     * @param data 数据
+     * @return 验证状态
+     */
+    public static Boolean matchesCRC(byte[] data) {
+        //数据段
+        byte[] original = Arrays.copyOfRange(data, 0, data.length - 2);
+        //crc段
+        byte[] crc = Arrays.copyOfRange(data, data.length - 2, data.length);
+        //比较值
+        return calcCRC(original) == Integer.valueOf(HexUtil.encodeHex(crc), 16);
+    }
+
+    /**
+     * 验证xor
+     *
+     * @param data      数据
+     * @param separator 分隔
+     * @return 验证状态
+     */
+    public static Boolean matchesXOR(byte[] data, String separator) {
+        //转换字符串,并删除换行符
+        String msg = new String(data);
+        msg = StringUtils.remove(msg, "\r");
+        msg = StringUtils.remove(msg, "\n");
+        //数据段
+        String original = StringUtils.substringBefore(msg, separator);
+        //xor段
+        String xor = StringUtils.substringAfter(msg, separator);
+        //比较值
+        return xor.equalsIgnoreCase(String.format("%02X", calcXOR(original.getBytes())));
+    }
+
+    /**
+     * 编码指令
+     *
+     * @param command 指令
+     * @return 指令信息
+     */
+    public static byte[] encodeCommand(CommandModbus command) {
+        //ascii直接处理内容
+        if (!command.getHex()) return command.getContent().getBytes();
+        //存入指令
+        ByteBuffer buffer = ByteBuffer.allocate(8);
+        buffer.put((byte) (command.getAddress() & 0xff));
+        buffer.put((byte) (command.getCommand() & 0xff));
+        buffer.putShort(command.getRegister());
+        //读取则存入读取数量
+        if (Objects.equals(ModbusMetadata.READ, command.getCommand()))
+            buffer.putShort(command.getRegisterNumber());
+        //写入则存入写入数据
+        if (Objects.equals(ModbusMetadata.WRITE, command.getCommand())) {
+            buffer.put((byte) ((command.getValue() >> 8) & 0xFF));
+            buffer.put((byte) (command.getValue() & 0xFF));
+        }
+        //计算crc
+        int crc = calcCRC(Arrays.copyOfRange(buffer.array(), 0, 6));
+        //存入crc
+        buffer.putShort((short) crc);
+        return buffer.array();
+    }
+
+    /**
+     * 解码数据
+     *
+     * @param data          数据
+     * @param equipmentCode 设备code
+     * @param templates     模版集合
+     * @return 数据集合
+     */
+    public static List<DataValue> decodeData(byte[] data, String equipmentCode, List<Template> templates) {
+        //非空判断
+        if (templates == null || templates.isEmpty() || data == null || data.length == 0 || (data[1] & 0xFF) != 3)
+            return Collections.emptyList();
+        //数据时间
+        LocalDateTime dateTime = LocalDateTime.now();
+        //过滤设备地址对应的模版
+        Template template = templates.stream()
+                .filter(it -> Integer.parseInt(it.getDataIndex()) == (data[0] & 0xFF))
+                .findFirst().orElse(null);
+        //命令非空判断
+        if (template == null) return Collections.emptyList();
+        //数据起始位
+        int begin = 3;
+        //解码规则集合
+        List<PointRule> rules = template.getRules();
+        //返回透传数据集合
+        return rules.stream().map(rule -> {
+                    //获取数据段
+                    byte[] dataItem = ArrayUtils.subarray(data, begin + Integer.parseInt(rule.getValueIndex()) * 2,
+                            (begin + Integer.parseInt(rule.getValueIndex()) * 2) + rule.getLength());
+                    //获取字节序
+                    EndiannessEnum endianness = rule.getEndianness() == null ? EndiannessEnum.AB : rule.getEndianness();
+                    //获取十进制值
+                    BigDecimal decimalValue = decodeDecimal(dataItem, endianness);
+                    if (decimalValue == null) return null;
+                    //获取数据类型
+                    ValueTypeEnum valueType = rule.getValueType() == null ? ValueTypeEnum.UNSIGNED_INT : rule.getValueType();
+                    //根据数据类型处理值
+                    Float value = null;
+                    switch (valueType) {
+                        //无符号整型
+                        case UNSIGNED_INT:
+                            //如值为0或小数位为0,直接返回值,否则处理小数位
+                            if (decimalValue.intValue() == 0 || rule.getDecimalPlace() <= 0)
+                                value = decimalValue.floatValue();
+                            else value = decimalValue
+                                    .divide(BigDecimal.valueOf(Math.pow(10, rule.getDecimalPlace())), rule.getDecimalPlace(), RoundingMode.HALF_UP)
+                                    .floatValue();
+                            break;
+                        //有符号整型
+                        case SIGNED_INT:
+                            float item = decimalValue.floatValue();
+                            //负数处理
+                            if (((dataItem[0] & 0xFF) >> 7) == 1) item = item - (1 << dataItem.length * 8);
+                            //如值为0或小数位为0,直接返回值,否则处理小数位
+                            if (item == 0 || rule.getDecimalPlace() <= 0) value = item;
+                            else value = BigDecimal.valueOf(item)
+                                    .divide(BigDecimal.valueOf(Math.pow(10, rule.getDecimalPlace())), 3, RoundingMode.HALF_UP)
+                                    .floatValue();
+                            break;
+                        //单经度浮点
+                        case SINGLE_FLOAT:
+                            value = Float.intBitsToFloat(decimalValue.intValue());
+                            break;
+                    }
+                    return value == null ? null : new DataValue(equipmentCode, rule.getKey(), value, dateTime);
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 解码十进制
+     *
+     * @param data       数据
+     * @param endianness 字节序枚举
+     * @return 十进制
+     */
+    public static BigDecimal decodeDecimal(byte[] data, EndiannessEnum endianness) {
+        //根据字节序获取值
+        Long value = null;
+        switch (endianness) {
+            //小端
+            case AB:
+            case ABCD:
+                value = IntStream.range(0, data.length)
+                        .mapToLong(i -> (long) (data[i] & 0xFF) << ((data.length - 1 - i) * 8))
+                        .reduce((x, y) -> x | y).orElse(0);
+                break;
+            //大端
+            case BA:
+            case BADC:
+                value = IntStream.range(0, data.length)
+                        .mapToLong(i -> (long) (data[i] & 0xFF) << ((data.length - 1 - (i % 2 == 0 ? i + 1 : i - 1)) * 8))
+                        .reduce((x, y) -> x | y).orElse(0);
+                break;
+            //交换寄存器
+            case CDAB:
+                value = IntStream.range(0, data.length)
+                        .mapToLong(i ->
+                                (long) (data[i] & 0xFF) << (i % 4 == 0 || i % 4 == 1 ?
+                                        (data.length - 1 - i - 2) * 8 :
+                                        (data.length - 1 - i + 2) * 8)
+                        ).reduce((x, y) -> x | y).orElse(0);
+                break;
+            //交换寄存器大端
+            case DCBA:
+                value = IntStream.range(0, data.length)
+                        .mapToLong(i -> {
+                            long shiftAmount;
+                            switch (i % 4) {
+                                case 0:
+                                    shiftAmount = (data.length - 1 - i - 3) * 8L;
+                                    break;
+                                case 1:
+                                    shiftAmount = (data.length - 1 - i - 1) * 8L;
+                                    break;
+                                case 2:
+                                    shiftAmount = (data.length - 1 - i + 1) * 8L;
+                                    break;
+                                case 3:
+                                    shiftAmount = (data.length - 1 - i + 3) * 8L;
+                                    break;
+                                default:
+                                    shiftAmount = 0;
+                            }
+                            return (long) (data[i] & 0xFF) << shiftAmount;
+                        }).reduce((x, y) -> x | y).orElse(0);
+                break;
+        }
+        return value == null ? null : BigDecimal.valueOf(value);
+    }
+
+
+}

+ 54 - 0
iot-common/iot-common-core/src/main/java/com/shkpr/iot/common/core/util/ResponseUtil.java

@@ -0,0 +1,54 @@
+package com.shkpr.iot.common.core.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Response工具
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public class ResponseUtil {
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 输出json
+     *
+     * @param response response
+     * @param value    json值
+     * @throws IOException io异常
+     */
+    public static void writeJson(HttpServletResponse response, Object value) throws IOException {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.getWriter().write(objectMapper.writeValueAsString(value));
+    }
+
+    /**
+     * 输出弹窗
+     *
+     * @param response response
+     * @param message  弹窗内容
+     * @throws IOException id异常
+     */
+    public static void writeAlert(HttpServletResponse response, String message) throws IOException {
+        writeHtml(response, String.format("<script>alert('%s');</script>", message));
+    }
+
+    /**
+     * 输出html页面
+     *
+     * @param response response
+     * @param html     html页面
+     * @throws IOException id异常
+     */
+    public static void writeHtml(HttpServletResponse response, String html) throws IOException {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType(MediaType.TEXT_HTML_VALUE);
+        response.getWriter().write(html);
+    }
+}

+ 32 - 0
iot-common/iot-common-driver/pom.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-common</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-common-driver</artifactId>
+    <!--项目显示名-->
+    <name>iot-common-driver</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-通用驱动模块</description>
+    <!--打包方式-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--公共核心模块-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-common-core</artifactId>
+            <version>${iot.common.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 21 - 0
iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/context/HandlerContext.java

@@ -0,0 +1,21 @@
+package com.shkpr.iot.common.driver.context;
+
+import com.shkpr.iot.common.core.domain.po.Equipment;
+
+/**
+ * 处理器上下文
+ *
+ * @param <C> 客户端类型
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface HandlerContext<C> {
+    /**
+     * 设备
+     */
+    Equipment equipment();
+    /**
+     * 通道
+     */
+    C channel();
+}

+ 29 - 0
iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/handle/DecoderHandler.java

@@ -0,0 +1,29 @@
+package com.shkpr.iot.common.driver.handle;
+
+import com.shkpr.iot.common.core.domain.po.DataValue;
+import com.shkpr.iot.common.driver.context.HandlerContext;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * 解码处理器
+ *
+ * @param <C> 客户端类型
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+public abstract class DecoderHandler< C> {
+    /**
+     * 解码
+     *
+     * @param ctx 上下文
+     * @param in  输入
+     * @param out 输出
+     */
+    public void decode(HandlerContext<C> ctx, ByteBuffer in, List<DataValue> out) {
+        log.info("读取到报文:{}", new String(in.array()));
+    }
+}

+ 24 - 0
iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/handle/EncoderHandler.java

@@ -0,0 +1,24 @@
+package com.shkpr.iot.common.driver.handle;
+
+import com.shkpr.iot.common.driver.context.HandlerContext;
+
+import java.nio.ByteBuffer;
+
+/**
+ * 编码处理器
+ *
+ * @param <I> 指令类型
+ * @param <C> 客户端类型
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface EncoderHandler<I, C> {
+    /**
+     * 编码
+     *
+     * @param ctx 处理器上下文
+     * @param msg 消息
+     * @param out 输出
+     */
+    void encode(HandlerContext<C> ctx, I msg, ByteBuffer out);
+}

+ 44 - 0
iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/handle/InboundHandler.java

@@ -0,0 +1,44 @@
+package com.shkpr.iot.common.driver.handle;
+
+import com.shkpr.iot.common.driver.context.HandlerContext;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 入站处理器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+public abstract class InboundHandler<C> {
+    /**
+     * 上下文
+     */
+    public HandlerContext<C> ctx;
+
+    public InboundHandler(HandlerContext<C> ctx) {
+        this.ctx = ctx;
+    }
+
+    /**
+     * 通道活跃
+     */
+    public void channelActive() {
+        log.info("通道活跃:{}", ctx.channel());
+    }
+
+
+    /**
+     * 终止连接
+     */
+    public void channelInactive() {
+        log.info("通道非活跃:{}", ctx.channel());
+    }
+
+    /**
+     * 读
+     */
+    public void channelRead(Object msg) {
+        log.info("读取到数据:{}", msg);
+    }
+}

+ 94 - 0
iot-common/iot-common-driver/src/main/java/com/shkpr/iot/common/driver/service/DriverService.java

@@ -0,0 +1,94 @@
+package com.shkpr.iot.common.driver.service;
+
+import com.shkpr.iot.common.core.domain.po.DataInfo;
+import com.shkpr.iot.common.core.domain.po.Destination;
+import com.shkpr.iot.common.core.domain.po.Equipment;
+
+import javax.annotation.PreDestroy;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * 驱动服务
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface DriverService {
+    /**
+     * 初始化
+     *
+     * @param equipments   设备集合
+     * @param dataConsumer 数据消费
+     */
+    void initial(List<Equipment> equipments, Consumer<DataInfo> dataConsumer);
+
+    /**
+     * 清理资源
+     *
+     * @param equipments 设备集合
+     */
+    void clear(List<Equipment> equipments);
+
+    /**
+     * 关闭
+     */
+    @PreDestroy
+    void shutdown() throws Exception;
+
+    /**
+     * 注册
+     *
+     * @param equipment 设备
+     */
+    void register(Equipment equipment);
+
+    /**
+     * 重新注册
+     */
+    void refRegister(Equipment equipment);
+
+    /**
+     * 判断目标是否连接
+     *
+     * @param destination 目标
+     * @return 是否在线
+     */
+    Boolean isConnected(Destination destination);
+
+    /**
+     * 断开连接
+     *
+     * @param destination 目标
+     */
+    void disconnect(Destination destination);
+
+    /**
+     * 开启任务
+     *
+     * @param equipment 设备
+     */
+    void startSchedule(Equipment equipment);
+
+    /**
+     * 刷新任务
+     *
+     * @param equipment 设备
+     */
+    void refSchedule(Equipment equipment);
+
+    /**
+     * 关闭任务
+     *
+     * @param equipment 设备
+     */
+    void stopSchedule(Equipment equipment);
+
+    /**
+     * 发送消息
+     *
+     * @param equipment 设备
+     * @param message   消息
+     */
+    void sendMessage(Equipment equipment, Object message);
+}

+ 25 - 0
iot-common/pom.xml

@@ -0,0 +1,25 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>kpr-iot</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-common</artifactId>
+    <!--项目显示名-->
+    <name>iot-common</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-通用模块</description>
+    <!--打包类型-->
+    <packaging>pom</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <modules>
+        <!--通用核心模块-->
+        <module>iot-common-core</module>
+    </modules>
+</project>

+ 24 - 0
iot-driver/iot-driver-http/pom.xml

@@ -0,0 +1,24 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-driver</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-driver-http</artifactId>
+    <!--项目显示名-->
+    <name>iot-driver-http</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-http驱动模块</description>
+    <!--打包类型-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <dependencies>
+
+    </dependencies>
+</project>

+ 10 - 0
iot-driver/iot-driver-http/src/main/java/com/shkpr/iot/driver/http/DriverHttpProperties.java

@@ -0,0 +1,10 @@
+package com.shkpr.iot.driver.http;
+
+/**
+ * http驱动属性
+ *
+ * @author 欧阳劲驰
+ * @since 1.0.0
+ */
+public class DriverHttpProperties {
+}

+ 29 - 0
iot-driver/iot-driver-mqtt/pom.xml

@@ -0,0 +1,29 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-driver</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-driver-mqtt</artifactId>
+    <!--项目显示名-->
+    <name>iot-driver-mqtt</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-mqtt驱动模块</description>
+    <!--打包类型-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--spring-mqtt-->
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 16 - 0
iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/common/config/MqttConfig.java

@@ -0,0 +1,16 @@
+package com.shkpr.iot.driver.mqtt.common.config;
+
+import com.shkpr.iot.driver.mqtt.common.properties.MqttProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * mqtt配置
+ *
+ * @author 欧阳劲驰
+ * @since JDK1.8
+ */
+@Configuration
+@EnableConfigurationProperties(MqttProperties.class)
+public class MqttConfig {
+}

+ 43 - 0
iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/common/context/MqttHandlerContext.java

@@ -0,0 +1,43 @@
+package com.shkpr.iot.driver.mqtt.common.context;
+
+import com.shkpr.iot.common.core.domain.po.Equipment;
+import com.shkpr.iot.common.driver.context.HandlerContext;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+
+/**
+ * mqtt处理上下文
+ *
+ * @author 欧阳劲驰
+ * @since JDK1.8
+ */
+public class MqttHandlerContext implements HandlerContext<MqttAsyncClient> {
+    /**
+     * 设备
+     */
+    Equipment equipment;
+    /**
+     * 客户端
+     */
+    MqttAsyncClient client;
+
+    public MqttHandlerContext( Equipment equipment,MqttAsyncClient client) {
+        this.equipment = equipment;
+        this.client = client;
+    }
+
+    /**
+     * 设备
+     */
+    @Override
+    public Equipment equipment() {
+        return equipment;
+    }
+
+    /**
+     * 通道
+     */
+    @Override
+    public MqttAsyncClient channel() {
+        return this.client;
+    }
+}

+ 18 - 0
iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/common/properties/MqttProperties.java

@@ -0,0 +1,18 @@
+package com.shkpr.iot.driver.mqtt.common.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * mqtt属性
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "mqtt")
+public class MqttProperties {
+
+}

+ 48 - 0
iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/handler/MqttDecoderHandler.java

@@ -0,0 +1,48 @@
+package com.shkpr.iot.driver.mqtt.handler;
+
+import com.shkpr.iot.common.core.domain.po.DataValue;
+import com.shkpr.iot.common.core.domain.po.Equipment;
+import com.shkpr.iot.common.core.domain.po.Template;
+import com.shkpr.iot.common.core.enums.ProtocolEnum;
+import com.shkpr.iot.common.core.util.JsonUtil;
+import com.shkpr.iot.common.driver.context.HandlerContext;
+import com.shkpr.iot.common.driver.handle.DecoderHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 杭州美仪解码处理器
+ *
+ * <p>临时用,后续改成可配置并删除该类</p>
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+public class MqttDecoderHandler extends DecoderHandler<MqttAsyncClient> {
+    /**
+     * {@inheritDoc}
+     */
+    public void decode(HandlerContext<MqttAsyncClient> ctx, ByteBuffer in, List<DataValue> out) {
+        //读取字节
+        byte[] content = new byte[in.remaining()];
+        in.get(content);
+        log.info("读取到报文:{}", new String(content));
+        //获取设备信息
+        Equipment equipment = ctx.equipment();
+        //获取解析列表
+        Set<String> protocols = equipment.getTemplates().stream().map(Template::getProtocolFlag).collect(Collectors.toSet());
+        List<DataValue> dataValues = new ArrayList<>();
+        //解析json
+        if (protocols.contains(ProtocolEnum.JSON.getFlag()))
+            dataValues.addAll(JsonUtil.decodeData(content, equipment.getEquipmentCode(), equipment.getTemplates()));
+        //存入数据
+        out.addAll(dataValues);
+    }
+}

+ 86 - 0
iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/handler/MqttInboundHandler.java

@@ -0,0 +1,86 @@
+package com.shkpr.iot.driver.mqtt.handler;
+
+import com.shkpr.iot.common.core.domain.po.DataInfo;
+import com.shkpr.iot.common.core.domain.po.DataValue;
+import com.shkpr.iot.common.driver.context.HandlerContext;
+import com.shkpr.iot.common.driver.handle.InboundHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import java.nio.ByteBuffer;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * mqtt入站处理器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+public class MqttInboundHandler extends InboundHandler<MqttAsyncClient> implements MqttCallback {
+    /**
+     * 解码处理器
+     */
+    private final MqttDecoderHandler decoderHandler;
+
+    /**
+     * 数据消费者
+     */
+    private final Consumer<DataInfo> dataConsumer;
+
+    public MqttInboundHandler(HandlerContext<MqttAsyncClient> ctx, Consumer<DataInfo> dataConsumer) {
+        super(ctx);
+        this.dataConsumer = dataConsumer;
+        decoderHandler = new MqttDecoderHandler();
+    }
+
+    /**
+     * 通道活跃
+     */
+    @Override
+    public void channelActive() {
+        MqttAsyncClient channel = this.ctx.channel();
+        log.info("通道活跃,地址: {}", channel.getServerURI());
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void connectionLost(Throwable cause) {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void messageArrived(String topic, MqttMessage message) {
+        //解码数据
+        List<DataValue> out = new ArrayList<>();
+        decoderHandler.decode(ctx, ByteBuffer.wrap(message.getPayload()), out);
+
+        DataInfo dataInfo = new DataInfo();
+        dataInfo.setDateTime(LocalDateTime.now());
+        dataInfo.setDataValues(out);
+        dataInfo.setEquipmentCode(ctx.equipment().getEquipmentCode());
+
+        log.info("读取数据:{}", dataInfo);
+        dataConsumer.accept(dataInfo);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+
+    }
+}

+ 301 - 0
iot-driver/iot-driver-mqtt/src/main/java/com/shkpr/iot/driver/mqtt/service/MqttService.java

@@ -0,0 +1,301 @@
+package com.shkpr.iot.driver.mqtt.service;
+
+import com.shkpr.iot.common.core.domain.po.DataInfo;
+import com.shkpr.iot.common.core.domain.po.Destination;
+import com.shkpr.iot.common.core.domain.po.Equipment;
+import com.shkpr.iot.common.driver.service.DriverService;
+import com.shkpr.iot.driver.mqtt.common.context.MqttHandlerContext;
+import com.shkpr.iot.driver.mqtt.handler.MqttInboundHandler;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * mqtt服务
+ *
+ * @author 欧阳劲驰
+ * @since JDK1.8
+ */
+@Component
+@Slf4j
+public class MqttService implements DriverService {
+    /**
+     * 客户端缓存
+     * <p>{@code k:address,v:client}</p>
+     */
+    private static final Map<Destination, MqttAsyncClient> CLIENT_CACHE = new ConcurrentHashMap<>(2048);
+    /**
+     * 定时任务缓存
+     * <p>{@code k:code,v:client}</p>
+     */
+    private final static Map<String, ScheduledFuture<?>> SCHEDULED_CACHE = new ConcurrentHashMap<>(2048);
+    /**
+     * 数据消费
+     */
+    private Consumer<DataInfo> dataConsumer;
+
+    /**
+     * 获取url
+     *
+     * @param destination 目标
+     * @return url
+     */
+    private static String getServerUrl(Destination destination) {
+        return "tcp://" + destination.getHost() + ":" + destination.getPort();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void initial(List<Equipment> equipments, Consumer<DataInfo> dataConsumer) {
+        //设置消费
+        this.dataConsumer = dataConsumer;
+        //批量注册
+        equipments.stream()
+                .filter(equipment -> equipment != null && equipment.getEnable())
+                .forEach(this::register);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear(List<Equipment> equipments) {
+        //设备目标集合
+        List<Destination> destinations = equipments.stream().map(Equipment::getDestinations).flatMap(List::stream).collect(Collectors.toList());
+        //设备code集合
+        List<String> codes = equipments.stream().map(Equipment::getEquipmentCode).collect(Collectors.toList());
+        //清除无效链接
+        CLIENT_CACHE.forEach((k, v) -> {
+            if (!destinations.contains(k)) disconnect(k);
+        });
+        //todo 清除无效任务
+        SCHEDULED_CACHE.forEach((k, v) -> {
+            if (!codes.contains(k)) {
+//                ScheduledFuture<?> remove = SCHEDULED_CACHE.remove(k);
+//                remove.cancel(true);
+            }
+        });
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void shutdown() throws MqttException {
+        for (MqttAsyncClient client : CLIENT_CACHE.values()) {
+            if (client != null) client.disconnect().setActionCallback(new IMqttActionListener() {
+                @Override
+                @SneakyThrows
+                public void onSuccess(IMqttToken asyncActionToken) {
+                    client.close(true);
+                }
+
+                @Override
+                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+                    log.error("断开连接失败:{}", exception.getMessage(), exception);
+                }
+            });
+        }
+    }
+
+
+    /**
+     * 注册
+     */
+    @Override
+    public void register(Equipment equipment) {
+        if (equipment.getEquipmentCode() == null) return;
+        //获取设备标识
+        String code = equipment.getEquipmentCode();
+        if (StringUtils.isBlank(code) || !equipment.getEnable()) return;
+        //获取目标,并连接
+        equipment.getDestinations().forEach(destination -> {
+            //如非活跃,则连接
+            boolean active = CLIENT_CACHE.containsKey(destination) && CLIENT_CACHE.get(destination).isConnected();
+            if (!active) connect(equipment, destination);
+        });
+        //启动任务
+        if (!equipment.getPassive()) startSchedule(equipment);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void refRegister(Equipment equipment) {
+
+    }
+
+    /**
+     * 连接
+     *
+     * @param equipment   设备
+     * @param destination 目标
+     */
+    private void connect(Equipment equipment, Destination destination) {
+        try {
+            //构建客户端
+            MqttAsyncClient mqttClient = new MqttAsyncClient(getServerUrl(destination), MqttClient.generateClientId(), new MemoryPersistence());
+            MqttConnectOptions options = new MqttConnectOptions();
+            options.setUserName(destination.getUsername());
+            options.setPassword(destination.getPassword().toCharArray());
+            options.setAutomaticReconnect(true);
+            //缓存
+            CLIENT_CACHE.put(destination, mqttClient);
+            //构建入站处理器
+            MqttInboundHandler mqttInboundHandler = new MqttInboundHandler(
+                    new MqttHandlerContext(equipment, mqttClient),
+                    data -> {
+                        if (dataConsumer != null) dataConsumer.accept(data);
+                    }
+            );
+            //连接服务端
+            mqttClient.connect(options, null, new IMqttActionListener() {
+                //成功
+                @Override
+                public void onSuccess(IMqttToken asyncActionToken) {
+                    try {
+                        mqttClient.subscribe(destination.getTopic(), 0);
+                    } catch (MqttException e) {
+                        throw new RuntimeException(e);
+                    }
+                    //回调活跃
+                    mqttInboundHandler.channelActive();
+                }
+
+                //失败
+                @Override
+                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+                    log.error("对端连接失败,通讯标识:{}:{}", destination.getHost(), destination.getPort(), exception);
+                    try {
+                        //删除缓存
+                        if (CLIENT_CACHE.containsKey(destination) && CLIENT_CACHE.get(destination) != null)
+                            CLIENT_CACHE.remove(destination).close(true);
+                    } catch (MqttException e) {
+                        log.error("MQTT客户端关闭失败,通讯标识:{},{}", destination.getHost(), destination.getPort(), exception);
+                    }
+                }
+            });
+            //订阅回调
+            mqttClient.setCallback(mqttInboundHandler);
+        } catch (MqttException e) {
+            log.error("连接MQTT服务器异常:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Boolean isConnected(Destination destination) {
+        //获取客户端
+        final MqttAsyncClient client = CLIENT_CACHE.get(destination);
+        //返回客户端状态
+        return client != null && client.isConnected();
+    }
+
+
+    /**
+     * 断开连接
+     *
+     * @param destination 目标
+     */
+    public void disconnect(Destination destination) {
+        if (destination == null) return;
+        //清除缓存,并关闭连接
+        if (CLIENT_CACHE.containsKey(destination))
+            disconnect(CLIENT_CACHE.get(destination));
+    }
+
+    /**
+     * 断开连接
+     *
+     * @param client 客户端
+     */
+    public void disconnect(MqttAsyncClient client) {
+        //获取目标
+        Destination destination = CLIENT_CACHE.entrySet().stream()
+                .filter(entry -> Objects.equals(entry.getValue().getClientId(), client.getClientId()))
+                .map(Map.Entry::getKey)
+                .findFirst().orElse(null);
+        if (destination == null) return;
+        //清除缓存
+        MqttAsyncClient tmp = CLIENT_CACHE.remove(destination);
+        if (tmp == null) return;
+        //断开连接
+        try {
+            tmp.disconnect().setActionCallback(new IMqttActionListener() {
+                //成功
+                @SneakyThrows
+                @Override
+                public void onSuccess(IMqttToken asyncActionToken) {
+                    log.info("断开连接,通信标识:{}:{}", destination.getHost(), destination.getPort());
+                    tmp.close(true);
+                }
+
+                //失败
+                @Override
+                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+                    log.error("断开连接失败,通信标识:{}:{}", destination.getHost(), destination.getPort(), exception);
+                }
+            });
+            if (!tmp.isConnected()) tmp.close(true);
+        } catch (MqttException e) {
+            log.error("MQTT客户端断开异常:{}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startSchedule(Equipment equipment) {
+
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void refSchedule(Equipment equipment) {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void stopSchedule(Equipment equipment) {
+
+    }
+
+    /**
+     * 发送消息
+     *
+     * @param equipment 设备
+     * @param message   消息
+     */
+    @Override
+    public void sendMessage(Equipment equipment, Object message) {
+
+    }
+}

+ 36 - 0
iot-driver/pom.xml

@@ -0,0 +1,36 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>kpr-iot</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-driver</artifactId>
+    <!--项目显示名-->
+    <name>iot-driver</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-驱动模块</description>
+    <!--打包类型-->
+    <packaging>pom</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--公共核心模块-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-common-core</artifactId>
+            <version>${iot.common.version}</version>
+        </dependency>
+        <!--公共驱动模块-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-common-driver</artifactId>
+            <version>${iot.common.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 20 - 0
iot-modules/pom.xml

@@ -0,0 +1,20 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>kpr-iot</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-modules</artifactId>
+    <!--项目显示名-->
+    <name>iot-modules</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-业务模块</description>
+    <!--打包类型-->
+    <packaging>pom</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+</project>

+ 51 - 0
iot-server/iot-server-data/pom.xml

@@ -0,0 +1,51 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-server</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-server-data</artifactId>
+    <!--项目显示名-->
+    <name>iot-server-data</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-数据服务模块</description>
+    <!--打包类型-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--http驱动-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-driver-http</artifactId>
+            <version>${iot.driver.version}</version>
+        </dependency>
+        <!--mqtt驱动-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-driver-mqtt</artifactId>
+            <version>${iot.driver.version}</version>
+        </dependency>
+        <!--spring-web-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!--spring-test-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+        <!--pgsql-->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 48 - 0
iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/DriverInitialRunner.java

@@ -0,0 +1,48 @@
+package com.shkpr.iot.server.data;
+
+import com.shkpr.iot.common.core.domain.po.Equipment;
+import com.shkpr.iot.driver.mqtt.service.MqttService;
+import com.shkpr.iot.server.data.service.DataService;
+import com.shkpr.iot.server.data.service.EquipmentService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 设备初始化执行器
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+@Component
+@Order(value = 1)
+public class DriverInitialRunner implements ApplicationRunner {
+    final
+    DataService dataService;
+    final
+    EquipmentService equipmentService;
+    final
+    MqttService mqttService;
+
+    public DriverInitialRunner(DataService dataService, EquipmentService equipmentService, MqttService mqttService) {
+        this.dataService = dataService;
+        this.equipmentService = equipmentService;
+        this.mqttService = mqttService;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run(ApplicationArguments args) {
+        //设备列表
+        List<Equipment> equipments = equipmentService.findEquipmentByAll();
+        //mqtt初始化
+        mqttService.initial(equipments, dataService::saveData);
+    }
+}

+ 23 - 0
iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/IotServerDataApplication.java

@@ -0,0 +1,23 @@
+package com.shkpr.iot.server.data;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * 启动类
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@SpringBootApplication
+@ComponentScan("com.shkpr.iot")
+@MapperScan("com.shkpr.iot.common.**.mapper")
+public class IotServerDataApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(IotServerDataApplication.class);
+    }
+
+}

+ 19 - 0
iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/DataService.java

@@ -0,0 +1,19 @@
+package com.shkpr.iot.server.data.service;
+
+import com.shkpr.iot.common.core.domain.po.DataInfo;
+
+/**
+ * 数据service
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+public interface DataService {
+    /**
+     * 保存数据
+     *
+     * @param data 数据
+     * @return 保存状态
+     */
+    Boolean saveData(DataInfo data);
+}

+ 20 - 0
iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/EquipmentService.java

@@ -0,0 +1,20 @@
+package com.shkpr.iot.server.data.service;
+
+import com.shkpr.iot.common.core.domain.po.Equipment;
+
+import java.util.List;
+
+/**
+ * 设备service
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+public interface EquipmentService {
+    /**
+     * 查询所有设备
+     *
+     * @return 设备集合
+     */
+    List<Equipment> findEquipmentByAll();
+}

+ 58 - 0
iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/impl/DataServiceImpl.java

@@ -0,0 +1,58 @@
+package com.shkpr.iot.server.data.service.impl;
+
+import com.shkpr.iot.common.core.constants.InfluxdbMetadata;
+import com.shkpr.iot.common.core.domain.po.DataInfo;
+import com.shkpr.iot.common.core.domain.po.DataValue;
+import com.shkpr.iot.common.core.util.InfluxDBUtil;
+import com.shkpr.iot.server.data.service.DataService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.influxdb.dto.Point;
+import org.springframework.stereotype.Service;
+
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * 数据service实现
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Slf4j
+@Service
+public class DataServiceImpl implements DataService {
+    final
+    InfluxDBUtil influxDBUtil;
+
+    public DataServiceImpl(InfluxDBUtil influxDBUtil) {
+        this.influxDBUtil = influxDBUtil;
+    }
+
+    /**
+     * 保存数据
+     *
+     * @param data 数据
+     * @return 保存状态
+     */
+    public Boolean saveData(DataInfo data) {
+        //排除空数据
+        if (CollectionUtils.isEmpty(data.getDataValues())) return false;
+        //获取时间戳
+        long timestamp = data.getDateTime()
+                .atZone(ZoneId.systemDefault())
+                .toInstant()
+                .toEpochMilli();
+        //构建点
+        Point point = Point.measurement("WaterQuality")
+                .tag(InfluxdbMetadata.FLAG_EQUIPMENT_CODE, data.getEquipmentCode())
+                .fields(data.getDataValues().stream()
+                        .collect(Collectors.toMap(DataValue::getKey, DataValue::getValue)))
+                .time(timestamp, TimeUnit.MILLISECONDS)
+                .build();
+        log.info("插入点位数据:{}", point);
+        //插入点
+        return influxDBUtil.insert(point);
+    }
+}

+ 73 - 0
iot-server/iot-server-data/src/main/java/com/shkpr/iot/server/data/service/impl/EquipmentServiceImpl.java

@@ -0,0 +1,73 @@
+package com.shkpr.iot.server.data.service.impl;
+
+import com.shkpr.iot.common.core.domain.po.Destination;
+import com.shkpr.iot.common.core.domain.po.Equipment;
+import com.shkpr.iot.common.core.domain.po.PointRule;
+import com.shkpr.iot.common.core.domain.po.Template;
+import com.shkpr.iot.server.data.service.EquipmentService;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 设备service实现
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@Service
+public class EquipmentServiceImpl implements EquipmentService {
+    /**
+     * 查询所有设备
+     *
+     * @return 设备集合
+     */
+    @Override
+    public List<Equipment> findEquipmentByAll() {
+        //设备
+        Equipment equipment = new Equipment();
+        equipment.setEquipmentCode("1MP0000025120005");
+        equipment.setPassive(true);
+        equipment.setEnable(true);
+        //目标
+        Destination destination = new Destination();
+        destination.setHost("111.170.129.106");
+        destination.setPort(1883);
+        destination.setUsername("hzmy");
+        destination.setPassword("hzmy123456");
+        destination.setTopic("/data/1MP0000025120005");
+        equipment.setDestinations(Collections.singletonList(destination));
+        //模版
+        Template template = new Template();
+        template.setProtocolFlag("json");
+        template.setDataIndex("dataList");
+        template.setDataRank((short) 1);
+        //点位规则
+        //ph
+        PointRule pointRule1 = new PointRule();
+        pointRule1.setKey("ph");
+        pointRule1.setFlagIndex("sensorId,267");
+        pointRule1.setValueIndex("dataValue");
+        //浊度
+        PointRule pointRule2 = new PointRule();
+        pointRule2.setKey("turbidity");
+        pointRule2.setFlagIndex("sensorId,9");
+        pointRule2.setValueIndex("dataValue");
+        //余氯
+        PointRule pointRule3 = new PointRule();
+        pointRule3.setKey("chlorine");
+        pointRule3.setFlagIndex("sensorId,264");
+        pointRule3.setValueIndex("dataValue");
+        //水温
+        PointRule pointRule4 = new PointRule();
+        pointRule4.setKey("temperature");
+        pointRule4.setFlagIndex("sensorId,10");
+        pointRule4.setValueIndex("dataValue");
+        template.setRules(Arrays.asList(pointRule1, pointRule2, pointRule3, pointRule4));
+        equipment.setTemplates(Collections.singletonList(template));
+
+        return Collections.singletonList(equipment);
+    }
+}

+ 11 - 0
iot-server/iot-server-data/src/main/resources/application-baokang.yml

@@ -0,0 +1,11 @@
+spring:
+  datasource:
+    username: postgres
+    password: kpr.23417.postgres
+    url: jdbc:postgresql://140.246.183.164:5432/water_smart_develop_branch?useSSL=false&useAffectedRows=false&allowMultiQueries=true&rewriteBatchedStatements=true
+    driver-class-name: org.postgresql.Driver
+influxdb:
+  url: http://111.170.129.106:8086
+  username: kpr
+  password: kpr.2024@117.influxdb
+  database: iot

+ 11 - 0
iot-server/iot-server-data/src/main/resources/application-local.yml

@@ -0,0 +1,11 @@
+spring:
+  datasource:
+    username: postgres
+    password: Al123456
+    url: jdbc:postgresql://118.178.230.53:5532/kpr-iot
+    driver-class-name: org.postgresql.Driver
+influxdb:
+  url: http://118.178.230.53:8186
+  username: influxdb
+  password: Al123456
+  database: iot

+ 8 - 0
iot-server/iot-server-data/src/main/resources/application.yml

@@ -0,0 +1,8 @@
+server:
+  port: 8003
+spring:
+  profiles:
+    active:
+      - baokang
+
+

+ 35 - 0
iot-server/iot-server-data/src/main/resources/logback-spring.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="LOG_PATH" value="logs"/>
+    <property name="LOG_NAME" value="iot-server-data"/>
+    <property name="LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Shanghai} [%thread] %-5level [%-40.40logger{39}] : %msg%n"/>
+    <property name="LOG_PATTERN_CONSOLE"
+              value="%d{yyyy-MM-dd HH:mm:ss,Asia/Shanghai} [%thread] %magenta(%-5level) %green([%-50.50class]) >>> %cyan(%msg) %n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>
+                ${LOG_PATTERN_CONSOLE}
+            </pattern>
+        </encoder>
+    </appender>
+
+    <!-- 滚动输出 -->
+    <appender name="fileLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!--日志文件输出的文件名-->
+            <FileNamePattern>${LOG_PATH}/${LOG_NAME}.%d{yyyy-MM-dd,Asia/Shanghai}.log</FileNamePattern>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- 日志输出级别 -->
+    <root level="info">
+        <appender-ref ref="consoleLog"/>
+        <appender-ref ref="fileLog"/>
+    </root>
+</configuration>

+ 22 - 0
iot-server/iot-server-data/src/test/java/TestApplication.java

@@ -0,0 +1,22 @@
+import com.shkpr.iot.server.data.IotServerDataApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 测试启动类
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1
+ */
+@SpringBootTest(classes = IotServerDataApplication.class)
+public class TestApplication {
+
+    /**
+     * 测试
+     */
+    @Test
+    public void test01() {
+
+    }
+
+}

+ 29 - 0
iot-server/iot-server-manager/pom.xml

@@ -0,0 +1,29 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>iot-server</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-server-manager</artifactId>
+    <!--项目显示名-->
+    <name>iot-server-manager</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-管理服务模块</description>
+    <!--打包类型-->
+    <packaging>jar</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--spring-web-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 18 - 0
iot-server/iot-server-manager/src/main/java/com/shkpr/iot/server/manager/IotServerManagerApplication.java

@@ -0,0 +1,18 @@
+package com.shkpr.iot.server.manager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+
+/**
+ * 启动类
+ *
+ * @author 欧阳劲驰
+ * @since 0.0.1-dev
+ */
+@SpringBootApplication
+public class IotServerManagerApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(IotServerManagerApplication.class);
+    }
+}

+ 2 - 0
iot-server/iot-server-manager/src/main/resources/application.yml

@@ -0,0 +1,2 @@
+server:
+  port: 8002

+ 38 - 0
iot-server/pom.xml

@@ -0,0 +1,38 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--父工程信息-->
+    <parent>
+        <groupId>com.shkpr</groupId>
+        <artifactId>kpr-iot</artifactId>
+        <version>0.0.1-dev</version>
+    </parent>
+    <!--项目名-->
+    <artifactId>iot-server</artifactId>
+    <!--项目显示名-->
+    <name>iot-server</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台-服务模块</description>
+    <modules>
+        <module>iot-server-manager</module>
+    </modules>
+    <!--打包类型-->
+    <packaging>pom</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+
+    <!--统一管理jar包版本-->
+    <properties>
+
+    </properties>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--认证模块-->
+        <dependency>
+            <groupId>com.shkpr</groupId>
+            <artifactId>iot-common-auth</artifactId>
+            <version>${iot.common.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 95 - 0
pom.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!--公司组织或名称-->
+    <groupId>com.shkpr</groupId>
+    <!--项目名-->
+    <artifactId>kpr-iot</artifactId>
+    <!--打包类型-->
+    <packaging>pom</packaging>
+    <!--版本号-->
+    <version>0.0.1-dev</version>
+    <!--项目显示名-->
+    <name>kpr-iot</name>
+    <!--项目描述-->
+    <description>科普睿物联网平台</description>
+
+    <!--子模块-->
+    <modules>
+        <!--物联网公共-->
+        <module>iot-common</module>
+        <!--物联网服务-->
+        <module>iot-server</module>
+        <!--物联网驱动-->
+        <module>iot-driver</module>
+        <!--物联网业务-->
+        <module>iot-modules</module>
+        <module>iot-driver/iot-driver-mqtt</module>
+        <module>iot-common/iot-common-driver</module>
+    </modules>
+
+    <!--统一管理jar包版本-->
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+
+        <!--公共包-->
+        <iot.common.version>0.0.1-dev</iot.common.version>
+        <!--驱动包-->
+        <iot.driver.version>0.0.1-dev</iot.driver.version>
+
+        <!--spring-boot-->
+        <spring.boot.version>2.7.18</spring.boot.version>
+        <!--mybatis-spring-->
+        <mybatis.spring.version>2.3.2</mybatis.spring.version>
+        <!--commons-->
+        <common.io.version>2.16.1</common.io.version>
+        <commons.collections4.version>4.4</commons.collections4.version>
+        <commons.rng.version>1.6</commons.rng.version>
+        <!--jjwt-->
+        <jjwt.version>0.9.0</jjwt.version>
+    </properties>
+
+    <!--项目依赖-->
+    <dependencies>
+        <!--lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <!--log4j-->
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-to-slf4j</artifactId>
+        </dependency>
+    </dependencies>
+
+    <!--Maven插件-->
+    <build>
+        <plugins>
+            <!--Maven编译插件-->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <!--依赖控制-->
+    <dependencyManagement>
+        <dependencies>
+            <!--spring依赖控制-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>