浏览代码

开源项目初始化

xiwa 3 年之前
父节点
当前提交
06b35af563
共有 100 个文件被更改,包括 3369 次插入0 次删除
  1. 3 0
      .gitignore
  2. 32 0
      LICENSE
  3. 49 0
      common/pom.xml
  4. 19 0
      common/src/main/java/cc/iotkit/common/exception/BizException.java
  5. 19 0
      common/src/main/java/cc/iotkit/common/exception/NotFoundException.java
  6. 20 0
      common/src/main/java/cc/iotkit/common/exception/OfflineException.java
  7. 101 0
      common/src/main/java/cc/iotkit/common/utils/CodecUtil.java
  8. 22 0
      common/src/main/java/cc/iotkit/common/utils/DeviceUtil.java
  9. 212 0
      common/src/main/java/cc/iotkit/common/utils/HexUtil.java
  10. 35 0
      common/src/main/java/cc/iotkit/common/utils/JsonUtil.java
  11. 26 0
      common/src/main/java/cc/iotkit/common/utils/ReflectUtil.java
  12. 28 0
      common/src/main/java/cc/iotkit/common/utils/UniqueIdUtil.java
  13. 43 0
      dao/pom.xml
  14. 9 0
      dao/src/main/java/cc/iotkit/dao/AligenieDeviceRepository.java
  15. 28 0
      dao/src/main/java/cc/iotkit/dao/AligenieProductDao.java
  16. 9 0
      dao/src/main/java/cc/iotkit/dao/AligenieProductRepository.java
  17. 9 0
      dao/src/main/java/cc/iotkit/dao/AppDesignRepository.java
  18. 9 0
      dao/src/main/java/cc/iotkit/dao/AppInfoRepository.java
  19. 59 0
      dao/src/main/java/cc/iotkit/dao/BaseDao.java
  20. 9 0
      dao/src/main/java/cc/iotkit/dao/CategoryRepository.java
  21. 11 0
      dao/src/main/java/cc/iotkit/dao/Constants.java
  22. 53 0
      dao/src/main/java/cc/iotkit/dao/DaoTool.java
  23. 61 0
      dao/src/main/java/cc/iotkit/dao/DeviceDao.java
  24. 15 0
      dao/src/main/java/cc/iotkit/dao/DeviceEventDao.java
  25. 13 0
      dao/src/main/java/cc/iotkit/dao/DeviceEventRepository.java
  26. 9 0
      dao/src/main/java/cc/iotkit/dao/DeviceRepository.java
  27. 9 0
      dao/src/main/java/cc/iotkit/dao/HomeRepository.java
  28. 28 0
      dao/src/main/java/cc/iotkit/dao/ProductDao.java
  29. 9 0
      dao/src/main/java/cc/iotkit/dao/ProductRepository.java
  30. 9 0
      dao/src/main/java/cc/iotkit/dao/SceneInfoRepository.java
  31. 22 0
      dao/src/main/java/cc/iotkit/dao/SceneLogDao.java
  32. 9 0
      dao/src/main/java/cc/iotkit/dao/SceneLogRepository.java
  33. 9 0
      dao/src/main/java/cc/iotkit/dao/SpaceDeviceRepository.java
  34. 9 0
      dao/src/main/java/cc/iotkit/dao/SpaceRepository.java
  35. 9 0
      dao/src/main/java/cc/iotkit/dao/TaskInfoRepository.java
  36. 22 0
      dao/src/main/java/cc/iotkit/dao/TaskLogDao.java
  37. 9 0
      dao/src/main/java/cc/iotkit/dao/TaskLogRepository.java
  38. 9 0
      dao/src/main/java/cc/iotkit/dao/ThingModelRepository.java
  39. 9 0
      dao/src/main/java/cc/iotkit/dao/UserAccountRepository.java
  40. 10 0
      dao/src/main/java/cc/iotkit/dao/UserActionLogRepository.java
  41. 15 0
      dao/src/main/java/cc/iotkit/dao/UserInfoDao.java
  42. 9 0
      dao/src/main/java/cc/iotkit/dao/UserInfoRepository.java
  43. 31 0
      dao/src/main/java/cc/iotkit/dao/config/RepositoryConfig.java
  44. 二进制
      device-server/.DS_Store
  45. 35 0
      device-server/device-api/pom.xml
  46. 44 0
      device-server/device-api/src/main/java/cc/iotkit/deviceapi/IDeviceManager.java
  47. 16 0
      device-server/device-api/src/main/java/cc/iotkit/deviceapi/IDeviceService.java
  48. 31 0
      device-server/device-api/src/main/java/cc/iotkit/deviceapi/Service.java
  49. 二进制
      device-server/mqtt-auth/.DS_Store
  50. 95 0
      device-server/mqtt-auth/pom.xml
  51. 12 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/Application.java
  52. 9 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/config/Constants.java
  53. 84 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/controller/MqttAuthController.java
  54. 53 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/dao/DaoTool.java
  55. 53 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/dao/DeviceDao.java
  56. 19 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/model/EmqAcl.java
  57. 18 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/model/EmqAuthInfo.java
  58. 75 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/DeviceMqttAuth.java
  59. 67 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/DeviceService.java
  60. 12 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/MqttAuth.java
  61. 42 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/SysMqttAuth.java
  62. 18 0
      device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/WxMqttAuth.java
  63. 5 0
      device-server/mqtt-auth/src/main/resources/application-dev.yml
  64. 5 0
      device-server/mqtt-auth/src/main/resources/application.yml
  65. 81 0
      device-server/mqtt-auth/src/main/resources/logback-spring.xml
  66. 31 0
      device-server/mqtt-auth/src/test/java/SupperUser.java
  67. 13 0
      device-server/mqtt-auth/src/test/java/SysMqttAuth.java
  68. 二进制
      device-server/mqtt-server/.DS_Store
  69. 二进制
      device-server/mqtt-server/log/error.2022-01-10.0.gz
  70. 二进制
      device-server/mqtt-server/log/error.2022-01-11.0.gz
  71. 二进制
      device-server/mqtt-server/log/error.2022-01-12.0.gz
  72. 二进制
      device-server/mqtt-server/log/error.2022-01-13.0.gz
  73. 二进制
      device-server/mqtt-server/log/info.2022-01-10.0.gz
  74. 二进制
      device-server/mqtt-server/log/info.2022-01-11.0.gz
  75. 二进制
      device-server/mqtt-server/log/info.2022-01-12.0.gz
  76. 二进制
      device-server/mqtt-server/log/info.2022-01-13.0.gz
  77. 123 0
      device-server/mqtt-server/pom.xml
  78. 14 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/Application.java
  79. 43 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/config/CacheConfig.java
  80. 25 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/config/Constants.java
  81. 42 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/config/GlobalExceptionHandler.java
  82. 154 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/config/MqttConfig.java
  83. 55 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/BaseDao.java
  84. 53 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DaoTool.java
  85. 62 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DeviceDao.java
  86. 13 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DeviceEventRepository.java
  87. 9 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DeviceRepository.java
  88. 9 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/ThingModelRepository.java
  89. 63 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/DisconnectedHandler.java
  90. 49 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/EventReportHandler.java
  91. 162 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/MqttConsumerHandler.java
  92. 14 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/MqttHandler.java
  93. 62 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/PropertyPostHandler.java
  94. 47 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/PropertyReplyHandler.java
  95. 37 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/RegisterHandler.java
  96. 49 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/ServiceReplyHandler.java
  97. 210 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/service/DeviceService.java
  98. 40 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/service/IMqttSender.java
  99. 60 0
      device-server/mqtt-server/src/main/java/cc/iotkit/server/service/ThingModelService.java
  100. 26 0
      device-server/mqtt-server/src/main/resources/application-dev.yml

+ 3 - 0
.gitignore

@@ -21,3 +21,6 @@
 
 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
 hs_err_pid*
+.idea
+target
+

+ 32 - 0
LICENSE

@@ -0,0 +1,32 @@
+# iotkit-parent
+
+#### 介绍
+iot系统后台项目,包含了mqtt认证服务、mqtt接入服务、web管理后台、规则引擎、第三方接入服务等模块。
+
+
+#### 软件架构
+软件架构说明
+本系统采用springboot、mongodb、mqtt(EMQX)、keycloak等框架和第三方软件
+
+
+#### 安装教程
+
+1.  EMQX安装和配置
+2.  keycloak安装和配置
+3.  mongodb安装和配置
+4.  本程序配置
+
+#### 使用说明
+
+1.  技术文档
+    https://ztktkv.yuque.com/books/share/b96f1fee-41d8-4da3-9e22-b73aeb1e29ed
+2.  系统操作文档
+
+
+#### 参与贡献
+
+1.  Fork 本仓库
+2.  新建 Feat_xxx 分支
+3.  提交代码
+4.  新建 Pull Request
+

+ 49 - 0
common/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>iotkit-parent</artifactId>
+        <groupId>cc.iotkit</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common</artifactId>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 19 - 0
common/src/main/java/cc/iotkit/common/exception/BizException.java

@@ -0,0 +1,19 @@
+package cc.iotkit.common.exception;
+
+public class BizException extends RuntimeException{
+
+    public BizException() {
+    }
+
+    public BizException(String message) {
+        super(message);
+    }
+
+    public BizException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BizException(Throwable cause) {
+        super(cause);
+    }
+}

+ 19 - 0
common/src/main/java/cc/iotkit/common/exception/NotFoundException.java

@@ -0,0 +1,19 @@
+package cc.iotkit.common.exception;
+
+
+public class NotFoundException extends BizException {
+    public NotFoundException() {
+    }
+
+    public NotFoundException(String message) {
+        super(message);
+    }
+
+    public NotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public NotFoundException(Throwable cause) {
+        super(cause);
+    }
+}

+ 20 - 0
common/src/main/java/cc/iotkit/common/exception/OfflineException.java

@@ -0,0 +1,20 @@
+package cc.iotkit.common.exception;
+
+
+public class OfflineException extends BizException {
+
+    public OfflineException() {
+    }
+
+    public OfflineException(String message) {
+        super(message);
+    }
+
+    public OfflineException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public OfflineException(Throwable cause) {
+        super(cause);
+    }
+}

+ 101 - 0
common/src/main/java/cc/iotkit/common/utils/CodecUtil.java

@@ -0,0 +1,101 @@
+package cc.iotkit.common.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import sun.misc.BASE64Decoder;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.spec.SecretKeySpec;
+
+public class CodecUtil {
+
+    private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
+
+    /**
+     * base 64 encode
+     *
+     * @param bytes 待编码的byte[]
+     * @return 编码后的base 64 code
+     */
+    private static String base64Encode(byte[] bytes) {
+        return Base64.encodeBase64String(bytes);
+    }
+
+    /**
+     * base 64 decode
+     *
+     * @param base64Code 待解码的base 64 code
+     * @return 解码后的byte[]
+     * @throws Exception 抛出异常
+     */
+    private static byte[] base64Decode(String base64Code) throws Exception {
+        return StringUtils.isEmpty(base64Code) ? null : new BASE64Decoder().decodeBuffer(base64Code);
+    }
+
+
+    /**
+     * AES加密
+     *
+     * @param content    待加密的内容
+     * @param encryptKey 加密密钥
+     * @return 加密后的byte[]
+     */
+    private static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
+        KeyGenerator kgen = KeyGenerator.getInstance("AES");
+        kgen.init(128);
+        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
+        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
+
+        return cipher.doFinal(content.getBytes("utf-8"));
+    }
+
+
+    /**
+     * AES加密为base 64 code
+     *
+     * @param content    待加密的内容
+     * @param encryptKey 加密密钥
+     * @return 加密后的base 64 code
+     */
+    public static String aesEncrypt(String content, String encryptKey) throws Exception {
+        String result = base64Encode(aesEncryptToBytes(content, encryptKey));
+        return HexUtil.toHexString(result.getBytes());
+    }
+
+    /**
+     * AES解密
+     *
+     * @param encryptBytes 待解密的byte[]
+     * @param decryptKey   解密密钥
+     * @return 解密后的String
+     */
+    private static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
+        KeyGenerator kgen = KeyGenerator.getInstance("AES");
+        kgen.init(128);
+
+        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
+        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
+        byte[] decryptBytes = cipher.doFinal(encryptBytes);
+
+        return new String(decryptBytes);
+    }
+
+    /**
+     * 将base 64 code AES解密
+     *
+     * @param encryptStr 待解密的base 64 code
+     * @param decryptKey 解密密钥
+     * @return 解密后的string
+     */
+    public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
+        encryptStr = new String(HexUtil.parseHex(encryptStr));
+        return StringUtils.isEmpty(encryptStr) ? "" : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
+    }
+
+    public static String aesDecryptHex(String encryptStr, String decryptKey) throws Exception {
+        encryptStr = new String(HexUtil.parseHex(encryptStr));
+        return StringUtils.isEmpty(encryptStr) ? "" : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
+    }
+
+}

+ 22 - 0
common/src/main/java/cc/iotkit/common/utils/DeviceUtil.java

@@ -0,0 +1,22 @@
+package cc.iotkit.common.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+public class DeviceUtil {
+
+    public static PkDn getPkDn(String pkDn) {
+        String[] arr = pkDn.split("/");
+        return new PkDn(arr[0], arr[1]);
+    }
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PkDn {
+        private String productKey;
+        private String deviceName;
+    }
+
+}

+ 212 - 0
common/src/main/java/cc/iotkit/common/utils/HexUtil.java

@@ -0,0 +1,212 @@
+package cc.iotkit.common.utils;
+
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.nio.ByteBuffer;
+
+public class HexUtil {
+
+    private static final char[] CHARS_TABLES = "0123456789ABCDEF".toCharArray();
+    static final byte[] BYTES = new byte[128];
+
+    static {
+        for (int i = 0; i < 10; i++) {
+            BYTES['0' + i] = (byte) i;
+            BYTES['A' + i] = (byte) (10 + i);
+            BYTES['a' + i] = (byte) (10 + i);
+        }
+    }
+
+    public static String toHexString(byte[] aBytes) {
+        return toHexString(aBytes, 0, aBytes.length);
+    }
+
+    public static String toFormattedHexString(byte[] aBytes) {
+        return toFormattedHexString(aBytes, 0, aBytes.length);
+    }
+
+    public static String toHexString(byte[] aBytes, int aLength) {
+        return toHexString(aBytes, 0, aLength);
+    }
+
+    public static byte[] parseHex(String aHexString) {
+        char[] src = aHexString.replace("\n", "").replace(" ", "").toUpperCase().toCharArray();
+        byte[] dst = new byte[src.length / 2];
+
+        for (int si = 0, di = 0; di < dst.length; di++) {
+            byte high = BYTES[src[si++] & 0x7f];
+            byte low = BYTES[src[si++] & 0x7f];
+            dst[di] = (byte) ((high << 4) + low);
+        }
+
+        return dst;
+    }
+
+    public static String toFormattedHexString(byte[] aBytes, int aOffset, int aLength) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        sb.append(aLength);
+        sb.append("] :");
+        for (int si = aOffset, di = 0; si < aOffset + aLength; si++, di++) {
+            byte b = aBytes[si];
+            if (di % 4 == 0) {
+                sb.append("  ");
+            } else {
+                sb.append(' ');
+            }
+            sb.append(CHARS_TABLES[(b & 0xf0) >>> 4]);
+            sb.append(CHARS_TABLES[(b & 0x0f)]);
+
+        }
+
+        return sb.toString();
+
+    }
+
+    public static String toHexString(byte[] aBytes, int aOffset, int aLength) {
+        char[] dst = new char[aLength * 2];
+
+        for (int si = aOffset, di = 0; si < aOffset + aLength; si++) {
+            byte b = aBytes[si];
+            dst[di++] = CHARS_TABLES[(b & 0xf0) >>> 4];
+            dst[di++] = CHARS_TABLES[(b & 0x0f)];
+        }
+
+        return new String(dst);
+    }
+
+    public static String unwrapCharString(String charStr) {
+        byte[] bytes = parseHex(charStr);
+        StringBuilder rawStr = new StringBuilder();
+        for (byte aByte : bytes) {
+            rawStr.append((char) aByte);
+        }
+        return rawStr.toString();
+    }
+
+    /**
+     * int转bytes
+     */
+    public static byte[] intToBytes(int x) {
+        ByteBuffer buffer = ByteBuffer.allocate(4);
+        buffer.putInt(0, x);
+        return buffer.array();
+    }
+
+    public static int checkSum(ByteBuffer buffer) {
+        buffer.flip();
+        byte sum = 0;
+        while (buffer.hasRemaining()) {
+            sum += buffer.get();
+        }
+        buffer.limit(buffer.capacity());
+        return sum % 256;
+    }
+
+    public static byte[] toLowerBytes(byte[] bytes) {
+        int len = bytes.length;
+        byte[] r = new byte[len];
+        for (int i = 0; i < len; i++) {
+            r[len - i - 1] = bytes[i];
+        }
+        return r;
+    }
+
+    public static int toLowerInt(byte[] bytes) {
+        int len = bytes.length;
+        byte[] r = new byte[len];
+        for (int i = 0; i < len; i++) {
+            r[len - i - 1] = (byte) (bytes[i] - 0x33);
+        }
+        return ByteBuffer.wrap(r).getInt();
+    }
+
+    public static byte[] shortToBytes(short x) {
+        ByteBuffer buffer = ByteBuffer.allocate(2);
+        buffer.putShort(0, x);
+        return buffer.array();
+    }
+
+    public static String readString(ByteBuffer buffer, int len) {
+        byte[] dest = new byte[len];
+        buffer.get(dest, 0, len);
+        return new String(dest);
+    }
+
+//    public static int readLowerInt(ByteBuffer buffer, int len) {
+//        int r = 0;
+//        for (int i = 0; i < len; i++) {
+//            byte b = buffer.get();
+//            r += (i == 0 ? b - 0x33 : ((b - 0x33) * Math.pow(10, i)));
+//        }
+//        return r;
+//    }
+
+    public static String readHexIntString(ByteBuffer buffer) {
+        int b = buffer.get();
+        String hex = Integer.toHexString(b - 0x33).replace("f", "");
+        return StringUtils.leftPad(hex, 2, "0");
+    }
+
+    public static byte[] add33Bytes(byte[] bytes) {
+        for (int i = 0; i < bytes.length; i++) {
+            bytes[i] = (byte) (bytes[i] + 0x33);
+        }
+        return bytes;
+    }
+
+    public static byte[] minus33Bytes(byte[] bytes) {
+        for (int i = 0; i < bytes.length; i++) {
+            bytes[i] = (byte) (bytes[i] - 0x33);
+        }
+        return bytes;
+    }
+
+    public static byte[] readBytes(ByteBuffer buffer, int len) {
+        byte[] data = new byte[len];
+        for (int i = 0; i < len; i++) {
+            data[i] = buffer.get();
+        }
+        return data;
+    }
+
+    public static byte[] readAndMinus33Bytes(ByteBuffer buffer, int len) {
+        byte[] data = new byte[len];
+        for (int i = 0; i < len; i++) {
+            data[i] = (byte) (buffer.get() - 0x33);
+        }
+        return data;
+    }
+
+    public static int bcdInt(String row) {
+        String bcd = bcdString(row);
+        bcd = bcd.replace("FF", "0");
+        return Integer.parseInt(bcd);
+    }
+
+    public static int bcdInt(ByteBuffer buffer, int len) {
+        byte[] bytes = readAndMinus33Bytes(buffer, len);
+        return bcdInt(HexUtil.toHexString(bytes));
+    }
+
+    public static String bcdString(String row) {
+        char[] chars = row.toCharArray();
+        int len = chars.length;
+        char[] newChars = new char[len];
+
+        for (int i = 0; i < len; i += 2) {
+            newChars[i] = chars[len - i - 2];
+            newChars[i + 1] = chars[len - i - 1];
+        }
+        return String.valueOf(newChars);
+    }
+
+    public static byte[] intBcdAdd33(int v, int len) {
+        String strV = String.valueOf(v);
+        strV = StringUtils.leftPad(strV, len * 2, '0');
+
+        return add33Bytes(HexUtil.parseHex(bcdString(strV)));
+    }
+
+}

+ 35 - 0
common/src/main/java/cc/iotkit/common/utils/JsonUtil.java

@@ -0,0 +1,35 @@
+package cc.iotkit.common.utils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import lombok.SneakyThrows;
+
+public final class JsonUtil {
+
+    private final static ObjectMapper MAPPER = new ObjectMapper()
+            .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
+            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+
+    @SneakyThrows
+    public static String toJsonString(Object obj) {
+        return MAPPER.writeValueAsString(obj);
+    }
+
+    @SneakyThrows
+    public static <T> T parse(String json, Class<T> cls) {
+        return MAPPER.readValue(json, cls);
+    }
+
+    @SneakyThrows
+    public static <T> T parse(String json, TypeReference<T> type) {
+        return MAPPER.readValue(json, type);
+    }
+
+    @SneakyThrows
+    public static JsonNode parse(String json) {
+        return MAPPER.readTree(json);
+    }
+}

+ 26 - 0
common/src/main/java/cc/iotkit/common/utils/ReflectUtil.java

@@ -0,0 +1,26 @@
+package cc.iotkit.common.utils;
+
+
+import lombok.SneakyThrows;
+import org.apache.commons.beanutils.BeanMap;
+import org.apache.commons.beanutils.BeanUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ReflectUtil {
+
+    @SneakyThrows
+    public static <T> T copyNoNulls(T from, T to) {
+        Map<String, Object> map = new HashMap<>();
+        new BeanMap(from).forEach((key, value) -> {
+            if (value == null) {
+                return;
+            }
+            map.put(key.toString(), value);
+        });
+        BeanUtils.populate(to, map);
+        return to;
+    }
+
+}

+ 28 - 0
common/src/main/java/cc/iotkit/common/utils/UniqueIdUtil.java

@@ -0,0 +1,28 @@
+package cc.iotkit.common.utils;
+
+import org.apache.commons.lang3.RandomUtils;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class UniqueIdUtil {
+
+    private static final int MACHINE_ID = RandomUtils.nextInt(10, 99);
+
+    private static final AtomicInteger SEQUENCE = new AtomicInteger(1000);
+
+    public static String newRequestId() {
+        return newUniqueId("RID");
+    }
+
+    public static String newUniqueId(String prefix) {
+        int id = SEQUENCE.getAndIncrement();
+        if (id >= 5000) {
+            SEQUENCE.set(1000);
+        }
+
+        return prefix + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + id + MACHINE_ID;
+    }
+
+}

+ 43 - 0
dao/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>iotkit-parent</artifactId>
+        <groupId>cc.iotkit</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dao</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>model</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/AligenieDeviceRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.aligenie.AligenieDevice;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AligenieDeviceRepository extends MongoRepository<AligenieDevice, String> {
+}

+ 28 - 0
dao/src/main/java/cc/iotkit/dao/AligenieProductDao.java

@@ -0,0 +1,28 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.aligenie.AligenieProduct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.domain.Example;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class AligenieProductDao extends BaseDao<AligenieProduct> {
+
+    private final AligenieProductRepository aligenieProductRepository;
+
+    @Autowired
+    public AligenieProductDao(MongoTemplate mongoTemplate,
+                              AligenieProductRepository aligenieProductRepository) {
+        super(mongoTemplate, AligenieProduct.class);
+        this.aligenieProductRepository = aligenieProductRepository;
+    }
+
+    @Cacheable(value = "cache_getAligenieProduct", key = "'getAligenieProduct'+#pk", unless = "#result == null")
+    public AligenieProduct getAligenieProduct(String pk) {
+        return aligenieProductRepository.findOne(Example.of(
+                AligenieProduct.builder().productKey(pk).build()
+        )).orElse(null);
+    }
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/AligenieProductRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.aligenie.AligenieProduct;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AligenieProductRepository extends MongoRepository<AligenieProduct, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/AppDesignRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.AppDesign;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AppDesignRepository extends MongoRepository<AppDesign, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/AppInfoRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.AppInfo;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AppInfoRepository extends MongoRepository<AppInfo, String> {
+}

+ 59 - 0
dao/src/main/java/cc/iotkit/dao/BaseDao.java

@@ -0,0 +1,59 @@
+package cc.iotkit.dao;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+
+import java.util.List;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+public class BaseDao<T> {
+
+    protected MongoTemplate mongoTemplate;
+
+    private Class<T> cls;
+
+    public BaseDao(MongoTemplate mongoTemplate, Class<T> cls) {
+        this.mongoTemplate = mongoTemplate;
+        this.cls = cls;
+    }
+
+    public List<T> find(Criteria condition) {
+        Query query = new Query();
+        query.addCriteria(condition);
+        return mongoTemplate.find(query, cls);
+    }
+
+    public List<T> find(Criteria condition, long skip, int count, Sort.Order order) {
+        Query query = new Query();
+        query.addCriteria(condition)
+                .with(Sort.by(order))
+                .skip(skip)
+                .limit(count);
+        return mongoTemplate.find(query, cls);
+    }
+
+    public long count(Criteria condition) {
+        Query query = new Query();
+        query.addCriteria(condition);
+        return mongoTemplate.count(query, cls);
+    }
+
+    public <T> T save(String id, T entity) {
+        if (id == null) {
+            return mongoTemplate.save(entity);
+        } else {
+            mongoTemplate.updateFirst(query(where("_id").is(id)),
+                    DaoTool.update(entity), entity.getClass());
+            return (T) mongoTemplate.findById(id, entity.getClass());
+        }
+    }
+
+    public <T> T save(T entity) {
+        return mongoTemplate.save(entity);
+    }
+
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/CategoryRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.Category;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CategoryRepository extends MongoRepository<Category, String> {
+}

+ 11 - 0
dao/src/main/java/cc/iotkit/dao/Constants.java

@@ -0,0 +1,11 @@
+package cc.iotkit.dao;
+
+public interface Constants {
+
+    String PRODUCT_CACHE = "product_cache";
+
+    String DEVICE_CACHE = "device_cache";
+
+    String THING_MODEL_CACHE = "thing_model_cache";
+
+}

+ 53 - 0
dao/src/main/java/cc/iotkit/dao/DaoTool.java

@@ -0,0 +1,53 @@
+package cc.iotkit.dao;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.commons.beanutils.BeanMap;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DaoTool {
+
+    public static void update(Update update, List<Prop> props) {
+        for (Prop pro : props) {
+            update.set(pro.getName(), pro.getValue());
+        }
+    }
+
+    public static List<Prop> getProp(String key, Object value) {
+        List<Prop> props = new ArrayList<>();
+        if (value instanceof Map) {
+            Set<Map.Entry<String, Object>> entrySet = ((Map) value).entrySet();
+            for (Map.Entry<String, Object> entry : entrySet) {
+                props.addAll(getProp(key + "." + entry.getKey(), entry.getValue()));
+            }
+        } else if (value != null && !(value instanceof Class)) {
+            props.add(new Prop(key, value));
+        }
+        return props;
+    }
+
+    @SneakyThrows
+    public static <T> Update update(T obj) {
+        Map<Object, Object> pros = new BeanMap(obj);
+        Update update = new Update();
+        for (Map.Entry<Object, Object> entry : pros.entrySet()) {
+            update(update, DaoTool.getProp(entry.getKey().toString(), entry.getValue()));
+        }
+        return update;
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    static class Prop {
+        private String name;
+        private Object value;
+    }
+}

+ 61 - 0
dao/src/main/java/cc/iotkit/dao/DeviceDao.java

@@ -0,0 +1,61 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.device.DeviceInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Repository;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+@Repository
+public class DeviceDao extends BaseDao<DeviceInfo> {
+
+    @Autowired
+    private DeviceRepository deviceRepository;
+
+    @Autowired
+    public DeviceDao(MongoTemplate mongoTemplate) {
+        super(mongoTemplate, DeviceInfo.class);
+    }
+
+    public void addDevice(DeviceInfo device) {
+        device.setCreateAt(System.currentTimeMillis());
+        mongoTemplate.insert(device);
+    }
+
+    public void updateDevice(DeviceInfo device) {
+        if (device.getDeviceId() == null) {
+            return;
+        }
+        mongoTemplate.updateFirst(query(where("deviceId").is(device.getDeviceId())),
+                DaoTool.update(device), DeviceInfo.class);
+    }
+
+    public void updateDeviceByPkAndDn(DeviceInfo device) {
+        if (device.getProductKey() == null || device.getDeviceName() == null) {
+            return;
+        }
+        mongoTemplate.updateFirst(query(where("productKey").is(device.getProductKey()).
+                        and("deviceName").is(device.getDeviceName())),
+                DaoTool.update(device), DeviceInfo.class);
+    }
+
+    @Cacheable(value = "deviceInfoCache", key = "#pk+'_'+#dn")
+    public DeviceInfo getByPkAndDn(String pk, String dn) {
+        Query query = query(where("productKey").is(pk).and("deviceName").is(dn));
+        return mongoTemplate.findOne(query, DeviceInfo.class);
+    }
+
+    public DeviceInfo getByDeviceId(String deviceId) {
+        Query query = query(where("deviceId").is(deviceId));
+        return mongoTemplate.findOne(query, DeviceInfo.class);
+    }
+
+    @Cacheable(value = Constants.DEVICE_CACHE, key = "#deviceId")
+    public DeviceInfo get(String deviceId) {
+        return deviceRepository.findById(deviceId).orElse(new DeviceInfo());
+    }
+}

+ 15 - 0
dao/src/main/java/cc/iotkit/dao/DeviceEventDao.java

@@ -0,0 +1,15 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.device.message.DeviceEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class DeviceEventDao extends BaseDao<DeviceEvent> {
+
+    @Autowired
+    public DeviceEventDao(MongoTemplate mongoTemplate) {
+        super(mongoTemplate, DeviceEvent.class);
+    }
+}

+ 13 - 0
dao/src/main/java/cc/iotkit/dao/DeviceEventRepository.java

@@ -0,0 +1,13 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.device.message.DeviceEvent;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface DeviceEventRepository extends MongoRepository<DeviceEvent, String> {
+
+    Page<DeviceEvent> findByDeviceId(String deviceId, Pageable pageable);
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/DeviceRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.device.DeviceInfo;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface DeviceRepository extends MongoRepository<DeviceInfo, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/HomeRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.space.Home;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface HomeRepository extends MongoRepository<Home, String> {
+}

+ 28 - 0
dao/src/main/java/cc/iotkit/dao/ProductDao.java

@@ -0,0 +1,28 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ThingModel;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.domain.Example;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class ProductDao {
+
+    @Autowired
+    private ProductRepository productRepository;
+    @Autowired
+    private ThingModelRepository thingModelRepository;
+
+    @Cacheable(value = Constants.PRODUCT_CACHE, key = "'getProductById'+#pk", unless = "#result == null")
+    public Product get(String pk) {
+        return productRepository.findById(pk).orElse(new Product());
+    }
+
+    @Cacheable(value = Constants.THING_MODEL_CACHE, key = "'getThingModel'+#pk", unless = "#result == null")
+    public ThingModel getThingModel(String pk) {
+        return thingModelRepository.findOne(Example.of(ThingModel.builder()
+                .productKey(pk).build())).orElse(new ThingModel());
+    }
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/ProductRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.Product;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ProductRepository extends MongoRepository<Product, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/SceneInfoRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.rule.SceneInfo;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SceneInfoRepository extends MongoRepository<SceneInfo, String> {
+}

+ 22 - 0
dao/src/main/java/cc/iotkit/dao/SceneLogDao.java

@@ -0,0 +1,22 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.rule.SceneLog;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Repository;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+
+@Repository
+public class SceneLogDao extends BaseDao<SceneLog> {
+
+    @Autowired
+    public SceneLogDao(MongoTemplate mongoTemplate) {
+        super(mongoTemplate, SceneLog.class);
+    }
+
+    public void deleteLogs(String sceneId) {
+        this.mongoTemplate.remove(Query.query(where("sceneId").is(sceneId)), SceneLog.class);
+    }
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/SceneLogRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.rule.SceneLog;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SceneLogRepository extends MongoRepository<SceneLog, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/SpaceDeviceRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.space.SpaceDevice;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SpaceDeviceRepository extends MongoRepository<SpaceDevice, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/SpaceRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.space.Space;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SpaceRepository extends MongoRepository<Space, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/TaskInfoRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.rule.TaskInfo;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TaskInfoRepository extends MongoRepository<TaskInfo, String> {
+}

+ 22 - 0
dao/src/main/java/cc/iotkit/dao/TaskLogDao.java

@@ -0,0 +1,22 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.rule.TaskLog;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Repository;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+
+@Repository
+public class TaskLogDao extends BaseDao<TaskLog> {
+
+    @Autowired
+    public TaskLogDao(MongoTemplate mongoTemplate) {
+        super(mongoTemplate, TaskLog.class);
+    }
+
+    public void deleteLogs(String taskId) {
+        this.mongoTemplate.remove(Query.query(where("taskId").is(taskId)), TaskLog.class);
+    }
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/TaskLogRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.rule.TaskLog;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TaskLogRepository extends MongoRepository<TaskLog, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/ThingModelRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.ThingModel;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ThingModelRepository extends MongoRepository<ThingModel, String> {
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/UserAccountRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.UserAccount;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserAccountRepository extends MongoRepository<UserAccount, String> {
+}

+ 10 - 0
dao/src/main/java/cc/iotkit/dao/UserActionLogRepository.java

@@ -0,0 +1,10 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.UserActionLog;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserActionLogRepository extends MongoRepository<UserActionLog, String> {
+
+}

+ 15 - 0
dao/src/main/java/cc/iotkit/dao/UserInfoDao.java

@@ -0,0 +1,15 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.UserInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class UserInfoDao extends BaseDao<UserInfo> {
+
+    @Autowired
+    public UserInfoDao(MongoTemplate mongoTemplate) {
+        super(mongoTemplate, UserInfo.class);
+    }
+}

+ 9 - 0
dao/src/main/java/cc/iotkit/dao/UserInfoRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.UserInfo;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserInfoRepository extends MongoRepository<UserInfo, String> {
+}

+ 31 - 0
dao/src/main/java/cc/iotkit/dao/config/RepositoryConfig.java

@@ -0,0 +1,31 @@
+package cc.iotkit.dao.config;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.mongodb.MongoDatabaseFactory;
+import org.springframework.data.mongodb.core.convert.DbRefResolver;
+import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
+import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
+import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
+import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+
+@Configuration
+@EnableMongoRepositories(basePackages = "cc.iotkit.dao")
+public class RepositoryConfig {
+
+    @Bean
+    public MappingMongoConverter mappingMongoConverter(
+            MongoDatabaseFactory factory,
+            MongoMappingContext context,
+            BeanFactory beanFactory) {
+        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
+        MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, context);
+        mappingMongoConverter.setCustomConversions(beanFactory.getBean(CustomConversions.class));
+        mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
+        return mappingMongoConverter;
+    }
+
+}

二进制
device-server/.DS_Store


+ 35 - 0
device-server/device-api/pom.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>device-server</artifactId>
+        <groupId>cc.iotkit</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>device-api</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-openfeign-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>model</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 44 - 0
device-server/device-api/src/main/java/cc/iotkit/deviceapi/IDeviceManager.java

@@ -0,0 +1,44 @@
+package cc.iotkit.deviceapi;
+
+import cc.iotkit.model.device.DeviceInfo;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@Component
+@FeignClient(value = "iot-device-manager",url = "localhost:8091")
+public interface IDeviceManager {
+
+    /**
+     * 设备注册
+     */
+    @PostMapping("/register")
+    @ResponseBody
+    DeviceInfo register(@RequestParam("parentId") String parentId,
+                        @RequestParam("productKey") String productKey,
+                        @RequestParam("deviceName") String deviceName,
+                        @RequestParam("model") String model);
+
+    /**
+     * 解绑子设备
+     */
+    @PostMapping("/{deviceId}/unbind")
+    void unbind(@PathVariable("deviceId") String deviceId);
+
+    /**
+     * 设置属性
+     */
+    @PostMapping("/{deviceId}/property/set")
+    String setProperty(@PathVariable("deviceId") String deviceId,
+                       @RequestBody Map<String, Object> properties);
+
+    /**
+     * 调用服务
+     */
+    @PostMapping("/{deviceId}/{identifier}/invoke")
+    String invokeService(@PathVariable("deviceId") String deviceId,
+                         @PathVariable("identifier") String identifier,
+                         @RequestBody Map<String, Object> properties);
+}

+ 16 - 0
device-server/device-api/src/main/java/cc/iotkit/deviceapi/IDeviceService.java

@@ -0,0 +1,16 @@
+package cc.iotkit.deviceapi;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.*;
+
+@Component
+@FeignClient(value = "iot-device-service", url = "localhost:8091")
+public interface IDeviceService {
+
+    /**
+     * 调用服务
+     */
+    @PostMapping("/invoke")
+    String invoke(@RequestBody Service service);
+}

+ 31 - 0
device-server/device-api/src/main/java/cc/iotkit/deviceapi/Service.java

@@ -0,0 +1,31 @@
+package cc.iotkit.deviceapi;
+
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class Service {
+
+    private String device;
+
+    private String identifier;
+
+    private List<Parameter> inputData;
+
+    public Map<String, Object> parseInputData() {
+        Map<String, Object> data = new HashMap<>();
+        for (Parameter p : inputData) {
+            data.put(p.getIdentifier(), p.getValue());
+        }
+        return data;
+    }
+
+    @Data
+    public static class Parameter {
+        private String identifier;
+        private Object value;
+    }
+}

二进制
device-server/mqtt-auth/.DS_Store


+ 95 - 0
device-server/mqtt-auth/pom.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>device-server</artifactId>
+        <groupId>cc.iotkit</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mqtt-auth</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 12 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/Application.java

@@ -0,0 +1,12 @@
+package cc.iotkit.mqttauth;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+}

+ 9 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/config/Constants.java

@@ -0,0 +1,9 @@
+package cc.iotkit.mqttauth.config;
+
+public interface Constants {
+
+    String MQTT_SECRET = "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU";
+
+    String ACCOUNT_SECRET = "3n1z33kzvpgz1foijpkepyd3e8tw84us";
+
+}

+ 84 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/controller/MqttAuthController.java

@@ -0,0 +1,84 @@
+package cc.iotkit.mqttauth.controller;
+
+
+import cc.iotkit.common.utils.CodecUtil;
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.mqttauth.config.Constants;
+import cc.iotkit.mqttauth.model.EmqAcl;
+import cc.iotkit.mqttauth.model.EmqAuthInfo;
+import cc.iotkit.mqttauth.service.DeviceMqttAuth;
+import cc.iotkit.mqttauth.service.MqttAuth;
+import cc.iotkit.mqttauth.service.SysMqttAuth;
+import cc.iotkit.mqttauth.service.WxMqttAuth;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+
+@Slf4j
+@RestController
+public class MqttAuthController {
+
+    @Autowired
+    private DeviceMqttAuth deviceMqttAuth;
+    @Autowired
+    private WxMqttAuth wxMqttAuth;
+    @Autowired
+    private SysMqttAuth sysMqttAuth;
+
+    @PostMapping("/mqtt/auth")
+    public void auth(@RequestBody EmqAuthInfo auth) {
+        log.info("mqtt auth:" + JsonUtil.toJsonString(auth));
+        String clientId = auth.getClientid();
+        if (isSupperUser(clientId)) {
+            return;
+        }
+        MqttAuth mqttAuth = getMqttAuth(clientId);
+        mqttAuth.auth(auth);
+    }
+
+    @PostMapping("/mqtt/acl")
+    public void acl(@RequestBody EmqAcl acl) {
+        log.info("mqtt acl:" + JsonUtil.toJsonString(acl));
+        if (isSupperUser(acl.getClientid())) {
+            return;
+        }
+        MqttAuth mqttAuth = getMqttAuth(acl.getClientid());
+        mqttAuth.acl(acl);
+    }
+
+    @PostMapping("/mqtt/superuser")
+    public void superuser(@RequestBody EmqAcl acl, HttpServletResponse response) {
+        response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
+//        log.info("mqtt superuser:" + JsonUtil.toJsonString(acl));
+//        if (!isSupperUser(acl.getClientid())) {
+//            throw new RuntimeException("superuser check false.");
+//        }
+    }
+
+    public boolean isSupperUser(String clientId) {
+        try {
+            if (!clientId.startsWith("su_")) {
+                return false;
+            }
+            clientId = clientId.replaceFirst("su_", "");
+            return CodecUtil.aesDecrypt(clientId, Constants.MQTT_SECRET).startsWith("admin_");
+        } catch (Throwable e) {
+            log.error("aesDecrypt error.", e);
+            return false;
+        }
+    }
+
+    private MqttAuth getMqttAuth(String clientId) {
+        if (clientId.startsWith("wx_")) {
+            return wxMqttAuth;
+        } else if (clientId.startsWith("sy_")) {
+            return sysMqttAuth;
+        }
+        return deviceMqttAuth;
+    }
+
+}

+ 53 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/dao/DaoTool.java

@@ -0,0 +1,53 @@
+package cc.iotkit.mqttauth.dao;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.commons.beanutils.BeanMap;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DaoTool {
+
+    public static void update(Update update, List<Prop> props) {
+        for (Prop pro : props) {
+            update.set(pro.getName(), pro.getValue());
+        }
+    }
+
+    public static List<Prop> getProp(String key, Object value) {
+        List<Prop> props = new ArrayList<>();
+        if (value instanceof Map) {
+            Set<Map.Entry<String, Object>> entrySet = ((Map) value).entrySet();
+            for (Map.Entry<String, Object> entry : entrySet) {
+                props.addAll(getProp(key + "." + entry.getKey(), entry.getValue()));
+            }
+        } else if (value != null && !(value instanceof Class)) {
+            props.add(new Prop(key, value));
+        }
+        return props;
+    }
+
+    @SneakyThrows
+    public static <T> Update update(T obj) {
+        Map<Object, Object> pros = new BeanMap(obj);
+        Update update = new Update();
+        for (Map.Entry<Object, Object> entry : pros.entrySet()) {
+            update(update, DaoTool.getProp(entry.getKey().toString(), entry.getValue()));
+        }
+        return update;
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    static class Prop {
+        private String name;
+        private Object value;
+    }
+}

+ 53 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/dao/DeviceDao.java

@@ -0,0 +1,53 @@
+package cc.iotkit.mqttauth.dao;
+
+import cc.iotkit.model.device.DeviceInfo;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+@Repository
+public class DeviceDao {
+
+    @Resource
+    private MongoTemplate mongoTemplate;
+
+    public void addDevice(DeviceInfo device) {
+        device.setId(device.getDeviceId());
+        device.setCreateAt(System.currentTimeMillis());
+        mongoTemplate.insert(device);
+    }
+
+    public void updateDevice(DeviceInfo device) {
+        if (device.getDeviceId() == null) {
+            return;
+        }
+        mongoTemplate.updateFirst(query(where("deviceId").is(device.getDeviceId())),
+                DaoTool.update(device), DeviceInfo.class);
+    }
+
+    public void updateDeviceByPkAndDn(DeviceInfo device) {
+        if (device.getProductKey() == null || device.getDeviceName() == null) {
+            return;
+        }
+        mongoTemplate.updateFirst(query(where("productKey").is(device.getProductKey()).
+                        and("deviceName").is(device.getDeviceName())),
+                DaoTool.update(device), DeviceInfo.class);
+    }
+
+    @Cacheable(value = "deviceInfoCache", key = "#pk+'_'+#dn")
+    public DeviceInfo getByPkAndDn(String pk, String dn) {
+        Query query = query(where("productKey").is(pk).and("deviceName").is(dn));
+        return mongoTemplate.findOne(query, DeviceInfo.class);
+    }
+
+    public DeviceInfo getByDeviceId(String deviceId) {
+        Query query = query(where("deviceId").is(deviceId));
+        return mongoTemplate.findOne(query, DeviceInfo.class);
+    }
+}

+ 19 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/model/EmqAcl.java

@@ -0,0 +1,19 @@
+package cc.iotkit.mqttauth.model;
+
+import lombok.Data;
+
+@Data
+public class EmqAcl {
+
+    private String access;
+
+    private String username;
+
+    private String clientid;
+
+    private String ipaddr;
+
+    private String protocol;
+
+    private String topic;
+}

+ 18 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/model/EmqAuthInfo.java

@@ -0,0 +1,18 @@
+package cc.iotkit.mqttauth.model;
+
+import lombok.Data;
+
+@Data
+public class EmqAuthInfo {
+
+    private String clientid;
+
+    private String password;
+
+    private String username;
+
+    private String ipaddress;
+
+    private String protocol;
+
+}

+ 75 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/DeviceMqttAuth.java

@@ -0,0 +1,75 @@
+package cc.iotkit.mqttauth.service;
+
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.mqttauth.config.Constants;
+import cc.iotkit.mqttauth.model.EmqAcl;
+import cc.iotkit.mqttauth.model.EmqAuthInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component("DeviceMqttAuth")
+public class DeviceMqttAuth implements MqttAuth {
+    @Autowired
+    private DeviceService deviceService;
+
+    @Override
+    public void auth(EmqAuthInfo auth) {
+
+        String clientId = auth.getClientid();
+        String[] pkDnAndModel = getPkDnAndModel(clientId);
+
+        String hmac = DigestUtils.md5Hex(Constants.MQTT_SECRET + clientId);
+        if (!hmac.equalsIgnoreCase(auth.getPassword())) {
+            throw new RuntimeException("password is illegal.");
+        }
+
+        DeviceInfo device = new DeviceInfo();
+        device.setProductKey(pkDnAndModel[0]);
+        device.setDeviceName(pkDnAndModel[1]);
+        device.setModel(pkDnAndModel[2]);
+        deviceService.register(device);
+    }
+
+    @Override
+    public void acl(EmqAcl acl) {
+        String[] pkDn = getPkDnFromTopic(acl.getTopic());
+        String pk = pkDn[2];
+        String dn = pkDn[3];
+        DeviceInfo device = deviceService.getByPkAndDn(pk, dn);
+        if (device == null) {
+            log.error("the device is not registered,pk:{},dn:{}", pk, dn);
+            return;
+        }
+
+        deviceService.online(pk, dn);
+    }
+
+    private String[] getPkDnAndModel(String clientId) {
+        if (StringUtils.isBlank(clientId)) {
+            throw new RuntimeException("clientId is blank.");
+        }
+        clientId += "_";
+
+        String[] pkDnAndModel = clientId.split("_", -1);
+        if (pkDnAndModel.length < 3) {
+            throw new RuntimeException("clientId is illegal.");
+        }
+        return pkDnAndModel;
+    }
+
+    private String[] getPkDnFromTopic(String topic) {
+        if (StringUtils.isBlank(topic)) {
+            throw new RuntimeException("topic is blank.");
+        }
+
+        String[] pkDn = topic.split("/", -1);
+        if (pkDn.length < 4) {
+            throw new RuntimeException("topic is illegal.");
+        }
+        return pkDn;
+    }
+}

+ 67 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/DeviceService.java

@@ -0,0 +1,67 @@
+package cc.iotkit.mqttauth.service;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.mqttauth.dao.DeviceDao;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class DeviceService {
+
+    @Autowired
+    private DeviceDao deviceDao;
+
+    public DeviceInfo register(DeviceInfo device) {
+        DeviceInfo deviceInfo = deviceDao.getByPkAndDn(device.getProductKey(), device.getDeviceName());
+        if (deviceInfo != null) {
+            device.setDeviceId(deviceInfo.getDeviceId());
+            deviceDao.updateDevice(device);
+            log.info("device register update:{}", JsonUtil.toJsonString(device));
+            return device;
+        }
+
+        device.setDeviceId(newDeviceId(device.getDeviceName()));
+        deviceDao.addDevice(device);
+        log.info("device registered:{}", JsonUtil.toJsonString(device));
+        return device;
+    }
+
+    public DeviceInfo getByPkAndDn(String pk, String dn) {
+        return deviceDao.getByPkAndDn(pk, dn);
+    }
+
+    public void online(String pk, String dn) {
+        DeviceInfo device = new DeviceInfo();
+        device.setProductKey(pk);
+        device.setDeviceName(dn);
+
+        device.getState().setOnline(true);
+        device.getState().setOnlineTime(System.currentTimeMillis());
+        deviceDao.updateDeviceByPkAndDn(device);
+    }
+
+    /**
+     * 1-13位	时间戳
+     * 14-29位	deviceNae,去除非字母和数字,不足16位补0,超过16位的mac取后16位,共16位
+     * 30-31位	mac长度,共2位
+     * 32位	随机一个0-f字符
+     */
+    public static String newDeviceId(String deviceNae) {
+        int maxDnLen = 16;
+        String dn = deviceNae.replaceAll("[^0-9A-Za-z]", "");
+        if (dn.length() > maxDnLen) {
+            dn = dn.substring(dn.length() - maxDnLen);
+        } else {
+            dn = (dn + "00000000000000000000").substring(0, maxDnLen);
+        }
+        String len = StringUtils.leftPad(deviceNae.length() + "", 2, '0');
+        String rnd = Integer.toHexString(RandomUtils.nextInt(0, 16));
+        return (System.currentTimeMillis() + "0" + dn + len + rnd).toLowerCase();
+    }
+
+}

+ 12 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/MqttAuth.java

@@ -0,0 +1,12 @@
+package cc.iotkit.mqttauth.service;
+
+import cc.iotkit.mqttauth.model.EmqAcl;
+import cc.iotkit.mqttauth.model.EmqAuthInfo;
+
+public interface MqttAuth {
+
+    void auth(EmqAuthInfo auth);
+
+    void acl(EmqAcl acl);
+
+}

+ 42 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/SysMqttAuth.java

@@ -0,0 +1,42 @@
+package cc.iotkit.mqttauth.service;
+
+import cc.iotkit.common.utils.CodecUtil;
+import cc.iotkit.mqttauth.config.Constants;
+import cc.iotkit.mqttauth.model.EmqAcl;
+import cc.iotkit.mqttauth.model.EmqAuthInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component("SysMqttAuth")
+public class SysMqttAuth implements MqttAuth {
+    @Override
+    public void auth(EmqAuthInfo auth) {
+        try {
+            //password= aes(sy_username,ACCOUNT_SECRET)
+            String uid = auth.getUsername();
+            String codes = CodecUtil.aesDecryptHex(auth.getPassword(), Constants.ACCOUNT_SECRET);
+            if (StringUtils.isBlank(codes)) {
+                throw new RuntimeException("mqtt auth failed,pwd error.");
+            }
+            //解出来的用户id与username是否一致
+            String[] parts = codes.split("_");
+            if (parts.length < 2 || !uid.equals(parts[1])) {
+                throw new RuntimeException("mqtt auth failed,pw validate error.");
+            }
+        } catch (Throwable e) {
+            log.error("sys user mqtt failed.", e);
+            throw new RuntimeException("mqtt auth failed:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public void acl(EmqAcl acl) {
+        //平台用户可订阅以所有设备
+//        String topic = acl.getTopic();
+//        if (!topic.startsWith("/app/")) {
+//            throw new RuntimeException("acl failed.");
+//        }
+    }
+}

+ 18 - 0
device-server/mqtt-auth/src/main/java/cc/iotkit/mqttauth/service/WxMqttAuth.java

@@ -0,0 +1,18 @@
+package cc.iotkit.mqttauth.service;
+
+import cc.iotkit.mqttauth.model.EmqAcl;
+import cc.iotkit.mqttauth.model.EmqAuthInfo;
+import org.springframework.stereotype.Component;
+
+@Component("WxMqttAuth")
+public class WxMqttAuth implements MqttAuth {
+    @Override
+    public void auth(EmqAuthInfo auth) {
+
+    }
+
+    @Override
+    public void acl(EmqAcl acl) {
+
+    }
+}

+ 5 - 0
device-server/mqtt-auth/src/main/resources/application-dev.yml

@@ -0,0 +1,5 @@
+spring:
+  data:
+    mongodb:
+      uri: mongodb://填写mongodb地址/admin
+      database: iotkit

+ 5 - 0
device-server/mqtt-auth/src/main/resources/application.yml

@@ -0,0 +1,5 @@
+spring:
+  data:
+    mongodb:
+      uri: mongodb://填写mongodb地址/admin
+      database: iotkit

+ 81 - 0
device-server/mqtt-auth/src/main/resources/logback-spring.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <jmxConfigurator/>
+    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+    <!-- Example for logging into the build folder of your project -->
+    <property name="LOG_FILE" value="log"/>
+
+    <!-- You can override this to have a custom pattern -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
+
+    <!-- Appender to log to console -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <!-- Minimum logging level to be presented in the console logs-->
+            <level>DEBUG</level>
+        </filter>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>utf8</charset>
+        </encoder>
+    </appender>
+
+    <!-- Appender to log to file -->
+    <appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_FILE}/info.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_FILE}/info.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
+            <!-- 如果按天来回滚,则最大保存时间为5天,5天之前的都将被清理掉 -->
+            <maxHistory>5</maxHistory>
+            <!-- 日志总保存量为20GB -->
+            <totalSizeCap>20GB</totalSizeCap>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!--文件达到 最大1GB时会被压缩和切割 -->
+                <maxFileSize>1GB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>utf8</charset>
+        </encoder>
+    </appender>
+
+    <!-- Appender to log to file only error level log -->
+    <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_FILE}/error.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_FILE}/error.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
+            <!-- 如果按天来回滚,则最大保存时间为5天,5天之前的都将被清理掉 -->
+            <maxHistory>5</maxHistory>
+            <!-- 日志总保存量为5GB -->
+            <totalSizeCap>5GB</totalSizeCap>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!--文件达到 最大1GB时会被压缩和切割 -->
+                <maxFileSize>1GB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>utf8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <logger name="cc.iotkit" level="debug" additivity="false">
+        <appender-ref ref="info"/>
+        <appender-ref ref="error"/>
+        <appender-ref ref="console"/>
+    </logger>
+
+    <root level="INFO">
+        <appender-ref ref="info"/>
+        <appender-ref ref="error"/>
+        <appender-ref ref="console"/>
+    </root>
+
+</configuration>

+ 31 - 0
device-server/mqtt-auth/src/test/java/SupperUser.java

@@ -0,0 +1,31 @@
+import cc.iotkit.common.utils.CodecUtil;
+import cc.iotkit.mqttauth.config.Constants;
+import cc.iotkit.mqttauth.controller.MqttAuthController;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SupperUser {
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("clientId:su_" + CodecUtil.aesEncrypt("admin_produce_dev", Constants.MQTT_SECRET));
+
+        String hmac = DigestUtils.md5Hex(Constants.MQTT_SECRET + "2P6MDKr8cB7y8EmM_ABC123DEF456");
+        System.out.println(hmac);
+
+    }
+
+    @Test
+    public void createSuperuser() throws Exception {
+        String clientId = "mqtt-server-producer-dev";
+        System.out.println("clientId:su_" + CodecUtil.aesEncrypt("admin_" + clientId, Constants.MQTT_SECRET));
+    }
+
+    @Test
+    public void isSupperUser() {
+        String clientId = "su_344A6E61654F567A30536E59646A306659664A75625A374D35484756776D457977374653684B306B414E513D";
+//        String clientId = "su_tng1t408QrZDEoM7CxiDueP++4FmXIxS7x35YbpuNf8=";
+        MqttAuthController authController = new MqttAuthController();
+        Assert.assertTrue(authController.isSupperUser(clientId));
+    }
+}

+ 13 - 0
device-server/mqtt-auth/src/test/java/SysMqttAuth.java

@@ -0,0 +1,13 @@
+import cc.iotkit.common.utils.CodecUtil;
+import cc.iotkit.mqttauth.config.Constants;
+import org.junit.Test;
+
+public class SysMqttAuth {
+
+    @Test
+    public void createSyPwd() throws Exception {
+        System.out.println(CodecUtil.aesEncrypt("sy_gateway_dev", Constants.ACCOUNT_SECRET));
+        System.out.println(CodecUtil.aesDecryptHex("4B6272544E59324C596562686173494A696E764E69673D3D", Constants.ACCOUNT_SECRET));
+    }
+
+}

二进制
device-server/mqtt-server/.DS_Store


二进制
device-server/mqtt-server/log/error.2022-01-10.0.gz


二进制
device-server/mqtt-server/log/error.2022-01-11.0.gz


二进制
device-server/mqtt-server/log/error.2022-01-12.0.gz


二进制
device-server/mqtt-server/log/error.2022-01-13.0.gz


二进制
device-server/mqtt-server/log/info.2022-01-10.0.gz


二进制
device-server/mqtt-server/log/info.2022-01-11.0.gz


二进制
device-server/mqtt-server/log/info.2022-01-12.0.gz


二进制
device-server/mqtt-server/log/info.2022-01-13.0.gz


+ 123 - 0
device-server/mqtt-server/pom.xml

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>device-server</artifactId>
+        <groupId>cc.iotkit</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mqtt-server</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-integration</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>model</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>device-api</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 14 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/Application.java

@@ -0,0 +1,14 @@
+package cc.iotkit.server;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@EnableDiscoveryClient
+@SpringBootApplication
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+}

+ 43 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/config/CacheConfig.java

@@ -0,0 +1,43 @@
+package cc.iotkit.server.config;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.google.common.collect.Lists;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.caffeine.CaffeineCache;
+import org.springframework.cache.support.SimpleCacheManager;
+import org.springframework.context.annotation.Bean;
+
+import java.util.concurrent.TimeUnit;
+
+//@Configuration
+//@EnableCaching
+public class CacheConfig {
+
+    /**
+     * 配置本地缓存
+     */
+    @Bean
+    public CacheManager cacheManager() {
+        SimpleCacheManager manager = new SimpleCacheManager();
+        manager.setCaches(Lists.newArrayList(new CaffeineCache(
+                        "device_cache",
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                ),
+                new CaffeineCache(
+                        "product_cache",
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                ),
+                new CaffeineCache(
+                        "app_design_cache",
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                )));
+        return manager;
+    }
+
+}

+ 25 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/config/Constants.java

@@ -0,0 +1,25 @@
+package cc.iotkit.server.config;
+
+public interface Constants {
+
+    String MQTT_SECRET = "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU";
+
+    String ACCOUNT_SECRET = "3n1z33kzvpgz1foijpkepyd3e8tw84us";
+
+    String PRODUCT_CACHE = "product_cache";
+
+    String DEVICE_CACHE = "device_cache";
+
+    String THING_MODEL_CACHE = "thing_model_cache";
+
+    /**
+     * topic前缀第三方接入网关
+     */
+    String TOPIC_PREFIX_GATEWAY = "gateway";
+
+    /**
+     * topic前缀APP
+     */
+    String TOPIC_PREFIX_APP = "app";
+
+}

+ 42 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/config/GlobalExceptionHandler.java

@@ -0,0 +1,42 @@
+package cc.iotkit.server.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletResponse;
+
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(Exception.class)
+    @ResponseBody
+    public RequestResult handleException(Exception e, HttpServletResponse response) {
+        log.error("handler exception", e);
+        if(e.getMessage().contains("Unauthorized")){
+            response.setStatus(403);
+            return new RequestResult("403", "没有权限");
+        }
+        response.setStatus(500);
+        return new RequestResult("500", e.getMessage());
+    }
+
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class RequestResult {
+
+        private String code;
+
+        private String message;
+
+    }
+
+}
+
+

+ 154 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/config/MqttConfig.java

@@ -0,0 +1,154 @@
+package cc.iotkit.server.config;
+
+import cc.iotkit.server.handler.MqttConsumerHandler;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.core.MessageProducer;
+import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+
+@Configuration
+public class MqttConfig {
+
+    /**
+     * 订阅的bean名称
+     */
+    public static final String CHANNEL_NAME_IN = "mqttInboundChannel";
+    /**
+     * 发布的bean名称
+     */
+    public static final String CHANNEL_NAME_OUT = "mqttOutboundChannel";
+
+    @Value("${mqtt.username}")
+    private String username;
+
+    @Value("${mqtt.password}")
+    private String password;
+
+    @Value("${mqtt.url}")
+    private String url;
+
+    @Value("${mqtt.producer.clientId}")
+    private String producerClientId;
+
+    @Value("${mqtt.producer.defaultTopic}")
+    private String producerDefaultTopic;
+
+    @Value("${mqtt.consumer.clientId}")
+    private String consumerClientId;
+
+    @Value("${mqtt.consumer.defaultTopic}")
+    private String consumerDefaultTopic;
+
+    /**
+     * MQTT连接器选项
+     *
+     * @return {@link MqttConnectOptions}
+     */
+    @Bean
+    public MqttConnectOptions getMqttConnectOptions() {
+        MqttConnectOptions options = new MqttConnectOptions();
+        // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
+        // 这里设置为true表示每次连接到服务器都以新的身份连接
+        options.setCleanSession(true);
+        // 设置连接的用户名
+        options.setUserName(username);
+        // 设置连接的密码
+        options.setPassword(password.toCharArray());
+        options.setServerURIs(StringUtils.split(url, ","));
+        // 设置超时时间 单位为秒
+        options.setConnectionTimeout(10);
+        // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送心跳判断客户端是否在线,但这个方法并没有重连的机制
+        options.setKeepAliveInterval(20);
+        return options;
+    }
+
+    /**
+     * MQTT客户端
+     *
+     * @return {@link MqttPahoClientFactory}
+     */
+    @Bean
+    public MqttPahoClientFactory mqttClientFactory() {
+        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
+        factory.setConnectionOptions(getMqttConnectOptions());
+        return factory;
+    }
+
+    /**
+     * MQTT信息通道(生产者)
+     *
+     * @return {@link MessageChannel}
+     */
+    @Bean(name = CHANNEL_NAME_OUT)
+    public MessageChannel mqttOutboundChannel() {
+        return new DirectChannel();
+    }
+
+    /**
+     * MQTT消息处理器(生产者)
+     *
+     * @return {@link MessageHandler}
+     */
+    @Bean
+    @ServiceActivator(inputChannel = CHANNEL_NAME_OUT)
+    public MessageHandler mqttOutbound() {
+        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(
+                producerClientId,
+                mqttClientFactory());
+        messageHandler.setAsync(true);
+        messageHandler.setDefaultTopic(producerDefaultTopic);
+        return messageHandler;
+    }
+
+    /**
+     * MQTT消息订阅绑定(消费者)
+     *
+     * @return {@link MessageProducer}
+     */
+    @Bean
+    public MessageProducer inbound() {
+        // 可以同时消费(订阅)多个Topic
+        MqttPahoMessageDrivenChannelAdapter adapter =
+                new MqttPahoMessageDrivenChannelAdapter(
+                        consumerClientId, mqttClientFactory(),
+                        StringUtils.split(consumerDefaultTopic, ","));
+        adapter.setCompletionTimeout(5000);
+        adapter.setConverter(new DefaultPahoMessageConverter());
+        adapter.setQos(1);
+        // 设置订阅通道
+        adapter.setOutputChannel(mqttInboundChannel());
+        return adapter;
+    }
+
+    /**
+     * MQTT信息通道(消费者)
+     *
+     * @return {@link MessageChannel}
+     */
+    @Bean(name = CHANNEL_NAME_IN)
+    public MessageChannel mqttInboundChannel() {
+        return new DirectChannel();
+    }
+
+    /**
+     * MQTT消息处理器(消费者)
+     *
+     * @return {@link MessageHandler}
+     */
+    @Bean
+    @ServiceActivator(inputChannel = CHANNEL_NAME_IN)
+    public MessageHandler handler() {
+        return new MqttConsumerHandler();
+    }
+}

+ 55 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/BaseDao.java

@@ -0,0 +1,55 @@
+package cc.iotkit.server.dao;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+
+import java.util.List;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+public class BaseDao<T> {
+
+    protected MongoTemplate mongoTemplate;
+
+    private Class<T> cls;
+
+    public BaseDao(MongoTemplate mongoTemplate, Class<T> cls) {
+        this.mongoTemplate = mongoTemplate;
+        this.cls = cls;
+    }
+
+    public List<T> find(Criteria condition) {
+        Query query = new Query();
+        query.addCriteria(condition);
+        return mongoTemplate.find(query, cls);
+    }
+
+    public List<T> find(Criteria condition, long skip, int count, Sort.Order order) {
+        Query query = new Query();
+        query.addCriteria(condition)
+                .with(Sort.by(order))
+                .skip(skip)
+                .limit(count);
+        return mongoTemplate.find(query, cls);
+    }
+
+    public long count(Criteria condition) {
+        Query query = new Query();
+        query.addCriteria(condition);
+        return mongoTemplate.count(query, cls);
+    }
+
+    public <T> T save(String id, T entity) {
+        if (id == null) {
+            return mongoTemplate.save(entity);
+        } else {
+            mongoTemplate.updateFirst(query(where("_id").is(id)),
+                    DaoTool.update(entity), entity.getClass());
+            return (T) mongoTemplate.findById(id, entity.getClass());
+        }
+    }
+
+}

+ 53 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DaoTool.java

@@ -0,0 +1,53 @@
+package cc.iotkit.server.dao;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.commons.beanutils.BeanMap;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DaoTool {
+
+    public static void update(Update update, List<Prop> props) {
+        for (Prop pro : props) {
+            update.set(pro.getName(), pro.getValue());
+        }
+    }
+
+    public static List<Prop> getProp(String key, Object value) {
+        List<Prop> props = new ArrayList<>();
+        if (value instanceof Map) {
+            Set<Map.Entry<String, Object>> entrySet = ((Map) value).entrySet();
+            for (Map.Entry<String, Object> entry : entrySet) {
+                props.addAll(getProp(key + "." + entry.getKey(), entry.getValue()));
+            }
+        } else if (value != null && !(value instanceof Class)) {
+            props.add(new Prop(key, value));
+        }
+        return props;
+    }
+
+    @SneakyThrows
+    public static <T> Update update(T obj) {
+        Map<Object, Object> pros = new BeanMap(obj);
+        Update update = new Update();
+        for (Map.Entry<Object, Object> entry : pros.entrySet()) {
+            update(update, DaoTool.getProp(entry.getKey().toString(), entry.getValue()));
+        }
+        return update;
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    static class Prop {
+        private String name;
+        private Object value;
+    }
+}

+ 62 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DeviceDao.java

@@ -0,0 +1,62 @@
+package cc.iotkit.server.dao;
+
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.server.config.Constants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Repository;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+@Repository
+public class DeviceDao extends BaseDao<DeviceInfo> {
+
+    @Autowired
+    private DeviceRepository deviceRepository;
+
+    @Autowired
+    public DeviceDao(MongoTemplate mongoTemplate) {
+        super(mongoTemplate, DeviceInfo.class);
+    }
+
+    public void addDevice(DeviceInfo device) {
+        device.setCreateAt(System.currentTimeMillis());
+        mongoTemplate.insert(device);
+    }
+
+    public void updateDevice(DeviceInfo device) {
+        if (device.getDeviceId() == null) {
+            return;
+        }
+        mongoTemplate.updateFirst(query(where("deviceId").is(device.getDeviceId())),
+                DaoTool.update(device), DeviceInfo.class);
+    }
+
+    public void updateDeviceByPkAndDn(DeviceInfo device) {
+        if (device.getProductKey() == null || device.getDeviceName() == null) {
+            return;
+        }
+        mongoTemplate.updateFirst(query(where("productKey").is(device.getProductKey()).
+                        and("deviceName").is(device.getDeviceName())),
+                DaoTool.update(device), DeviceInfo.class);
+    }
+
+    @Cacheable(value = "deviceInfoCache", key = "#pk+'_'+#dn")
+    public DeviceInfo getByPkAndDn(String pk, String dn) {
+        Query query = query(where("productKey").is(pk).and("deviceName").is(dn));
+        return mongoTemplate.findOne(query, DeviceInfo.class);
+    }
+
+    public DeviceInfo getByDeviceId(String deviceId) {
+        Query query = query(where("deviceId").is(deviceId));
+        return mongoTemplate.findOne(query, DeviceInfo.class);
+    }
+
+    @Cacheable(value = Constants.DEVICE_CACHE, key = "#deviceId")
+    public DeviceInfo get(String deviceId) {
+        return deviceRepository.findById(deviceId).orElse(new DeviceInfo());
+    }
+}

+ 13 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DeviceEventRepository.java

@@ -0,0 +1,13 @@
+package cc.iotkit.server.dao;
+
+import cc.iotkit.model.device.message.DeviceEvent;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface DeviceEventRepository extends MongoRepository<DeviceEvent, String> {
+
+    Page<DeviceEvent> findByDeviceId(String deviceId, Pageable pageable);
+}

+ 9 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/DeviceRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.server.dao;
+
+import cc.iotkit.model.device.DeviceInfo;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface DeviceRepository extends MongoRepository<DeviceInfo, String> {
+}

+ 9 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/dao/ThingModelRepository.java

@@ -0,0 +1,9 @@
+package cc.iotkit.server.dao;
+
+import cc.iotkit.model.product.ThingModel;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ThingModelRepository extends MongoRepository<ThingModel, String> {
+}

+ 63 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/DisconnectedHandler.java

@@ -0,0 +1,63 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.server.dao.DeviceRepository;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.server.service.DeviceService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Slf4j
+@Component
+public class DisconnectedHandler {
+
+    @Autowired
+    private DeviceService deviceService;
+    @Autowired
+    private DeviceRepository deviceRepository;
+
+    public void handler(String msg) {
+        Disconnected disconnected = JsonUtil.parse(msg, new TypeReference<Disconnected>() {
+        });
+        String clientId = disconnected.getClientid();
+        String[] parts = clientId.split("_");
+        if (parts.length < 2) {
+            return;
+        }
+        String pk = parts[0];
+        String dn = parts[1];
+        log.info("gateway disconnected, offline,pk:{},dn:{}", pk, dn);
+
+        DeviceInfo example = new DeviceInfo();
+        example.setProductKey(pk);
+        example.setDeviceName(dn);
+        DeviceInfo device = deviceRepository.findOne(Example.of(example)).orElse(new DeviceInfo());
+        if (device.getDeviceId() == null) {
+            log.error("no device found by pk:{} and dn:{}", pk, dn);
+            return;
+        }
+        deviceService.offline(pk, dn);
+
+        example = new DeviceInfo();
+        example.setParentId(device.getDeviceId());
+        //子设备下线
+        List<DeviceInfo> children = deviceRepository.findAll(Example.of(example));
+        children.forEach(c -> deviceService.offline(c.getProductKey(), c.getDeviceName()));
+    }
+
+    @Data
+    private static class Disconnected {
+        private String reason;
+        private String clientid;
+        private String username;
+        private String peername;
+        private String sockname;
+    }
+
+}

+ 49 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/EventReportHandler.java

@@ -0,0 +1,49 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.server.dao.DeviceEventRepository;
+import cc.iotkit.model.device.message.DeviceEvent;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.model.mq.Response;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+@Component
+public class EventReportHandler implements MqttHandler<Map<String, Object>, Response.Empty> {
+
+    private static final Pattern MATCH_REG = Pattern.compile("^/sys/\\w+/\\w+/s/event/[^_/]+$");
+
+    @Autowired
+    DeviceEventRepository deviceEventRepository;
+
+    @Override
+    public boolean compliant(String topic) {
+        return MATCH_REG.matcher(topic).matches();
+    }
+
+    @Override
+    public Request parse(String msg) {
+        return JsonUtil.parse(msg, new TypeReference<Request>() {
+        });
+    }
+
+    @Override
+    public Response.Empty handler(String topic, DeviceInfo device, Request request) {
+        String identifier = topic.substring(topic.indexOf("/event/") + 7);
+        DeviceEvent event = DeviceEvent.builder()
+                .deviceId(device.getDeviceId())
+                .identifier(identifier)
+                .request(request)
+                .type(topic.endsWith("_reply") ? "ack" : "event")
+                .createAt(System.currentTimeMillis())
+                .build();
+        deviceEventRepository.save(event);
+        return Response.empty();
+    }
+
+}

+ 162 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/MqttConsumerHandler.java

@@ -0,0 +1,162 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.server.config.Constants;
+import cc.iotkit.server.dao.DeviceDao;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.model.mq.Response;
+import cc.iotkit.server.service.DeviceService;
+import cc.iotkit.server.service.IMqttSender;
+import com.fasterxml.jackson.core.type.TypeReference;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.MessagingException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+public class MqttConsumerHandler implements MessageHandler, ApplicationContextAware {
+
+    private List<MqttHandler> mqttHandlers = new ArrayList<>();
+
+    @Autowired
+    private DeviceDao deviceDao;
+    @Autowired
+    private IMqttSender mqttSender;
+    @Autowired
+    private DeviceService deviceService;
+    @Autowired
+    private DisconnectedHandler disconnectedHandler;
+
+    @Override
+    public void handleMessage(Message<?> msg) throws MessagingException {
+        log.info(JsonUtil.toJsonString(msg));
+        MessageHeaders headers = msg.getHeaders();
+        String topic = headers.get("mqtt_receivedTopic", String.class);
+        if (topic == null) {
+            log.error("message topic is null.");
+            return;
+        }
+
+        if (topic.equals("/sys/session/topic/unsubscribed")) {
+            topicUnsubscribed(msg.getPayload().toString());
+            return;
+        }
+
+        if (topic.equals("/sys/client/disconnected")) {
+            disconnectedHandler.handler(msg.getPayload().toString());
+            return;
+        }
+
+        String[] parts = topic.split("/");
+        if (parts.length < 5) {
+            log.error("message topic is illegal.");
+            return;
+        }
+
+        String pk = parts[2];
+        String dn = parts[3];
+        DeviceInfo device = deviceDao.getByPkAndDn(pk, dn);
+        if (device == null) {
+            log.warn("device not found by pk and dn.");
+            return;
+        }
+        String payload = msg.getPayload().toString();
+
+        //转发到deviceId对应的topic中给客户端消费
+        sendToAppClientTopic(device.getDeviceId(), topic, payload);
+
+        Object result = null;
+        Request<?> request = new Request<>();
+        try {
+            for (MqttHandler handler : mqttHandlers) {
+                if (!handler.compliant(topic)) {
+                    continue;
+                }
+                request = handler.parse(payload);
+                result = handler.handler(topic, device, request);
+            }
+        } catch (Throwable e) {
+            log.error("handler mqtt msg error.", e);
+            reply(device.getDeviceId(), topic, request.getId(), 1, "");
+            return;
+        }
+
+        if (result == null) {
+            return;
+        }
+
+        reply(device.getDeviceId(), topic, request.getId(), 0, result);
+    }
+
+    private void reply(String deviceId, String topic, String id, int code, Object result) {
+        topic = topic.replace("/s/", "/c/") + "_reply";
+        String msg = JsonUtil.toJsonString(new Response<>(id, code, result));
+        mqttSender.sendToMqtt(topic, msg);
+        sendToAppClientTopic(deviceId, topic, msg);
+    }
+
+    private void topicUnsubscribed(String msg) {
+        Unsubscribed unsubscribed = JsonUtil.parse(msg, new TypeReference<Unsubscribed>() {
+        });
+        String topic = unsubscribed.getTopic();
+        String[] parts = topic.split("/");
+        if (parts.length < 4) {
+            return;
+        }
+
+        log.info("device offline,pk:{},dn:{}", parts[2], parts[3]);
+        deviceService.offline(parts[2], parts[3]);
+    }
+
+    private void sendToAppClientTopic(String deviceId, String topic, String msg) {
+        //排除服务调用和属性设置消息
+        if (topic.contains("/c/service/") || topic.endsWith("/post_reply")) {
+            return;
+        }
+
+        //发给app端订阅消息
+        distributeMsg(Constants.TOPIC_PREFIX_APP, topic, deviceId, msg);
+        //发送给第三方接入gateway
+        distributeMsg(Constants.TOPIC_PREFIX_GATEWAY, topic, deviceId, msg);
+    }
+
+    /**
+     * 分发消息
+     */
+    void distributeMsg(String topicNamePrefix, String topic, String deviceId, String msg) {
+        /*
+        /app/xxxdeviceId/event/事件名
+        /app/xxxdeviceId/event/property/post
+        /app/xxxdeviceId/service/服务名_reply
+         */
+        String distTopic = "/" + topicNamePrefix + "/" + deviceId + "/" +
+                (topic.replaceAll("/sys/.*/s/", "")
+                        .replaceAll("/sys/.*/c/", ""));
+        log.info("send msg:{},to topic:{}", JsonUtil.toJsonString(msg), distTopic);
+        //转发到deviceId对应的topic中给客户端消费
+        mqttSender.sendToMqtt(distTopic, msg);
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        mqttHandlers.addAll(context.getBeansOfType(MqttHandler.class).values());
+    }
+
+    @Data
+    private static class Unsubscribed {
+        private String clientid;
+        private String username;
+        private String topic;
+        private String peerhost;
+    }
+}

+ 14 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/MqttHandler.java

@@ -0,0 +1,14 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.mq.Request;
+
+public interface MqttHandler<T, R> {
+
+    boolean compliant(String topic);
+
+    Request<T> parse(String msg);
+
+    R handler(String topic, DeviceInfo device, Request<T> request);
+
+}

+ 62 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/PropertyPostHandler.java

@@ -0,0 +1,62 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.server.dao.DeviceEventRepository;
+import cc.iotkit.server.dao.DeviceRepository;
+import cc.iotkit.model.device.message.DeviceEvent;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.model.mq.Response;
+import com.fasterxml.jackson.core.type.TypeReference;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class PropertyPostHandler implements MqttHandler<Map<String, Object>, Response.Empty> {
+
+    @Autowired
+    DeviceRepository deviceRepository;
+    @Autowired
+    DeviceEventRepository deviceEventRepository;
+
+    @Override
+    public boolean compliant(String topic) {
+        return topic.endsWith("/event/property/post");
+    }
+
+    @Override
+    public Request<Map<String, Object>> parse(String msg) {
+        return JsonUtil.parse(msg, new TypeReference<Request<Map<String, Object>>>() {
+        });
+    }
+
+    @Override
+    public Response.Empty handler(String topic, DeviceInfo device, Request<Map<String, Object>> request) {
+        device.setId(device.getDeviceId());
+        if (device.getProperty() == null) {
+            device.setProperty(new HashMap<>());
+        }
+        Map<String, Object> newProps = request.getParams();
+        if (newProps != null && newProps.size() > 0) {
+            request.getParams().forEach(device.getProperty()::put);
+        }
+
+        deviceRepository.save(device);
+
+        DeviceEvent event = DeviceEvent.builder()
+                .deviceId(device.getDeviceId())
+                .identifier("propertyPost")
+                .request(request)
+                .type("property")
+                .createAt(System.currentTimeMillis())
+                .build();
+        deviceEventRepository.save(event);
+        return Response.empty();
+    }
+
+}

+ 47 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/PropertyReplyHandler.java

@@ -0,0 +1,47 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.server.dao.DeviceEventRepository;
+import cc.iotkit.model.device.message.DeviceEvent;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.model.mq.Response;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Component
+public class PropertyReplyHandler implements MqttHandler<Map<String, Object>, Response.Empty> {
+
+    @Autowired
+    DeviceEventRepository deviceEventRepository;
+
+    @Override
+    public boolean compliant(String topic) {
+        return topic.endsWith("/service/property/set_reply");
+    }
+
+    @Override
+    public Request<Map<String, Object>> parse(String msg) {
+        return JsonUtil.parse(msg, new TypeReference<Request<Map<String, Object>>>() {
+        });
+    }
+
+    @Override
+    public Response.Empty handler(String topic, DeviceInfo device, Request<Map<String, Object>> request) {
+        String identifier = "propertySetReply";
+        DeviceEvent event =
+                DeviceEvent.builder()
+                .deviceId(device.getDeviceId())
+                .identifier(identifier)
+                .request(request)
+                .type("ack")
+                .createAt(System.currentTimeMillis())
+                .build();
+        deviceEventRepository.save(event);
+        return null;
+    }
+
+}

+ 37 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/RegisterHandler.java

@@ -0,0 +1,37 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.device.message.DeviceRegister;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.model.mq.Response;
+import cc.iotkit.server.service.DeviceService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class RegisterHandler implements MqttHandler<DeviceRegister, Response.Empty> {
+
+    @Autowired
+    private DeviceService deviceService;
+
+    @Override
+    public boolean compliant(String topic) {
+        return topic.endsWith("/register");
+    }
+
+    @Override
+    public Request<DeviceRegister> parse(String msg) {
+        return JsonUtil.parse(msg, new TypeReference<Request<DeviceRegister>>() {
+        });
+    }
+
+    @Override
+    public Response.Empty handler(String topic, DeviceInfo device, Request<DeviceRegister> request) {
+        DeviceRegister regInfo = request.getParams();
+        deviceService.register(device.getDeviceId(), regInfo.getProductKey(),
+                regInfo.getDeviceName(), regInfo.getModel());
+        return Response.empty();
+    }
+}

+ 49 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/handler/ServiceReplyHandler.java

@@ -0,0 +1,49 @@
+package cc.iotkit.server.handler;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.server.dao.DeviceEventRepository;
+import cc.iotkit.model.device.message.DeviceEvent;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.model.mq.Response;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+@Component
+public class ServiceReplyHandler implements MqttHandler<Map<String, Object>, Response.Empty> {
+
+    private static final Pattern MATCH_REG = Pattern.compile("^/sys/\\w+/\\w+/s/service/[^_/]+_reply$");
+
+    @Autowired
+    DeviceEventRepository deviceEventRepository;
+
+    @Override
+    public boolean compliant(String topic) {
+        return MATCH_REG.matcher(topic).matches();
+    }
+
+    @Override
+    public Request<Map<String, Object>> parse(String msg) {
+        return JsonUtil.parse(msg, new TypeReference<Request<Map<String, Object>>>() {
+        });
+    }
+
+    @Override
+    public Response.Empty handler(String topic, DeviceInfo device, Request<Map<String, Object>> request) {
+        String identifier = topic.substring(topic.indexOf("/service/") + 9);
+        DeviceEvent event = DeviceEvent.builder()
+                .deviceId(device.getDeviceId())
+                .identifier(identifier)
+                .request(request)
+                .type("ack")
+                .createAt(System.currentTimeMillis())
+                .build();
+        deviceEventRepository.save(event);
+        return null;
+    }
+
+}

+ 210 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/service/DeviceService.java

@@ -0,0 +1,210 @@
+package cc.iotkit.server.service;
+
+import cc.iotkit.common.exception.NotFoundException;
+import cc.iotkit.common.exception.OfflineException;
+import cc.iotkit.common.utils.DeviceUtil;
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.common.utils.UniqueIdUtil;
+import cc.iotkit.deviceapi.IDeviceManager;
+import cc.iotkit.deviceapi.IDeviceService;
+import cc.iotkit.deviceapi.Service;
+import cc.iotkit.model.device.message.DeviceEvent;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.product.ThingModel;
+import cc.iotkit.model.mq.Request;
+import cc.iotkit.server.dao.DeviceDao;
+import cc.iotkit.server.dao.DeviceEventRepository;
+import cc.iotkit.server.dao.DeviceRepository;
+import cc.iotkit.server.dao.ThingModelRepository;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@RestController
+public class DeviceService implements IDeviceManager, IDeviceService {
+
+    @Autowired
+    private DeviceDao deviceDao;
+    @Autowired
+    private DeviceRepository deviceRepository;
+    @Autowired
+    private ThingModelRepository thingModelRepository;
+    @Autowired
+    private ThingModelService thingModelService;
+    @Autowired
+    private DeviceEventRepository deviceEventRepository;
+
+    @Autowired
+    private IMqttSender mqttSender;
+
+    private static final String identifier_set = "property/set";
+
+    @Override
+    public DeviceInfo register(String parentId, String productKey, String deviceName, String model) {
+        DeviceInfo device = new DeviceInfo();
+        device.setParentId(parentId);
+        device.setProductKey(productKey);
+        device.setDeviceName(deviceName);
+        device.setModel(model);
+
+        DeviceInfo deviceInfo = deviceDao.getByPkAndDn(productKey, deviceName);
+        if (deviceInfo != null) {
+            device.setId(deviceInfo.getId());
+            device.setDeviceId(deviceInfo.getDeviceId());
+            deviceDao.updateDevice(device);
+            log.info("device register update:{}", JsonUtil.toJsonString(device));
+            return deviceInfo;
+        }
+
+        String deviceId = newDeviceId(deviceName);
+
+        device.setId(deviceId);
+        device.setDeviceId(deviceId);
+        deviceDao.addDevice(device);
+        log.info("device registered:{}", JsonUtil.toJsonString(device));
+        return device;
+    }
+
+    @Override
+    public void unbind(String deviceId) {
+        log.info("start unbind device,deviceId:{}", deviceId);
+
+        DeviceInfo device = deviceRepository.findById(deviceId)
+                .orElseThrow(() -> new RuntimeException("no device found by deviceId:" + deviceId));
+
+        String gatewayId = device.getParentId();
+        DeviceInfo gateway = deviceRepository.findById(gatewayId)
+                .orElseThrow(() -> new RuntimeException("no device found by deviceId:" + deviceId));
+
+        //数据库解绑
+        device.setParentId("");
+        deviceRepository.save(device);
+
+        //网关注销
+        String topic = "/sys/" + gateway.getProductKey() + "/" + gateway.getDeviceName() + "/c/service/deregister";
+        String requestId = UniqueIdUtil.newRequestId();
+        Map<String, Object> params = new HashMap<>();
+        params.put("productKey", device.getProductKey());
+        params.put("deviceName", device.getDeviceName());
+        CmdRequest request = new CmdRequest(requestId, params);
+        String msg = JsonUtil.toJsonString(request);
+        log.info("start send mqtt msg,topic:{},payload:{}", topic, msg);
+        mqttSender.sendToMqtt(topic, msg);
+    }
+
+    @Override
+    public String invoke(Service service) {
+        return sendMsg(service);
+    }
+
+    @Override
+    public String setProperty(String deviceId, @RequestBody Map<String, Object> properties) {
+        return sendMsg(deviceId, identifier_set, properties);
+    }
+
+    @Override
+    public String invokeService(String deviceId, String identifier, Map<String, Object> properties) {
+        return sendMsg(deviceId, identifier, properties);
+    }
+
+    public void offline(String pk, String dn) {
+        DeviceInfo device = new DeviceInfo();
+        device.setProductKey(pk);
+        device.setDeviceName(dn);
+
+        device.getState().setOnline(false);
+        device.getState().setOfflineTime(System.currentTimeMillis());
+        deviceDao.updateDeviceByPkAndDn(device);
+        log.info("device offline,pk:{},dn:{}", pk, dn);
+    }
+
+    public String sendMsg(String deviceId, String service, Map<String, Object> args) {
+        DeviceInfo device = deviceRepository.findById(deviceId)
+                .orElseThrow(() -> new NotFoundException("device not found by deviceId"));
+
+        return this.sendMsg(device, service, args);
+    }
+
+    public String sendMsg(DeviceInfo device, String service, Map<String, Object> args) {
+        if (device.getState() == null || device.getState().getOnline() != Boolean.TRUE) {
+            throw new OfflineException("device is offline");
+        }
+
+        String pk = device.getProductKey();
+        String dn = device.getDeviceName();
+
+        ThingModel thingModel = thingModelRepository.findById(pk)
+                .orElseThrow(() -> new NotFoundException("device thingModel not found"));
+
+        String topic = "/sys/" + pk + "/" + dn + "/c/service/" + service;
+        String requestId = UniqueIdUtil.newRequestId();
+
+        //参数类型转换
+        args = thingModelService.paramsParse(thingModel, service, args);
+
+        CmdRequest request = new CmdRequest(requestId, args);
+        String msg = JsonUtil.toJsonString(request);
+        log.info("start send mqtt msg,topic:{},payload:{}", topic, msg);
+        mqttSender.sendToMqtt(topic, msg);
+
+        //记录下行日志
+        DeviceEvent deviceEvent = DeviceEvent.builder()
+                .deviceId(device.getDeviceId())
+                .identifier(service.replace("property/set", "propertySet"))
+                .type("service")
+                .request(new Request<>(requestId, args))
+                .createAt(System.currentTimeMillis())
+                .build();
+        deviceEventRepository.save(deviceEvent);
+
+        return requestId;
+    }
+
+    public String sendMsg(Service service) {
+        DeviceUtil.PkDn pkDn = DeviceUtil.getPkDn(service.getDevice());
+        DeviceInfo deviceInfo = deviceDao.getByPkAndDn(pkDn.getProductKey(), pkDn.getDeviceName());
+        if ("set".equals(service.getIdentifier())) {
+            return sendMsg(deviceInfo.getDeviceId(), identifier_set, service.parseInputData());
+        } else {
+            return sendMsg(deviceInfo.getDeviceId(), service.getIdentifier(), service.parseInputData());
+        }
+    }
+
+    /**
+     * 1-13位	时间戳
+     * 14-29位	deviceNae,去除非字母和数字,不足16位补0,超过16位的mac取后16位,共16位
+     * 30-31位	mac长度,共2位
+     * 32位	随机一个0-f字符
+     */
+    private static String newDeviceId(String deviceNae) {
+        int maxDnLen = 16;
+        String dn = deviceNae.replaceAll("[^0-9A-Za-z]", "");
+        if (dn.length() > maxDnLen) {
+            dn = dn.substring(dn.length() - maxDnLen);
+        } else {
+            dn = (dn + "00000000000000000000").substring(0, maxDnLen);
+        }
+        String len = StringUtils.leftPad(deviceNae.length() + "", 2, '0');
+        String rnd = Integer.toHexString(RandomUtils.nextInt(0, 16));
+        return (System.currentTimeMillis() + "0" + dn + len + rnd).toLowerCase();
+    }
+
+
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    private static class CmdRequest {
+        private String id;
+        private Object params;
+    }
+}

+ 40 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/service/IMqttSender.java

@@ -0,0 +1,40 @@
+package cc.iotkit.server.service;
+import cc.iotkit.server.config.MqttConfig;
+import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+@MessagingGateway(defaultRequestChannel = MqttConfig.CHANNEL_NAME_OUT)
+public interface IMqttSender {
+
+    /**
+     * 发送信息到MQTT服务器
+     *
+     * @param data 发送的文本
+     */
+    void sendToMqtt(String data);
+
+    /**
+     * 发送信息到MQTT服务器
+     *
+     * @param topic 主题
+     * @param payload 消息主体
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
+                    String payload);
+
+    /**
+     * 发送信息到MQTT服务器
+     *
+     * @param topic 主题
+     * @param qos 对消息处理的几种机制。<br> 0 表示的是订阅者没收到消息不会再次发送,消息会丢失。<br>
+     * 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。<br>
+     * 2 多了一次去重的动作,确保订阅者收到的消息有一次。
+     * @param payload 消息主体
+     */
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
+                    @Header(MqttHeaders.QOS) int qos,
+                    String payload);
+}

+ 60 - 0
device-server/mqtt-server/src/main/java/cc/iotkit/server/service/ThingModelService.java

@@ -0,0 +1,60 @@
+package cc.iotkit.server.service;
+
+import cc.iotkit.model.product.ThingModel;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ThingModelService {
+
+    public Map<String, Object> paramsParse(ThingModel thingModel, String identifier, Map<?, ?> params) {
+        Map<String, Object> parsedParams = new HashMap<>();
+        ThingModel.Model model = thingModel.getModel();
+
+        //属性设置
+        if ("property/set".equals(identifier)) {
+            List<ThingModel.Property> properties = model.getProperties();
+            if (properties == null) {
+                return parsedParams;
+            }
+            return parseProperties(properties, params);
+        } else {
+            //服务调用
+            Map<String, ThingModel.Service> services = model.serviceMap();
+            ThingModel.Service service = services.get(identifier);
+            if (service == null) {
+                return parsedParams;
+            }
+            List<ThingModel.Parameter> parameters = service.getInputData();
+            return parseParams(parameters, params);
+        }
+    }
+
+    private Map<String, Object> parseParams(List<ThingModel.Parameter> parameters, Map<?, ?> params) {
+        Map<String, Object> parsed = new HashMap<>();
+        parameters.forEach((p -> parseField(p.getIdentifier(), p.getDataType(), params, parsed)));
+        return parsed;
+    }
+
+    private Map<String, Object> parseProperties(List<ThingModel.Property> properties, Map<?, ?> params) {
+        Map<String, Object> parsed = new HashMap<>();
+        properties.forEach((p -> parseField(p.getIdentifier(), p.getDataType(), params, parsed)));
+        return parsed;
+    }
+
+    private void parseField(String identifier, ThingModel.DataType dataType, Map<?, ?> params, Map<String, Object> parsed) {
+        Object val = params.get(identifier);
+        if (val == null) {
+            return;
+        }
+        Object result = dataType.parse(val);
+        if (result == null) {
+            return;
+        }
+        parsed.put(identifier, result);
+    }
+
+}

+ 26 - 0
device-server/mqtt-server/src/main/resources/application-dev.yml

@@ -0,0 +1,26 @@
+spring:
+  data:
+    mongodb:
+      uri: mongodb://填写mongodb地址/admin
+      database: iotkit
+
+  cache:
+    cache-names: foo,bar
+    caffeine:
+      spec: maximumSize=5000,expireAfterAccess=120s
+
+mqtt:
+  username: admin
+  password: password
+  url: tcp://填写mqtt连接地址
+
+  producer:
+    #su_mqtt-server-producer-prod
+    clientId: 填写mqtt连接clientId见文档中生成clientId说明
+    defaultTopic: topic1
+
+  consumer:
+    #su_mqtt-server-consumer-prod
+    clientId: 填写mqtt连接clientId见文档中生成clientId说明
+    defaultTopic: /sys/#
+

部分文件因为文件数量过多而无法显示