request = new Request<>();
+ request.setData(data);
+ request.setRequestId(IdUtil.simpleUUID());
+ return request;
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/RequestEmpty.java b/iot-common-core/src/main/java/cc/iotkit/common/api/RequestEmpty.java
new file mode 100755
index 0000000..bd2891d
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/api/RequestEmpty.java
@@ -0,0 +1,27 @@
+package cc.iotkit.common.api;
+
+import cn.hutool.core.util.IdUtil;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author: Longjun.Tu
+ * @description:
+ * @date:created in 2023/5/10 23:12
+ * @modificed by:
+ */
+@Data
+public class RequestEmpty implements Serializable {
+
+ @NotBlank
+ private String requestId;
+
+ public static RequestEmpty of() {
+ RequestEmpty request = new RequestEmpty();
+ request.setRequestId(IdUtil.simpleUUID());
+ return request;
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/Response.java b/iot-common-core/src/main/java/cc/iotkit/common/api/Response.java
new file mode 100755
index 0000000..b8b1058
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/api/Response.java
@@ -0,0 +1,15 @@
+package cc.iotkit.common.api;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Response {
+ private Integer code;
+ private String message;
+ private Object data;
+ private String requestId;
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/CacheConstants.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/CacheConstants.java
new file mode 100755
index 0000000..c6d8fea
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/CacheConstants.java
@@ -0,0 +1,25 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 缓存的key 常量
+ *
+ * @author Lion Li
+ */
+public interface CacheConstants {
+
+ /**
+ * 在线用户 redis key
+ */
+ String ONLINE_TOKEN_KEY = "online_tokens:";
+
+ /**
+ * 参数管理 cache key
+ */
+ String SYS_CONFIG_KEY = "sys_config:";
+
+ /**
+ * 字典管理 cache key
+ */
+ String SYS_DICT_KEY = "sys_dict:";
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/CacheNames.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/CacheNames.java
new file mode 100755
index 0000000..de593f7
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/CacheNames.java
@@ -0,0 +1,63 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 缓存组名称常量
+ *
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
+ *
+ * ttl 过期时间 如果设置为0则不过期 默认为0
+ * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
+ * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
+ *
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
+ *
+ * @author Lion Li
+ */
+public interface CacheNames {
+
+ /**
+ * 演示案例
+ */
+ String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+ /**
+ * 系统配置
+ */
+ String SYS_CONFIG = "sys_config";
+
+ /**
+ * 数据字典
+ */
+ String SYS_DICT = "sys_dict";
+
+ /**
+ * 租户
+ */
+ String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
+ /**
+ * 用户账户
+ */
+ String SYS_USER_NAME = "sys_user_name#30d";
+
+ /**
+ * 部门
+ */
+ String SYS_DEPT = "sys_dept#30d";
+
+ /**
+ * OSS内容
+ */
+ String SYS_OSS = "sys_oss#30d";
+
+ /**
+ * OSS配置
+ */
+ String SYS_OSS_CONFIG = "sys_oss_config";
+
+ /**
+ * 在线用户
+ */
+ String ONLINE_TOKEN = "online_tokens";
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/Constants.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/Constants.java
new file mode 100755
index 0000000..15a3a1a
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/Constants.java
@@ -0,0 +1,282 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public interface Constants {
+
+ /**
+ * UTF-8 字符集
+ */
+ String UTF8 = "UTF-8";
+
+ /**
+ * GBK 字符集
+ */
+ String GBK = "GBK";
+
+ /**
+ * www主域
+ */
+ String WWW = "www.";
+
+ /**
+ * http请求
+ */
+ String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ String HTTPS = "https://";
+
+ /**
+ * 通用成功标识
+ */
+ String SUCCESS = "0";
+
+ /**
+ * 通用失败标识
+ */
+ String FAIL = "1";
+
+ /**
+ * 登录成功
+ */
+ String LOGIN_SUCCESS = "Success";
+
+ /**
+ * 注销
+ */
+ String LOGOUT = "Logout";
+
+ /**
+ * 注册
+ */
+ String REGISTER = "Register";
+
+ /**
+ * 登录失败
+ */
+ String LOGIN_FAIL = "Error";
+
+ /**
+ * 验证码有效期(分钟)
+ */
+ Integer CAPTCHA_EXPIRATION = 2;
+
+ String ACCOUNT_SECRET = "3n1z33kzvpgz1foijpkepyd3e8tw84us";
+
+ String CACHE_PRODUCT = "product_cache";
+
+ String CACHE_DEVICE_INFO = "device_info_cache";
+
+ String CACHE_DEVICE_STATS = "device_stats_cache";
+
+ String CACHE_CATEGORY = "category_cache";
+
+ String CACHE_SPACE = "space_cache";
+
+ String CACHE_THING_MODEL = "thing_model_cache";
+
+ String CACHE_USER_INFO = "user_info_cache";
+
+ String CACHE_OAUTH_CLIENT = "oauth_client_cache";
+
+ String CACHE_PRODUCT_SCRIPT = "product_script_cache";
+
+ /**
+ * 管理员角色
+ */
+ String ROLE_ADMIN = "iot_admin";
+
+ /**
+ * 可写角色
+ */
+ String ROLE_WRITE = "iot_write";
+
+ /**
+ * 管理系统用户角色
+ */
+ String ROLE_SYSTEM = "iot_system";
+
+ /**
+ * C端用户角色
+ */
+ String ROLE_CLIENT = "iot_client";
+
+ /**
+ * C端用户默认密码
+ */
+ String PWD_CLIENT_USER = "c123456";
+
+ /**
+ * 系统用户默认密码
+ */
+ String PWD_SYSTEM_USER = "s123456";
+
+ /**
+ * 设备物模型消息的topic
+ */
+ String THING_MODEL_MESSAGE_TOPIC = "device_thing";
+
+ /**
+ * 设备属性上报消息的topic
+ */
+ String DEVICE_PROPERTY_REPORT_TOPIC = "device_property_report";
+
+ /**
+ * 设备配置消息topic
+ */
+ String DEVICE_CONFIG_TOPIC = "device_config";
+
+ /**
+ * http消费设备信息的topic
+ */
+ String HTTP_CONSUMER_DEVICE_INFO_TOPIC = "device_info:";
+
+ /**
+ * 写权限
+ */
+ String PERMISSION_WRITE = "write";
+
+ /**
+ * 设备属性缓存key
+ */
+ String PROPERTY_CACHE_KEY = "str:iotkit:device:property:%s";
+
+ /**
+ * 三方平台类型
+ */
+ enum ThirdPlatform {
+ dueros("小度"),
+ aligenie("天猫精灵"),
+ miiot("小爱");
+
+ public String desc;
+
+ ThirdPlatform(String desc) {
+ this.desc = desc;
+ }
+ }
+
+ /**
+ * 三方平台openUid名称
+ */
+ enum ThirdOpenUid {
+ duerosOpenUid("小度OpenUid"),
+ aligenieOpenUid("天猫精灵OpenUid"),
+ miiotOpenUid("小爱OpenUid");
+
+ public String desc;
+
+ ThirdOpenUid(String desc) {
+ this.desc = desc;
+ }
+ }
+
+ interface API_DEVICE {
+
+ /**
+ * 设备-基路径
+ */
+ String BASE = "/device";
+
+ /**
+ * 设备-设备列表
+ */
+ String LIST = "/list/{size}/{page}";
+
+ /**
+ * 设备-设备详情
+ */
+ String DETAIL = "/{deviceId}/detail";
+
+ /**
+ * 设备-属性设置
+ */
+ String SET_PROPERTIES = "/{deviceId}/service/property/set";
+
+ /**
+ * 设备-服务调用
+ */
+ String INVOKE_SERVICE = "/{deviceId}/service/{service}/invoke";
+ /**
+ * 设备-属性获取
+ */
+ String INVOKE_SERVICE_PROPERTY_GET = "/service/property/get";
+
+ /**
+ * OTA升级
+ */
+ String OTA_UPGRADE_PACKAGE = "{deviceId}/ota/upgrade/package/";
+
+ /**
+ * OTA升级进度上报
+ */
+ String OTA_UPGRADE_INFORM = "{deviceId}/ota/upgrade/inform/";
+
+ }
+
+ interface API_SPACE {
+
+ /**
+ * 空间-基路径
+ */
+ String BASE = "/space";
+
+ /**
+ * 最近使用设备列表
+ */
+ String RECENT_DEVICES = "/myRecentDevices";
+
+ /**
+ * 获取用户当前收藏设备
+ */
+ String GET_COLLECT_DEVICES = "/getCollectDevices";
+
+ /**
+ * 我的空间设备列表
+ */
+ String SPACE_DEVICES = "/myDevices/{spaceId}";
+
+ /**
+ * 查找设备
+ */
+ String FIND_DEVICE = "/findDevice";
+
+ /**
+ * 空间添加设备
+ */
+ String ADD_DEVICE = "/addDevice";
+
+ /**
+ * 空间删除设备
+ */
+ String REMOVE_DEVICE = "/removeDevice";
+
+ /**
+ * 空间修改设备
+ */
+ String SAVE_DEVICE = "/saveDevice";
+
+ /**
+ * 收藏/取消收藏设备
+ */
+ String COLLECT_DEVICE = "/collectDevice";
+
+ /**
+ * 获取空间设备信息
+ */
+ String GET_DEVICE = "/device/{deviceId}";
+
+ /**
+ * 设置第三方平台openUid
+ */
+ String SET_OPEN_UID = "/setOpenUid";
+ }
+
+}
+
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/GlobalConstants.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/GlobalConstants.java
new file mode 100755
index 0000000..d48c886
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/GlobalConstants.java
@@ -0,0 +1,34 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 全局的key常量 (业务无关的key)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+ /**
+ * 全局 redis key (业务无关的key)
+ */
+ String GLOBAL_REDIS_KEY = "global:";
+
+ /**
+ * 验证码 redis key
+ */
+ String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+ /**
+ * 防重提交 redis key
+ */
+ String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+ /**
+ * 限流 redis key
+ */
+ String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+ /**
+ * 登录账户密码错误次数 redis key
+ */
+ String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/HttpStatus.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/HttpStatus.java
new file mode 100755
index 0000000..24cbcab
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/HttpStatus.java
@@ -0,0 +1,93 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 返回状态码
+ *
+ * @author Lion Li
+ */
+public interface HttpStatus {
+ /**
+ * 操作成功
+ */
+ int SUCCESS = 200;
+
+ /**
+ * 对象创建成功
+ */
+ int CREATED = 201;
+
+ /**
+ * 请求已经被接受
+ */
+ int ACCEPTED = 202;
+
+ /**
+ * 操作已经执行成功,但是没有返回数据
+ */
+ int NO_CONTENT = 204;
+
+ /**
+ * 资源已被移除
+ */
+ int MOVED_PERM = 301;
+
+ /**
+ * 重定向
+ */
+ int SEE_OTHER = 303;
+
+ /**
+ * 资源没有被修改
+ */
+ int NOT_MODIFIED = 304;
+
+ /**
+ * 参数列表错误(缺少,格式不匹配)
+ */
+ int BAD_REQUEST = 400;
+
+ /**
+ * 未授权
+ */
+ int UNAUTHORIZED = 401;
+
+ /**
+ * 访问受限,授权过期
+ */
+ int FORBIDDEN = 403;
+
+ /**
+ * 资源,服务未找到
+ */
+ int NOT_FOUND = 404;
+
+ /**
+ * 不允许的http方法
+ */
+ int BAD_METHOD = 405;
+
+ /**
+ * 资源冲突,或者资源被锁
+ */
+ int CONFLICT = 409;
+
+ /**
+ * 不支持的数据,媒体类型
+ */
+ int UNSUPPORTED_TYPE = 415;
+
+ /**
+ * 系统内部错误
+ */
+ int ERROR = 500;
+
+ /**
+ * 接口未实现
+ */
+ int NOT_IMPLEMENTED = 501;
+
+ /**
+ * 系统警告消息
+ */
+ int WARN = 601;
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/TenantConstants.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/TenantConstants.java
new file mode 100755
index 0000000..f22fbf8
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/TenantConstants.java
@@ -0,0 +1,45 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 租户常量信息
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+ /**
+ * 租户正常状态
+ */
+ String NORMAL = "0";
+
+ /**
+ * 租户封禁状态
+ */
+ String DISABLE = "1";
+
+ /**
+ * 超级管理员ID
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+ /**
+ * 超级管理员角色 roleKey
+ */
+ String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+ /**
+ * 租户管理员角色 roleKey
+ */
+ String TENANT_ADMIN_ROLE_KEY = "admin";
+
+ /**
+ * 租户管理员角色名称
+ */
+ String TENANT_ADMIN_ROLE_NAME = "管理员";
+
+ /**
+ * 默认租户ID
+ */
+ String DEFAULT_TENANT_ID = "000000";
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/constant/UserConstants.java b/iot-common-core/src/main/java/cc/iotkit/common/constant/UserConstants.java
new file mode 100755
index 0000000..699c09b
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/constant/UserConstants.java
@@ -0,0 +1,132 @@
+package cc.iotkit.common.constant;
+
+/**
+ * 用户常量信息
+ *
+ * @author ruoyi
+ */
+public interface UserConstants {
+
+ /**
+ * 平台内系统用户的唯一标志
+ */
+ String SYS_USER = "SYS_USER";
+
+ /**
+ * 正常状态
+ */
+ String NORMAL = "0";
+
+ /**
+ * 异常状态
+ */
+ String EXCEPTION = "1";
+
+ /**
+ * 用户正常状态
+ */
+ String USER_NORMAL = "0";
+
+ /**
+ * 用户封禁状态
+ */
+ String USER_DISABLE = "1";
+
+ /**
+ * 角色正常状态
+ */
+ String ROLE_NORMAL = "0";
+
+ /**
+ * 角色封禁状态
+ */
+ String ROLE_DISABLE = "1";
+
+ /**
+ * 部门正常状态
+ */
+ String DEPT_NORMAL = "0";
+
+ /**
+ * 部门停用状态
+ */
+ String DEPT_DISABLE = "1";
+
+ /**
+ * 字典正常状态
+ */
+ String DICT_NORMAL = "0";
+
+ /**
+ * 是否为系统默认(是)
+ */
+ String YES = "Y";
+
+ /**
+ * 是否菜单外链(是)
+ */
+ String YES_FRAME = "0";
+
+ /**
+ * 是否菜单外链(否)
+ */
+ String NO_FRAME = "1";
+
+ /**
+ * 菜单正常状态
+ */
+ String MENU_NORMAL = "0";
+
+ /**
+ * 菜单停用状态
+ */
+ String MENU_DISABLE = "1";
+
+ /**
+ * 菜单类型(目录)
+ */
+ String TYPE_DIR = "M";
+
+ /**
+ * 菜单类型(菜单)
+ */
+ String TYPE_MENU = "C";
+
+ /**
+ * 菜单类型(按钮)
+ */
+ String TYPE_BUTTON = "F";
+
+ /**
+ * Layout组件标识
+ */
+ String LAYOUT = "Layout";
+
+ /**
+ * ParentView组件标识
+ */
+ String PARENT_VIEW = "ParentView";
+
+ /**
+ * InnerLink组件标识
+ */
+ String INNER_LINK = "InnerLink";
+
+ /**
+ * 用户名长度限制
+ */
+ int USERNAME_MIN_LENGTH = 2;
+ int USERNAME_MAX_LENGTH = 20;
+
+ /**
+ * 密码长度限制
+ */
+ int PASSWORD_MIN_LENGTH = 5;
+ int PASSWORD_MAX_LENGTH = 20;
+
+ /**
+ * 超级管理员ID
+ */
+ Long SUPER_ADMIN_ID = 1L;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/enums/DeviceType.java b/iot-common-core/src/main/java/cc/iotkit/common/enums/DeviceType.java
new file mode 100755
index 0000000..829175b
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/enums/DeviceType.java
@@ -0,0 +1,32 @@
+package cc.iotkit.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设备类型
+ * 针对一套 用户体系
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+ /**
+ * pc端
+ */
+ PC("pc"),
+
+ /**
+ * app端
+ */
+ APP("app"),
+
+ /**
+ * 小程序端
+ */
+ XCX("xcx");
+
+ private final String device;
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/enums/ErrCode.java b/iot-common-core/src/main/java/cc/iotkit/common/enums/ErrCode.java
new file mode 100755
index 0000000..2934d79
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/enums/ErrCode.java
@@ -0,0 +1,133 @@
+package cc.iotkit.common.enums;
+
+/**
+ * @author :tfd
+ * 异常枚举类
+ */
+public enum ErrCode implements IEnum {
+ /**
+ * 系统通用异常段
+ */
+ PARAMS_EXCEPTION(10000001, "参数异常"),
+ SYSTEM_EXCEPTION(10000002, "系统异常"),
+ UNKNOWN_EXCEPTION(10000003, "未知异常"),
+ SYSTEM_ERROR(10000004, "服务器内部错误"),
+ METHOD_NOT_ALLOWED(10000005, "请求方法不支持"),
+ NOT_FOUND(10000006, "请求资源不存在"),
+ FORBIDDEN(10000007, "请求被拒绝"),
+ UNAUTHORIZED_EXCEPTION(10000008, "未授权访问"),
+ UNSUPPORTED_OPERATION_EXCEPTION(10000009, "方法未实现"),
+ DATA_NOT_EXIST(10000010, "数据不存在"),
+
+ /**
+ * openapi通用异常段
+ */
+ IDENTIFIER_ERROR(20000000, "签名验证失败"),
+ API_LOGIN_ERROR(20000000, "登录验证失败"),
+
+
+ /**
+ * 组件通用异常段
+ */
+ GET_COMPONENT_INSTANCE_ERROR(30000001, "获取通讯组件实例失败"),
+ GET_COMPONENT_SCRIPT_ERROR(30000002, "获取通讯组件脚本失败"),
+ GET_CONVERT_ERROR(30000003, "获取转换器失败"),
+ GET_SPI_COMPONENT_ERROR(30000004, "获取组件CLASS失败"),
+ GET_SPI_CONVERT_ERROR(30000005, "获取转换器CLASS失败"),
+ COMPONENT_NOT_FOUND(30000006, "通讯组件不存在"),
+ SEND_DESTINATION_NOT_FOUND(30000007, "发送目标不存在"),
+ MSG_CONVERT_ERROR(30000008, "消息转换失败"),
+ DEVICE_REGISTER_ERROR(30000009, "设备注册失败"),
+ COMPONENT_ID_BLANK(30000010, "通讯组件ID为空"),
+ COMPONENT_JAR_NOT_FOUND(30000011, "通讯组件JAR包为空"),
+ COMPONENT_ALREADY(30000012, "通讯组件已经存在"),
+ SAVE_COMPONENT_SCRIPT_ERROR(30000013, "保存通讯组件脚本失败"),
+ SAVE_CONVERT_SCRIPT_ERROR(30000014, "保存转换器脚本失败"),
+ ADD_COMPONENT_ERROR(30000015, "添加通讯组件失败"),
+ ADD_CONVERT_ERROR(30000016, "添加转换器失败"),
+ CONVERT_NOT_FOUND(30000017, "转换器不存在"),
+ DELETE_CONVERT_ERROR(30000018, "删除转换器失败"),
+ DELETE_COMPONENT_ERROR(30000019, "删除通讯组件失败"),
+ PRODUCT_SECRET_ERROR(30000020, "产品密钥错误"),
+ COMPONENT_START_ERROR(30000021, "通讯组件启动失败"),
+ INIT_PRODUCER_ERROR(30000022, "初始化MQ生产者失败"),
+ SEND_MSG_ERROR(30000023, "发送消息失败"),
+ PLUGIN_ROUTER_NOT_FOUND(30000100, "未找到插件路由"),
+ PLUGIN_INSTANCE_NOT_FOUND(30000101, "插件实例未找到"),
+ PLUGIN_SERVICE_NOT_FOUND(30000102, "插件设备服务未找到"),
+ PLUGIN_INSTALL_FAILED(30000103, "插件安装失败"),
+ DEVICE_ACTION_FAILED(30000200, "设备动作执行失败"),
+
+
+ /**
+ * 大屏通用异常段
+ */
+ RESOURCE_FILE_NOT_FOUND(40000000, "资源包为空"),
+ BIG_SCREEN_NOT_FOUND(40000001, "大屏不存在"),
+ BIG_SCREEN_ALREADY(40000002, "大屏已存在"),
+ ADD_BIG_SCREEN_ERROR(40000003, "保存大屏失败"),
+ DELETE_BIG_SCREEN_ERROR(40000004, "删除大屏资源失败"),
+ SCREEN_API_NOT_FOUND(40000005, "大屏接口不存在"),
+ ADD_SCREEN_API_ERROR(40000006, "添加大屏接口失败"),
+ SCREEN_PUBLISH_ERROR(40000007, "大屏发布失败"),
+ API_LIST_BLANK(40000008, "接口列表为空"),
+
+ /**
+ * 业务通用异常段
+ */
+ ID_BLANK(50000001, "ID为空"),
+ TASK_NOT_SUPPORT_RENEW(50000002, "任务不支持续订"),
+ GROUP_ALREADY(50000003, "分组已经存在"),
+ GROUP_NOT_FOUND(50000004, "分组不存在"),
+ PRODUCT_NOT_FOUND(50000005, "产品不存在"),
+ DEVICE_NOT_FOUND(50000006, "设备不存在"),
+ DEVICE_OFFLINE(50000007, "设备已离线"),
+ DEVICE_ALREADY(50000008, "设备已存在"),
+ MODEL_DEVICE_ALREADY(50000009, "设备DN已存在"),
+
+ DEVICE_HAS_ASSOCIATED(50000010, "设备已关联"),
+ MODEL_ALREADY(50000011, "型号已存在"),
+ MODEL_SCRIPT_NOT_FOUND(50000012, "产品型号脚本不存在"),
+ PRODUCT_MODEL_NOT_FOUND(50000013, "产品型号不存在"),
+ FILE_NOT_FOUND(50000014, "文件不存在"),
+ RULE_NOT_FOUND(50000015, "规则不存在"),
+ RULE_ALREADY_RUNNING(50000016, "规则已运行"),
+ SEND_REQUEST_ERROR(50000017, "发送请求失败"),
+ TASK_NOT_FOUND(50000018, "任务不存在"),
+ RENEW_TASK_ERROR(50000019, "重启任务失败"),
+ HOME_NOT_FOUND(50000020, "家庭不存在"),
+ CURRENT_HOME_NOT_FOUND(50000021, "当前家庭不存在"),
+ SPACE_NOT_FOUND(50000022, "空间不存在"),
+ SPACE_DEVICE_NOT_FOUND(50000023, "空间设备不存在"),
+ DATA_BLANK(50000024, "数据为空"),
+ DATA_LENGTH_ERROR(50000025, "数据长度错误"),
+ DATA_FORMAT_ERROR(50000026, "数据格式错误"),
+ USER_NOT_FOUND(50000027, "用户不存在"),
+ RESET_PWD_ERROR(50000028, "重置密码失败"),
+ UPDATE_PWD_ERROR(50000029, "修改密码失败"),
+ PWD_ERROR(50000030, "密码错误"),
+ STATE_ERROR(50000031, "状态错误"),
+ RECORD_NOT_FOUND(50000032, "记录不存在"),
+ ADD_PLATFORM_USER_ERROR(50000033, "添加平台用户失败"),
+ UPLOAD_FILE_ERROR(50000034, "上传文件失败"),
+ FILE_NAME_IS_NULL(50000035, "文件名为空,获取文件名失败");
+
+
+ private int code;
+ private String message;
+
+ ErrCode(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ @Override
+ public Integer getKey() {
+ return this.code;
+ }
+
+ @Override
+ public String getValue() {
+ return this.message;
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/enums/IEnum.java b/iot-common-core/src/main/java/cc/iotkit/common/enums/IEnum.java
new file mode 100755
index 0000000..0b079e0
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/enums/IEnum.java
@@ -0,0 +1,33 @@
+package cc.iotkit.common.enums;
+
+public interface IEnum {
+ /**
+ * 获取枚举的key
+ */
+ Integer getKey();
+
+ /**
+ * 获取枚举的下标
+ */
+ String getValue();
+
+ /**
+ * 将参数反序列化为枚举
+ *
+ * @param param 当前值
+ * @param clazz 枚举类型
+ */
+ static & IEnum> T parse(Integer param, Class clazz) {
+ if (param == null || clazz == null) {
+ return null;
+ }
+ T[] enums = clazz.getEnumConstants();
+ for (T t : enums) {
+ Integer key = t.getKey();
+ if (key.equals(param)) {
+ return t;
+ }
+ }
+ return null;
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/enums/LoginType.java b/iot-common-core/src/main/java/cc/iotkit/common/enums/LoginType.java
new file mode 100755
index 0000000..b566a64
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/enums/LoginType.java
@@ -0,0 +1,44 @@
+package cc.iotkit.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 登录类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+ /**
+ * 密码登录
+ */
+ PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+ /**
+ * 短信登录
+ */
+ SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+ /**
+ * 邮箱登录
+ */
+ EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
+
+ /**
+ * 小程序登录
+ */
+ XCX("", "");
+
+ /**
+ * 登录重试超出限制提示
+ */
+ final String retryLimitExceed;
+
+ /**
+ * 登录重试限制计数提示
+ */
+ final String retryLimitCount;
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/enums/UserStatus.java b/iot-common-core/src/main/java/cc/iotkit/common/enums/UserStatus.java
new file mode 100755
index 0000000..61ae829
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package cc.iotkit.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 用户状态
+ *
+ * @author ruoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum UserStatus {
+ /**
+ * 正常
+ */
+ OK("0", "正常"),
+ /**
+ * 停用
+ */
+ DISABLE("1", "停用"),
+ /**
+ * 删除
+ */
+ DELETED("2", "删除");
+
+ private final String code;
+ private final String info;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/enums/UserType.java b/iot-common-core/src/main/java/cc/iotkit/common/enums/UserType.java
new file mode 100755
index 0000000..ce4d00e
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/enums/UserType.java
@@ -0,0 +1,43 @@
+package cc.iotkit.common.enums;
+
+import cc.iotkit.common.exception.BizException;
+import cc.iotkit.common.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 设备类型
+ * 针对多套 用户体系
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+ /**
+ * pc端
+ */
+ SYS_USER("sys_user"),
+
+ /**
+ * 第三方api
+ */
+ API_USER("api_user"),
+
+ /**
+ * app端
+ */
+ APP_USER("app_user");
+
+ private final String userType;
+
+ public static UserType getUserType(String str) {
+ for (UserType value : values()) {
+ if (StringUtils.contains(str, value.getUserType())) {
+ return value;
+ }
+ }
+ throw new BizException("'UserType' not found By " + str);
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/exception/BaseException.java b/iot-common-core/src/main/java/cc/iotkit/common/exception/BaseException.java
new file mode 100755
index 0000000..b3f42cd
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/exception/BaseException.java
@@ -0,0 +1,80 @@
+package cc.iotkit.common.exception;
+
+
+import cc.iotkit.common.utils.MessageUtils;
+import cc.iotkit.common.utils.StringUtils;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+
+
+/**
+ * 基础异常
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class BaseException extends RuntimeException {
+
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 所属模块
+ */
+ private String module;
+
+ /**
+ * 错误码
+ */
+ private String code;
+
+ /**
+ * 错误码对应的参数
+ */
+ private Object[] args;
+
+ /**
+ * 错误消息
+ */
+ private String message;
+
+ public BaseException(String module, String code, Object[] args, String message) {
+ this.module = module;
+ this.code = code;
+ this.args = args;
+ this.message = message;
+ }
+
+ public BaseException(String module, String code, String message) {
+ this(module, code, null, message);
+ }
+
+ public BaseException(String module, String message) {
+ this(module, null, null, message);
+ }
+
+ public BaseException(String code, Object[] args) {
+ this(null, code, args, null);
+ }
+
+ public BaseException(String message) {
+ this(null, null, null, message);
+ }
+
+ @Override
+ public String getMessage() {
+ String message = null;
+ if (!StringUtils.isEmpty(code)) {
+ message = MessageUtils.message(code, args);
+ }
+ if (message == null) {
+ message = message;
+ }
+ return message;
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/exception/BizException.java b/iot-common-core/src/main/java/cc/iotkit/common/exception/BizException.java
new file mode 100755
index 0000000..7fc0850
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/exception/BizException.java
@@ -0,0 +1,74 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.exception;
+
+import cc.iotkit.common.enums.ErrCode;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BizException extends RuntimeException {
+
+ /**
+ * 所属模块
+ */
+ private String module;
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误消息
+ */
+ private String message;
+
+ public BizException(String message) {
+ super(message);
+ this.message = message;
+ this.code = ErrCode.SYSTEM_EXCEPTION.getKey();
+ }
+
+ /**
+ * 统一异常消息处理
+ *
+ * @param errCode 异常枚举值
+ */
+ public BizException(ErrCode errCode) {
+ this.message = errCode.getValue();
+ this.code = errCode.getKey();
+ }
+
+ public BizException(ErrCode errCode, Throwable cause) {
+ super(cause);
+ this.code = errCode.getKey();
+ this.message = errCode.getValue();
+ }
+
+ public BizException(ErrCode errCode, String message) {
+ this.message = message;
+ this.code = errCode.getKey();
+ }
+
+ public BizException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public BizException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/exception/ViewException.java b/iot-common-core/src/main/java/cc/iotkit/common/exception/ViewException.java
new file mode 100755
index 0000000..ffb1b2e
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/exception/ViewException.java
@@ -0,0 +1,41 @@
+package cc.iotkit.common.exception;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 视图异常
+ *
+ * @author sjg
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ViewException extends RuntimeException {
+
+ public static final int CODE_FAILED = 500;
+ public static final int CODE_WARN = 601;
+
+ private int code;
+ private String message;
+ private Object data;
+
+ public ViewException() {
+ }
+
+ public ViewException(String message) {
+ super(message);
+ this.message = message;
+ }
+
+ public ViewException(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public ViewException(int code, String message, Object data) {
+ this.code = code;
+ this.message = message;
+ this.data = data;
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/exception/user/UserException.java b/iot-common-core/src/main/java/cc/iotkit/common/exception/user/UserException.java
new file mode 100755
index 0000000..1af89ae
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/exception/user/UserException.java
@@ -0,0 +1,47 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.exception.user;
+
+import cc.iotkit.common.enums.ErrCode;
+import cc.iotkit.common.exception.BizException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserException extends BizException {
+
+ /**
+ * 所属模块
+ */
+ private String module;
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误消息
+ */
+ private String message;
+
+ public UserException(String message) {
+ super( message);
+ this.code = ErrCode.SYSTEM_EXCEPTION.getKey();
+ this.message = message;
+
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/function/IfHandler.java b/iot-common-core/src/main/java/cc/iotkit/common/function/IfHandler.java
new file mode 100755
index 0000000..320f669
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/function/IfHandler.java
@@ -0,0 +1,11 @@
+package cc.iotkit.common.function;
+
+/**
+ * @author huangwenl
+ * @date 2022-11-10
+ */
+@FunctionalInterface
+public interface IfHandler {
+
+ void handler(Runnable tHandler, Runnable fHandler);
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/service/ConfigService.java b/iot-common-core/src/main/java/cc/iotkit/common/service/ConfigService.java
new file mode 100755
index 0000000..910acc4
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/service/ConfigService.java
@@ -0,0 +1,18 @@
+package cc.iotkit.common.service;
+
+/**
+ * 通用 参数配置服务
+ *
+ * @author Lion Li
+ */
+public interface ConfigService {
+
+ /**
+ * 根据参数 key 获取参数值
+ *
+ * @param configKey 参数 key
+ * @return 参数值
+ */
+ String getConfigValue(String configKey);
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/service/DeptService.java b/iot-common-core/src/main/java/cc/iotkit/common/service/DeptService.java
new file mode 100755
index 0000000..53e4993
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/service/DeptService.java
@@ -0,0 +1,18 @@
+package cc.iotkit.common.service;
+
+/**
+ * 通用 部门服务
+ *
+ * @author Lion Li
+ */
+public interface DeptService {
+
+ /**
+ * 通过部门ID查询部门名称
+ *
+ * @param deptIds 部门ID串逗号分隔
+ * @return 部门名称串逗号分隔
+ */
+ String selectDeptNameByIds(String deptIds);
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/service/DeviceService.java b/iot-common-core/src/main/java/cc/iotkit/common/service/DeviceService.java
new file mode 100755
index 0000000..608cbd7
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/service/DeviceService.java
@@ -0,0 +1,19 @@
+package cc.iotkit.common.service;
+
+import cc.iotkit.common.thing.ThingService;
+
+/**
+ * 通用设备服务
+ *
+ * @author sjg
+ */
+public interface DeviceService {
+
+ /**
+ * 调用设备服务
+ *
+ * @param service 服务
+ */
+ void invoke(ThingService> service);
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/service/DictService.java b/iot-common-core/src/main/java/cc/iotkit/common/service/DictService.java
new file mode 100755
index 0000000..6ab1082
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/service/DictService.java
@@ -0,0 +1,57 @@
+package cc.iotkit.common.service;
+
+/**
+ * 通用 字典服务
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+ /**
+ * 分隔符
+ */
+ String SEPARATOR = ",";
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @return 字典标签
+ */
+ default String getDictLabel(String dictType, String dictValue) {
+ return getDictLabel(dictType, dictValue, SEPARATOR);
+ }
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @return 字典值
+ */
+ default String getDictValue(String dictType, String dictLabel) {
+ return getDictValue(dictType, dictLabel, SEPARATOR);
+ }
+
+ /**
+ * 根据字典类型和字典值获取字典标签
+ *
+ * @param dictType 字典类型
+ * @param dictValue 字典值
+ * @param separator 分隔符
+ * @return 字典标签
+ */
+ String getDictLabel(String dictType, String dictValue, String separator);
+
+ /**
+ * 根据字典类型和字典标签获取字典值
+ *
+ * @param dictType 字典类型
+ * @param dictLabel 字典标签
+ * @param separator 分隔符
+ * @return 字典值
+ */
+ String getDictValue(String dictType, String dictLabel, String separator);
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/service/OssService.java b/iot-common-core/src/main/java/cc/iotkit/common/service/OssService.java
new file mode 100755
index 0000000..0ac5f8f
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/service/OssService.java
@@ -0,0 +1,18 @@
+package cc.iotkit.common.service;
+
+/**
+ * 通用 OSS服务
+ *
+ * @author Lion Li
+ */
+public interface OssService {
+
+ /**
+ * 通过ossId查询对应的url
+ *
+ * @param ossIds ossId串逗号分隔
+ * @return url串逗号分隔
+ */
+ String selectUrlByIds(String ossIds);
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/service/UserService.java b/iot-common-core/src/main/java/cc/iotkit/common/service/UserService.java
new file mode 100755
index 0000000..5b57e39
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/service/UserService.java
@@ -0,0 +1,18 @@
+package cc.iotkit.common.service;
+
+/**
+ * 通用 用户服务
+ *
+ * @author Lion Li
+ */
+public interface UserService {
+
+ /**
+ * 通过用户ID查询用户账户
+ *
+ * @param userId 用户ID
+ * @return 用户账户
+ */
+ String selectUserNameById(Long userId);
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/thing/ThingService.java b/iot-common-core/src/main/java/cc/iotkit/common/thing/ThingService.java
new file mode 100755
index 0000000..b443203
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/thing/ThingService.java
@@ -0,0 +1,43 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.thing;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sjg
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class ThingService {
+
+ public static final String TYPE_PROPERTY = "property";
+ public static final String TYPE_SERVICE = "service";
+
+ public static final String TYPE_OTA = "ota";
+
+ private String mid;
+
+ private String productKey;
+
+ private String deviceName;
+
+ private String type;
+
+ private String identifier;
+
+ private T params;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/EmailLoginBody.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/EmailLoginBody.java
new file mode 100755
index 0000000..bbda62b
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/EmailLoginBody.java
@@ -0,0 +1,35 @@
+package cc.iotkit.common.undefined;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class EmailLoginBody {
+
+ /**
+ * 租户ID
+ */
+ @NotBlank(message = "{tenant.number.not.blank}")
+ private String tenantId;
+
+ /**
+ * 邮箱
+ */
+ @NotBlank(message = "{user.email.not.blank}")
+ @Email(message = "{user.email.not.valid}")
+ private String email;
+
+ /**
+ * 邮箱code
+ */
+ @NotBlank(message = "{email.code.not.blank}")
+ private String emailCode;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/LoginBody.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/LoginBody.java
new file mode 100755
index 0000000..3dff14a
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/LoginBody.java
@@ -0,0 +1,43 @@
+package cc.iotkit.common.undefined;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 用户登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LoginBody {
+
+ /**
+ * 租户ID
+ */
+ @NotBlank(message = "{tenant.number.not.blank}")
+ private String tenantId;
+
+ /**
+ * 用户名
+ */
+ @NotBlank(message = "{user.username.not.blank}")
+ private String username;
+
+ /**
+ * 用户密码
+ */
+ @NotBlank(message = "{user.password.not.blank}")
+ private String password;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 唯一标识
+ */
+ private String uuid;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/LoginUser.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/LoginUser.java
new file mode 100755
index 0000000..d93e713
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/LoginUser.java
@@ -0,0 +1,143 @@
+package cc.iotkit.common.undefined;
+
+import cc.iotkit.common.utils.StringUtils;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 部门ID
+ */
+ private Long deptId;
+
+ /**
+ * 部门名
+ */
+ private String deptName;
+
+ /**
+ * 用户唯一标识
+ */
+ private String token;
+
+ /**
+ * 用户类型
+ */
+ private String userType;
+
+ /**
+ * 登录时间
+ */
+ private Long loginTime;
+
+ /**
+ * 过期时间
+ */
+ private Long expireTime;
+
+ /**
+ * 登录IP地址
+ */
+ private String ipaddr;
+
+ /**
+ * 登录地点
+ */
+ private String loginLocation;
+
+ /**
+ * 浏览器类型
+ */
+ private String browser;
+
+ /**
+ * 操作系统
+ */
+ private String os;
+
+ /**
+ * 菜单权限
+ */
+ private Set menuPermission;
+
+ /**
+ * 角色权限
+ */
+ private Set rolePermission;
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 角色对象
+ */
+ private List roles;
+
+ /**
+ * 数据权限 当前角色ID
+ */
+ private Long roleId;
+
+ /**
+ * 获取登录id
+ */
+ public String getLoginId() {
+ if (userType == null) {
+ throw new IllegalArgumentException("用户类型不能为空");
+ }
+ if (userId == null) {
+ throw new IllegalArgumentException("用户ID不能为空");
+ }
+ return userType + ":" + userId;
+ }
+
+ /**
+ * 根据loginId构造loginUser对象
+ *
+ * @param loginId 登录id
+ * @return LoginUser
+ * @see LoginUser::getLoginId
+ */
+ public static LoginUser from(String loginId) {
+ if (StringUtils.isBlank(loginId)) {
+ return null;
+ }
+ String[] split = loginId.split(":");
+ if (split.length < 2) {
+ return null;
+ }
+
+ LoginUser user = new LoginUser();
+ user.setUserType(split[0]);
+ user.setUserId(Long.parseLong(split[1]));
+ return user;
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/RegisterBody.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/RegisterBody.java
new file mode 100755
index 0000000..3e8a6cb
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/RegisterBody.java
@@ -0,0 +1,17 @@
+package cc.iotkit.common.undefined;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户注册对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RegisterBody extends LoginBody {
+
+ private String userType;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/RoleDTO.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/RoleDTO.java
new file mode 100755
index 0000000..1198d8d
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/RoleDTO.java
@@ -0,0 +1,38 @@
+package cc.iotkit.common.undefined;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 角色
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class RoleDTO implements Serializable {
+
+ /**
+ * 角色ID
+ */
+ private Long id;
+
+ /**
+ * 角色名称
+ */
+ private String roleName;
+
+ /**
+ * 角色权限
+ */
+ private String roleKey;
+
+ /**
+ * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
+ */
+ private String dataScope;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/SmsLoginBody.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/SmsLoginBody.java
new file mode 100755
index 0000000..b3ad99b
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/SmsLoginBody.java
@@ -0,0 +1,33 @@
+package cc.iotkit.common.undefined;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class SmsLoginBody {
+
+ /**
+ * 租户ID
+ */
+ @NotBlank(message = "{tenant.number.not.blank}")
+ private String tenantId;
+
+ /**
+ * 手机号
+ */
+ @NotBlank(message = "{user.phonenumber.not.blank}")
+ private String phonenumber;
+
+ /**
+ * 短信code
+ */
+ @NotBlank(message = "{sms.code.not.blank}")
+ private String smsCode;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/UserOnlineDTO.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/UserOnlineDTO.java
new file mode 100755
index 0000000..4e27ac7
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/UserOnlineDTO.java
@@ -0,0 +1,60 @@
+package cc.iotkit.common.undefined;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 当前在线会话
+ *
+ * @author ruoyi
+ */
+
+@Data
+@NoArgsConstructor
+public class UserOnlineDTO implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 会话编号
+ */
+ private String tokenId;
+
+ /**
+ * 部门名称
+ */
+ private String deptName;
+
+ /**
+ * 用户名称
+ */
+ private String userName;
+
+ /**
+ * 登录IP地址
+ */
+ private String ipaddr;
+
+ /**
+ * 登录地址
+ */
+ private String loginLocation;
+
+ /**
+ * 浏览器类型
+ */
+ private String browser;
+
+ /**
+ * 操作系统
+ */
+ private String os;
+
+ /**
+ * 登录时间
+ */
+ private Long loginTime;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/undefined/XcxLoginUser.java b/iot-common-core/src/main/java/cc/iotkit/common/undefined/XcxLoginUser.java
new file mode 100755
index 0000000..cd8963a
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/undefined/XcxLoginUser.java
@@ -0,0 +1,26 @@
+package cc.iotkit.common.undefined;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+
+/**
+ * 小程序登录用户身份权限
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+public class XcxLoginUser extends LoginUser {
+
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * openid
+ */
+ private String openid;
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/CodecUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/CodecUtil.java
new file mode 100755
index 0000000..bce9482
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/CodecUtil.java
@@ -0,0 +1,115 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+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) {
+ return StringUtils.isEmpty(base64Code) ? null : new Base64().decode(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(StandardCharsets.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);
+ }
+
+ public static String md5Str(String content) {
+ return DigestUtils.md5Hex(content);
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ComponentClassLoader.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ComponentClassLoader.java
new file mode 100755
index 0000000..4412b89
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ComponentClassLoader.java
@@ -0,0 +1,103 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.utils;
+
+import cc.iotkit.common.enums.ErrCode;
+import cc.iotkit.common.exception.BizException;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ComponentClassLoader {
+ private static final Map classLoaders = new HashMap<>();
+
+ protected static Class findClass(String name, String clsName) throws ClassNotFoundException {
+ ClassLoader classLoader = classLoaders.get(name);
+ return (Class) classLoader.loadClass(clsName);
+ }
+
+ private static String addUrl(String name, File jarPath) throws NoSuchMethodException, InvocationTargetException,
+ IllegalAccessException, IOException {
+ URLClassLoader classLoader = classLoaders.get(name);
+ if (classLoader != null) {
+ classLoader.close();
+ }
+
+ classLoader = URLClassLoader.newInstance(new URL[]{jarPath.toURI().toURL()},
+ Thread.currentThread().getContextClassLoader());
+ classLoaders.put(name, classLoader);
+
+ Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+ if (!method.canAccess(classLoader)) {
+ method.setAccessible(true);
+ }
+
+ URL url = jarPath.toURI().toURL();
+ method.invoke(classLoader, url);
+ InputStream is = classLoader.getResourceAsStream("component.spi");
+ if (is == null) {
+ return null;
+ }
+
+ //多行只取第1行,并处理空格
+ String[] lines = IOUtils.toString(is, StandardCharsets.UTF_8).split("\\s");
+ if (lines.length == 0) {
+ return null;
+ }
+ return lines[0].trim();
+ }
+
+ public static T getComponent(String name, File jarFile) throws Exception {
+ String className = addUrl(name, jarFile);
+ if (StringUtils.isBlank(className)) {
+ throw new BizException(ErrCode.GET_SPI_COMPONENT_ERROR);
+ }
+ Class componentClass = findClass(name, className);
+ return componentClass.getDeclaredConstructor().newInstance();
+ }
+
+ public static T getConverter(String name) throws Exception {
+ URLClassLoader classLoader = classLoaders.get(name);
+ InputStream is = classLoader.getResourceAsStream("convert.spi");
+ if (is == null) {
+ return null;
+ }
+
+ //多行只取第1行,并处理空格
+ String[] lines = IOUtils.toString(is, StandardCharsets.UTF_8).split("\\s");
+ if (lines.length == 0) {
+ throw new BizException(ErrCode.GET_SPI_CONVERT_ERROR);
+ }
+ String className = lines[0].trim();
+ Class converterClass = findClass(name, className);
+ return converterClass.getDeclaredConstructor().newInstance();
+ }
+
+ public static void closeClassLoader(String name) {
+ try {
+ URLClassLoader classLoader = classLoaders.get(name);
+ if (classLoader != null){
+ classLoader.close();
+ }
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/DateUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/DateUtils.java
new file mode 100755
index 0000000..1a5835a
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/DateUtils.java
@@ -0,0 +1,164 @@
+package cc.iotkit.common.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.util.Date;
+
+/**
+ * 时间工具类
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+
+ public static final String YYYY = "yyyy";
+
+ public static final String YYYY_MM = "yyyy-MM";
+
+ public static final String YYYY_MM_DD = "yyyy-MM-dd";
+
+ public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+ public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+ private static final String[] PARSE_PATTERNS = {
+ "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+ "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+ "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+ /**
+ * 获取当前Date型日期
+ *
+ * @return Date() 当前日期
+ */
+ public static Date getNowDate() {
+ return new Date();
+ }
+
+ /**
+ * 获取当前日期, 默认格式为yyyy-MM-dd
+ *
+ * @return String
+ */
+ public static String getDate() {
+ return dateTimeNow(YYYY_MM_DD);
+ }
+
+ public static String getTime() {
+ return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+ }
+
+ public static String dateTimeNow() {
+ return dateTimeNow(YYYYMMDDHHMMSS);
+ }
+
+ public static String dateTimeNow(final String format) {
+ return parseDateToStr(format, new Date());
+ }
+
+ public static String dateTime(final Date date) {
+ return parseDateToStr(YYYY_MM_DD, date);
+ }
+
+ public static String parseDateToStr(final String format, final Date date) {
+ return new SimpleDateFormat(format).format(date);
+ }
+
+ public static Date dateTime(final String format, final String ts) {
+ try {
+ return new SimpleDateFormat(format).parse(ts);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 日期路径 即年/月/日 如2018/08/08
+ */
+ public static String datePath() {
+ Date now = new Date();
+ return DateFormatUtils.format(now, "yyyy/MM/dd");
+ }
+
+ /**
+ * 日期路径 即年/月/日 如20180808
+ */
+ public static String dateTime() {
+ Date now = new Date();
+ return DateFormatUtils.format(now, "yyyyMMdd");
+ }
+
+ /**
+ * 日期型字符串转化为日期 格式
+ */
+ public static Date parseDate(Object str) {
+ if (str == null) {
+ return null;
+ }
+ try {
+ return parseDate(str.toString(), PARSE_PATTERNS);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 获取服务器启动时间
+ */
+ public static Date getServerStartDate() {
+ long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+ return new Date(time);
+ }
+
+ /**
+ * 计算相差天数
+ */
+ public static int differentDaysByMillisecond(Date date1, Date date2) {
+ return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+ }
+
+ /**
+ * 计算两个时间差
+ */
+ public static String getDatePoor(Date endDate, Date nowDate) {
+ long nd = 1000 * 24 * 60 * 60L;
+ long nh = 1000 * 60 * 60L;
+ long nm = 1000 * 60L;
+ // long ns = 1000;
+ // 获得两个时间的毫秒时间差异
+ long diff = endDate.getTime() - nowDate.getTime();
+ // 计算差多少天
+ long day = diff / nd;
+ // 计算差多少小时
+ long hour = diff % nd / nh;
+ // 计算差多少分钟
+ long min = diff % nd % nh / nm;
+ // 计算差多少秒//输出结果
+ // long sec = diff % nd % nh % nm / ns;
+ return day + "天" + hour + "小时" + min + "分钟";
+ }
+
+ /**
+ * 增加 LocalDateTime ==> Date
+ */
+ public static Date toDate(LocalDateTime temporalAccessor) {
+ ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 增加 LocalDate ==> Date
+ */
+ public static Date toDate(LocalDate temporalAccessor) {
+ LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+ ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/DeviceUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/DeviceUtil.java
new file mode 100755
index 0000000..0e33b8c
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/DeviceUtil.java
@@ -0,0 +1,35 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.utils;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.commons.lang3.StringUtils;
+
+public class DeviceUtil {
+
+ /**
+ * 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 + 1);
+ } 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();
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/FIUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/FIUtil.java
new file mode 100755
index 0000000..bee8630
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/FIUtil.java
@@ -0,0 +1,21 @@
+package cc.iotkit.common.utils;
+
+import cc.iotkit.common.function.IfHandler;
+
+/**
+ * @author huangwenl
+ * @date 2022-11-10
+ */
+public class FIUtil {
+
+
+ public static IfHandler isTotF(boolean param) {
+ return (tHandler, fHandler) -> {
+ if (param) {
+ tHandler.run();
+ } else {
+ fHandler.run();
+ }
+ };
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/HexUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/HexUtil.java
new file mode 100755
index 0000000..576d593
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/HexUtil.java
@@ -0,0 +1,328 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+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();
+ }
+
+ /**
+ * bytes转int
+ */
+ public static int bytesToInt(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ buffer.flip();
+ return buffer.getInt();
+ }
+
+ 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)));
+ }
+
+ /**
+ * 16进制表示的字符串转换为字节数组
+ *
+ * @param s 16进制表示的字符串
+ * @return byte[] 字节数组
+ */
+ public static byte[] hexStringToByteArray(String s) {
+ int len = s.length();
+ byte[] b = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
+ b[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
+ .digit(s.charAt(i + 1), 16));
+ }
+ return b;
+
+ }
+ /**
+ * 计算CRC16校验
+ *
+ * @param data 需要计算的数组
+ * @param offset 起始位置
+ * @param len 长度
+ * @return CRC16校验值
+ */
+ public static int calcCrc16(byte[] data, int offset, int len) {
+ byte[] crc16_tab_h = {
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+ (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+ (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+
+ (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+ (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+ (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+
+ (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+ (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+ (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+ (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,
+ (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40
+ };
+ byte[] crc16_tab_l = {
+ (byte) 0x00, (byte) 0xC0, (byte) 0xC1, (byte) 0x01, (byte) 0xC3, (byte) 0x03, (byte) 0x02, (byte) 0xC2, (byte) 0xC6, (byte) 0x06,
+ (byte) 0x07, (byte) 0xC7, (byte) 0x05, (byte) 0xC5, (byte) 0xC4, (byte) 0x04, (byte) 0xCC, (byte) 0x0C, (byte) 0x0D, (byte) 0xCD,
+ (byte) 0x0F, (byte) 0xCF, (byte) 0xCE, (byte) 0x0E, (byte) 0x0A, (byte) 0xCA, (byte) 0xCB, (byte) 0x0B, (byte) 0xC9, (byte) 0x09,
+ (byte) 0x08, (byte) 0xC8, (byte) 0xD8, (byte) 0x18, (byte) 0x19, (byte) 0xD9, (byte) 0x1B, (byte) 0xDB, (byte) 0xDA, (byte) 0x1A,
+ (byte) 0x1E, (byte) 0xDE, (byte) 0xDF, (byte) 0x1F, (byte) 0xDD, (byte) 0x1D, (byte) 0x1C, (byte) 0xDC, (byte) 0x14, (byte) 0xD4,
+ (byte) 0xD5, (byte) 0x15, (byte) 0xD7, (byte) 0x17, (byte) 0x16, (byte) 0xD6, (byte) 0xD2, (byte) 0x12, (byte) 0x13, (byte) 0xD3,
+ (byte) 0x11, (byte) 0xD1, (byte) 0xD0, (byte) 0x10, (byte) 0xF0, (byte) 0x30, (byte) 0x31, (byte) 0xF1, (byte) 0x33, (byte) 0xF3,
+ (byte) 0xF2, (byte) 0x32, (byte) 0x36, (byte) 0xF6, (byte) 0xF7, (byte) 0x37, (byte) 0xF5, (byte) 0x35, (byte) 0x34, (byte) 0xF4,
+ (byte) 0x3C, (byte) 0xFC, (byte) 0xFD, (byte) 0x3D, (byte) 0xFF, (byte) 0x3F, (byte) 0x3E, (byte) 0xFE, (byte) 0xFA, (byte) 0x3A,
+ (byte) 0x3B, (byte) 0xFB, (byte) 0x39, (byte) 0xF9, (byte) 0xF8, (byte) 0x38, (byte) 0x28, (byte) 0xE8, (byte) 0xE9, (byte) 0x29,
+
+ (byte) 0xEB, (byte) 0x2B, (byte) 0x2A, (byte) 0xEA, (byte) 0xEE, (byte) 0x2E, (byte) 0x2F, (byte) 0xEF, (byte) 0x2D, (byte) 0xED,
+ (byte) 0xEC, (byte) 0x2C, (byte) 0xE4, (byte) 0x24, (byte) 0x25, (byte) 0xE5, (byte) 0x27, (byte) 0xE7, (byte) 0xE6, (byte) 0x26,
+ (byte) 0x22, (byte) 0xE2, (byte) 0xE3, (byte) 0x23, (byte) 0xE1, (byte) 0x21, (byte) 0x20, (byte) 0xE0, (byte) 0xA0, (byte) 0x60,
+ (byte) 0x61, (byte) 0xA1, (byte) 0x63, (byte) 0xA3, (byte) 0xA2, (byte) 0x62, (byte) 0x66, (byte) 0xA6, (byte) 0xA7, (byte) 0x67,
+ (byte) 0xA5, (byte) 0x65, (byte) 0x64, (byte) 0xA4, (byte) 0x6C, (byte) 0xAC, (byte) 0xAD, (byte) 0x6D, (byte) 0xAF, (byte) 0x6F,
+ (byte) 0x6E, (byte) 0xAE, (byte) 0xAA, (byte) 0x6A, (byte) 0x6B, (byte) 0xAB, (byte) 0x69, (byte) 0xA9, (byte) 0xA8, (byte) 0x68,
+ (byte) 0x78, (byte) 0xB8, (byte) 0xB9, (byte) 0x79, (byte) 0xBB, (byte) 0x7B, (byte) 0x7A, (byte) 0xBA, (byte) 0xBE, (byte) 0x7E,
+ (byte) 0x7F, (byte) 0xBF, (byte) 0x7D, (byte) 0xBD, (byte) 0xBC, (byte) 0x7C, (byte) 0xB4, (byte) 0x74, (byte) 0x75, (byte) 0xB5,
+ (byte) 0x77, (byte) 0xB7, (byte) 0xB6, (byte) 0x76, (byte) 0x72, (byte) 0xB2, (byte) 0xB3, (byte) 0x73, (byte) 0xB1, (byte) 0x71,
+ (byte) 0x70, (byte) 0xB0, (byte) 0x50, (byte) 0x90, (byte) 0x91, (byte) 0x51, (byte) 0x93, (byte) 0x53, (byte) 0x52, (byte) 0x92,
+
+ (byte) 0x96, (byte) 0x56, (byte) 0x57, (byte) 0x97, (byte) 0x55, (byte) 0x95, (byte) 0x94, (byte) 0x54, (byte) 0x9C, (byte) 0x5C,
+ (byte) 0x5D, (byte) 0x9D, (byte) 0x5F, (byte) 0x9F, (byte) 0x9E, (byte) 0x5E, (byte) 0x5A, (byte) 0x9A, (byte) 0x9B, (byte) 0x5B,
+ (byte) 0x99, (byte) 0x59, (byte) 0x58, (byte) 0x98, (byte) 0x88, (byte) 0x48, (byte) 0x49, (byte) 0x89, (byte) 0x4B, (byte) 0x8B,
+ (byte) 0x8A, (byte) 0x4A, (byte) 0x4E, (byte) 0x8E, (byte) 0x8F, (byte) 0x4F, (byte) 0x8D, (byte) 0x4D, (byte) 0x4C, (byte) 0x8C,
+ (byte) 0x44, (byte) 0x84, (byte) 0x85, (byte) 0x45, (byte) 0x87, (byte) 0x47, (byte) 0x46, (byte) 0x86, (byte) 0x82, (byte) 0x42,
+
+ (byte) 0x43, (byte) 0x83, (byte) 0x41, (byte) 0x81, (byte) 0x80, (byte) 0x40
+ };
+ int pre = 0xffff;
+ int ucCRCHi = (pre & 0xff00) >> 8;
+ int ucCRCLo = pre & 0x00ff;
+ int iIndex;
+ for (int i = 0; i < len; ++i) {
+ iIndex = (ucCRCLo ^ data[offset + i]) & 0x00ff;
+ ucCRCLo = ucCRCHi ^ crc16_tab_h[iIndex];
+ ucCRCHi = crc16_tab_l[iIndex];
+ }
+ return ((ucCRCHi & 0x00ff) << 8) | (ucCRCLo & 0x00ff) & 0xffff;
+ }
+}
\ No newline at end of file
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/JsonUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/JsonUtils.java
new file mode 100755
index 0000000..fe102af
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/JsonUtils.java
@@ -0,0 +1,129 @@
+package cc.iotkit.common.utils;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.PrimitiveArrayUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * JSON 工具类
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils {
+
+ private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+ public static ObjectMapper getObjectMapper() {
+ return OBJECT_MAPPER;
+ }
+
+ public static String toJsonString(Object object) {
+ if (ObjectUtil.isNull(object)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static T parse(String json, Class cls) {
+ if (StringUtils.isBlank(json)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(json, cls);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static T parseObject(String text, Class clazz) {
+ if (StringUtils.isEmpty(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, clazz);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static T parseObject(byte[] bytes, Class clazz) {
+ if (PrimitiveArrayUtil.isEmpty(bytes)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(bytes, clazz);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static T parseObject(String text, TypeReference typeReference) {
+ if (StringUtils.isBlank(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, typeReference);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Dict parseMap(String text) {
+ if (StringUtils.isBlank(text)) {
+ return null;
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+ } catch (MismatchedInputException e) {
+ // 类型不匹配说明不是json
+ return null;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static List parseArrayMap(String text) {
+ if (StringUtils.isBlank(text)) {
+ return Collections.emptyList();
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static List parseArray(String text, Class clazz) {
+ if (StringUtils.isEmpty(text)) {
+ return new ArrayList<>();
+ }
+ try {
+ return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static T objectToJavaBean(Object obj, Class clazz) {
+ if (Objects.isNull(obj)) {
+ return null;
+ }
+ return OBJECT_MAPPER.convertValue(obj, clazz);
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/MapstructUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/MapstructUtils.java
new file mode 100755
index 0000000..f9a31e7
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/MapstructUtils.java
@@ -0,0 +1,111 @@
+package cc.iotkit.common.utils;
+
+import cc.iotkit.common.api.Paging;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import io.github.linpeilie.Converter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.util.Assert;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mapstruct 工具类
+ * 参考文档:mapstruct-plus
+ *
+ * @author Michelle.Chung
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MapstructUtils {
+
+ private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
+
+ /**
+ * 将 T 类型对象,转换为 desc 类型的对象并返回
+ *
+ * @param source 数据来源实体
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static V convert(T source, Class desc) {
+ Assert.notNull(desc, "desc is null");
+ if (source == null) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象
+ *
+ * @param source 数据来源实体
+ * @param desc 转换后的对象
+ * @return desc
+ */
+ public static V convert(T source, V desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(desc)) {
+ return null;
+ }
+ return CONVERTER.convert(source, desc);
+ }
+
+ /**
+ * 将 T 类型的集合,转换为 desc 类型的集合并返回
+ *
+ * @param sourceList 数据来源实体列表
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static List convert(List sourceList, Class desc) {
+ if (ObjectUtil.isNull(sourceList)) {
+ return Collections.emptyList();
+ }
+ if (CollUtil.isEmpty(sourceList)) {
+ return CollUtil.newArrayList();
+ }
+ return CONVERTER.convert(sourceList, desc);
+ }
+
+ /**
+ * 将 Map 转换为 beanClass 类型的集合并返回
+ *
+ * @param map 数据来源
+ * @param beanClass bean类
+ * @return bean对象
+ */
+ public static T convert(Map map, Class beanClass) {
+ if (MapUtil.isEmpty(map)) {
+ return null;
+ }
+ if (ObjectUtil.isNull(beanClass)) {
+ return null;
+ }
+ return CONVERTER.convert(map, beanClass);
+ }
+
+ /**
+ * 转换分页对象
+ *
+ * @param source 数据来源
+ * @param desc 描述对象 转换后的对象
+ * @return desc
+ */
+ public static Paging convert(Paging source, Class desc) {
+ if (ObjectUtil.isNull(source)) {
+ return null;
+ }
+ if (CollUtil.isEmpty(source.getRows())) {
+ return new Paging<>(0, new ArrayList<>());
+ }
+ return new Paging<>(source.getTotal(), CONVERTER.convert(source.getRows(), desc));
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/MessageUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/MessageUtils.java
new file mode 100755
index 0000000..3222016
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/MessageUtils.java
@@ -0,0 +1,34 @@
+package cc.iotkit.common.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MessageUtils {
+
+ private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
+
+ /**
+ * 根据消息键和参数 获取消息 委托给spring messageSource
+ *
+ * @param code 消息键
+ * @param args 参数
+ * @return 获取国际化翻译值
+ */
+ public static String message(String code, Object... args) {
+// TODO: 国际化
+ try{
+ return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
+ } catch (Exception e) {
+ return code;
+ }
+
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ReflectUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ReflectUtil.java
new file mode 100755
index 0000000..509d084
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ReflectUtil.java
@@ -0,0 +1,54 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.utils;
+
+
+import lombok.SneakyThrows;
+import org.apache.commons.beanutils.BeanMap;
+import org.apache.commons.beanutils.BeanUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ReflectUtil {
+
+ @SneakyThrows
+ public static T copyNoNulls(T from, T to, String... fields) {
+ List fieldList = Arrays.asList(fields);
+
+ Map map = new HashMap<>();
+ new BeanMap(from).forEach((key, value) -> {
+ if (value == null) {
+ return;
+ }
+ String field = key.toString();
+ if (fields.length == 0 || fieldList.contains(field)) {
+ map.put(field, value);
+ }
+ });
+ BeanUtils.populate(to, map);
+ return to;
+ }
+
+ public static Map toMap(Object bean) {
+ Map map = new HashMap<>();
+ new BeanMap(bean).forEach((key, value) -> {
+ if (key.equals("class")) {
+ return;
+ }
+ String field = key.toString();
+ map.put(field, value);
+ });
+ return map;
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ReflectUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ReflectUtils.java
new file mode 100755
index 0000000..4455611
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ReflectUtils.java
@@ -0,0 +1,54 @@
+package cc.iotkit.common.utils;
+
+import cn.hutool.core.util.ReflectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import java.lang.reflect.Method;
+
+/**
+ * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ *
+ * @author Lion Li
+ */
+@SuppressWarnings("rawtypes")
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReflectUtils extends ReflectUtil {
+
+ private static final String SETTER_PREFIX = "set";
+
+ private static final String GETTER_PREFIX = "get";
+
+ /**
+ * 调用Getter方法.
+ * 支持多级,如:对象名.对象名.方法
+ */
+ @SuppressWarnings("unchecked")
+ public static E invokeGetter(Object obj, String propertyName) {
+ Object object = obj;
+ for (String name : StringUtils.split(propertyName, ".")) {
+ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+ object = invoke(object, getterMethodName);
+ }
+ return (E) object;
+ }
+
+ /**
+ * 调用Setter方法, 仅匹配方法名。
+ * 支持多级,如:对象名.对象名.方法
+ */
+ public static void invokeSetter(Object obj, String propertyName, E value) {
+ Object object = obj;
+ String[] names = StringUtils.split(propertyName, ".");
+ for (int i = 0; i < names.length; i++) {
+ if (i < names.length - 1) {
+ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+ object = invoke(object, getterMethodName);
+ } else {
+ String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+ Method method = getMethodByName(object.getClass(), setterMethodName);
+ invoke(object, method, value);
+ }
+ }
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/SpringUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/SpringUtils.java
new file mode 100755
index 0000000..80c1081
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/SpringUtils.java
@@ -0,0 +1,62 @@
+package cc.iotkit.common.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring工具类
+ *
+ * @author Lion Li
+ */
+@Component
+public final class SpringUtils extends SpringUtil {
+
+ /**
+ * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+ */
+ public static boolean containsBean(String name) {
+ return getBeanFactory().containsBean(name);
+ }
+
+ /**
+ * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
+ * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+ */
+ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().isSingleton(name);
+ }
+
+ /**
+ * @return Class 注册对象的类型
+ */
+ public static Class> getType(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getType(name);
+ }
+
+ /**
+ * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+ */
+ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+ return getBeanFactory().getAliases(name);
+ }
+
+ /**
+ * 获取aop代理对象
+ */
+ @SuppressWarnings("unchecked")
+ public static T getAopProxy(T invoker) {
+ return (T) AopContext.currentProxy();
+ }
+
+
+ /**
+ * 获取spring上下文
+ */
+ public static ApplicationContext context() {
+ return getApplicationContext();
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/StreamUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/StreamUtils.java
new file mode 100755
index 0000000..eeab4e8
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/StreamUtils.java
@@ -0,0 +1,254 @@
+package cc.iotkit.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 流工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+ /**
+ * 将collection过滤
+ *
+ * @param collection 需要转化的集合
+ * @param function 过滤方法
+ * @return 过滤后的list
+ */
+ public static List filter(Collection collection, Predicate function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ return collection.stream().filter(function).collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection拼接
+ *
+ * @param collection 需要转化的集合
+ * @param function 拼接方法
+ * @return 拼接后的list
+ */
+ public static String join(Collection collection, Function function) {
+ return join(collection, function, StringUtils.SEPARATOR);
+ }
+
+ /**
+ * 将collection拼接
+ *
+ * @param collection 需要转化的集合
+ * @param function 拼接方法
+ * @param delimiter 拼接符
+ * @return 拼接后的list
+ */
+ public static String join(Collection collection, Function function, CharSequence delimiter) {
+ if (CollUtil.isEmpty(collection)) {
+ return StringUtils.EMPTY;
+ }
+ return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+ }
+
+ /**
+ * 将collection排序
+ *
+ * @param collection 需要转化的集合
+ * @param comparing 排序方法
+ * @return 排序后的list
+ */
+ public static List sorted(Collection collection, Comparator comparing) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ return collection.stream().sorted(comparing).collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection转化为类型不变的map
+ * {@code Collection ----> Map}
+ *
+ * @param collection 需要转化的集合
+ * @param key V类型转化为K类型的lambda方法
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @return 转化后的map
+ */
+ public static Map toIdentityMap(Collection collection, Function key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+ }
+
+ /**
+ * 将Collection转化为map(value类型与collection的泛型不同)
+ * {@code Collection -----> Map }
+ *
+ * @param collection 需要转化的集合
+ * @param key E类型转化为K类型的lambda方法
+ * @param value E类型转化为V类型的lambda方法
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @param map中的value类型
+ * @return 转化后的map
+ */
+ public static Map toMap(Collection collection, Function key, Function value) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l));
+ }
+
+ /**
+ * 将collection按照规则(比如有相同的班级id)分类成map
+ * {@code Collection -------> Map> }
+ *
+ * @param collection 需要分类的集合
+ * @param key 分类的规则
+ * @param collection中的泛型
+ * @param map中的key类型
+ * @return 分类后的map
+ */
+ public static Map> groupByKey(Collection collection, Function key) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream()
+ .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+ }
+
+ /**
+ * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map>> }
+ *
+ * @param collection 需要分类的集合
+ * @param key1 第一个分类的规则
+ * @param key2 第二个分类的规则
+ * @param 集合元素类型
+ * @param 第一个map中的key类型
+ * @param 第二个map中的key类型
+ * @return 分类后的map
+ */
+ public static Map>> groupBy2Key(Collection collection, Function key1, Function key2) {
+ if (CollUtil.isEmpty(collection)) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream()
+ .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+ }
+
+ /**
+ * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map
+ * {@code Collection ---> Map> }
+ *
+ * @param collection 需要分类的集合
+ * @param key1 第一个分类的规则
+ * @param key2 第二个分类的规则
+ * @param 第一个map中的key类型
+ * @param 第二个map中的key类型
+ * @param collection中的泛型
+ * @return 分类后的map
+ */
+ public static Map> group2Map(Collection collection, Function key1, Function key2) {
+ if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
+ return MapUtil.newHashMap();
+ }
+ return collection
+ .stream()
+ .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+ }
+
+ /**
+ * 将collection转化为List集合,但是两者的泛型不同
+ * {@code Collection ------> List }
+ *
+ * @param collection 需要转化的集合
+ * @param function collection中的泛型转化为list泛型的lambda表达式
+ * @param collection中的泛型
+ * @param List中的泛型
+ * @return 转化后的list
+ */
+ public static List toList(Collection collection, Function function) {
+ if (CollUtil.isEmpty(collection)) {
+ return CollUtil.newArrayList();
+ }
+ return collection
+ .stream()
+ .map(function)
+ .filter(Objects::nonNull)
+ // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 将collection转化为Set集合,但是两者的泛型不同
+ * {@code Collection ------> Set }
+ *
+ * @param collection 需要转化的集合
+ * @param function collection中的泛型转化为set泛型的lambda表达式
+ * @param collection中的泛型
+ * @param Set中的泛型
+ * @return 转化后的Set
+ */
+ public static Set toSet(Collection collection, Function function) {
+ if (CollUtil.isEmpty(collection) || function == null) {
+ return CollUtil.newHashSet();
+ }
+ return collection
+ .stream()
+ .map(function)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ }
+
+
+ /**
+ * 合并两个相同key类型的map
+ *
+ * @param map1 第一个需要合并的 map
+ * @param map2 第二个需要合并的 map
+ * @param merge 合并的lambda,将key value1 value2合并成最终的类型,注意value可能为空的情况
+ * @param map中的key类型
+ * @param 第一个 map的value类型
+ * @param 第二个 map的value类型
+ * @param 最终map的value类型
+ * @return 合并后的map
+ */
+ public static Map merge(Map map1, Map map2, BiFunction merge) {
+ if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
+ return MapUtil.newHashMap();
+ } else if (MapUtil.isEmpty(map1)) {
+ map1 = MapUtil.newHashMap();
+ } else if (MapUtil.isEmpty(map2)) {
+ map2 = MapUtil.newHashMap();
+ }
+ Set key = new HashSet<>();
+ key.addAll(map1.keySet());
+ key.addAll(map2.keySet());
+ Map map = new HashMap<>();
+ for (K t : key) {
+ X x = map1.get(t);
+ Y y = map2.get(t);
+ V z = merge.apply(x, y);
+ if (z != null) {
+ map.put(t, z);
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/StringUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/StringUtils.java
new file mode 100755
index 0000000..96b4b5c
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/StringUtils.java
@@ -0,0 +1,321 @@
+package cc.iotkit.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 字符串工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+ public static final String SEPARATOR = ",";
+
+ /**
+ * 获取参数不为空值
+ *
+ * @param str defaultValue 要判断的value
+ * @return value 返回值
+ */
+ public static String blankToDefault(String str, String defaultValue) {
+ return StrUtil.blankToDefault(str, defaultValue);
+ }
+
+ /**
+ * * 判断一个字符串是否为空串
+ *
+ * @param str String
+ * @return true:为空 false:非空
+ */
+ public static boolean isEmpty(String str) {
+ return StrUtil.isEmpty(str);
+ }
+
+ /**
+ * * 判断一个字符串是否为非空串
+ *
+ * @param str String
+ * @return true:非空串 false:空串
+ */
+ public static boolean isNotEmpty(String str) {
+ return !isEmpty(str);
+ }
+
+ /**
+ * 去空格
+ */
+ public static String trim(String str) {
+ return StrUtil.trim(str);
+ }
+
+ /**
+ * 截取字符串
+ *
+ * @param str 字符串
+ * @param start 开始
+ * @return 结果
+ */
+ public static String substring(final String str, int start) {
+ return substring(str, start, str.length());
+ }
+
+ /**
+ * 截取字符串
+ *
+ * @param str 字符串
+ * @param start 开始
+ * @param end 结束
+ * @return 结果
+ */
+ public static String substring(final String str, int start, int end) {
+ return StrUtil.sub(str, start, end);
+ }
+
+ /**
+ * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is {} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ *
+ * @param template 文本模板,被替换的部分用 {} 表示
+ * @param params 参数值
+ * @return 格式化后的文本
+ */
+ public static String format(String template, Object... params) {
+ return StrUtil.format(template, params);
+ }
+
+ /**
+ * 是否为http(s)://开头
+ *
+ * @param link 链接
+ * @return 结果
+ */
+ public static boolean ishttp(String link) {
+ return Validator.isUrl(link);
+ }
+
+ /**
+ * 字符串转set
+ *
+ * @param str 字符串
+ * @param sep 分隔符
+ * @return set集合
+ */
+ public static Set str2Set(String str, String sep) {
+ return new HashSet<>(str2List(str, sep, true, false));
+ }
+
+ /**
+ * 字符串转list
+ *
+ * @param str 字符串
+ * @param sep 分隔符
+ * @param filterBlank 过滤纯空白
+ * @param trim 去掉首尾空白
+ * @return list集合
+ */
+ public static List str2List(String str, String sep, boolean filterBlank, boolean trim) {
+ List list = new ArrayList<>();
+ if (isEmpty(str)) {
+ return list;
+ }
+
+ // 过滤空白字符串
+ if (filterBlank && isBlank(str)) {
+ return list;
+ }
+ String[] split = str.split(sep);
+ for (String string : split) {
+ if (filterBlank && isBlank(string)) {
+ continue;
+ }
+ if (trim) {
+ string = trim(string);
+ }
+ list.add(string);
+ }
+
+ return list;
+ }
+
+ /**
+ * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
+ *
+ * @param cs 指定字符串
+ * @param searchCharSequences 需要检查的字符串数组
+ * @return 是否包含任意一个字符串
+ */
+ public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) {
+ return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences);
+ }
+
+ /**
+ * 驼峰转下划线命名
+ */
+ public static String toUnderScoreCase(String str) {
+ return StrUtil.toUnderlineCase(str);
+ }
+
+ /**
+ * 是否包含字符串
+ *
+ * @param str 验证字符串
+ * @param strs 字符串组
+ * @return 包含返回true
+ */
+ public static boolean inStringIgnoreCase(String str, String... strs) {
+ return StrUtil.equalsAnyIgnoreCase(str, strs);
+ }
+
+ /**
+ * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
+ *
+ * @param name 转换前的下划线大写方式命名的字符串
+ * @return 转换后的驼峰式命名的字符串
+ */
+ public static String convertToCamelCase(String name) {
+ return StrUtil.upperFirst(StrUtil.toCamelCase(name));
+ }
+
+ /**
+ * 驼峰式命名法 例如:user_name->userName
+ */
+ public static String toCamelCase(String s) {
+ return StrUtil.toCamelCase(s);
+ }
+
+ /**
+ * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
+ *
+ * @param str 指定字符串
+ * @param strs 需要检查的字符串数组
+ * @return 是否匹配
+ */
+ public static boolean matches(String str, List strs) {
+ if (isEmpty(str) || CollUtil.isEmpty(strs)) {
+ return false;
+ }
+ for (String pattern : strs) {
+ if (isMatch(pattern, str)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断url是否与规则配置:
+ * ? 表示单个字符;
+ * * 表示一层路径内的任意字符串,不可跨层级;
+ * ** 表示任意层路径;
+ *
+ * @param pattern 匹配规则
+ * @param url 需要匹配的url
+ */
+ public static boolean isMatch(String pattern, String url) {
+ AntPathMatcher matcher = new AntPathMatcher();
+ return matcher.match(pattern, url);
+ }
+
+ /**
+ * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
+ *
+ * @param num 数字对象
+ * @param size 字符串指定长度
+ * @return 返回数字的字符串格式,该字符串为指定长度。
+ */
+ public static String padl(final Number num, final int size) {
+ return padl(num.toString(), size, '0');
+ }
+
+ /**
+ * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
+ *
+ * @param s 原始字符串
+ * @param size 字符串指定长度
+ * @param c 用于补齐的字符
+ * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
+ */
+ public static String padl(final String s, final int size, final char c) {
+ final StringBuilder sb = new StringBuilder(size);
+ if (s != null) {
+ final int len = s.length();
+ if (s.length() <= size) {
+ sb.append(String.valueOf(c).repeat(size - len));
+ sb.append(s);
+ } else {
+ return s.substring(len - size, len);
+ }
+ } else {
+ sb.append(String.valueOf(c).repeat(Math.max(0, size)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 切分字符串(分隔符默认逗号)
+ *
+ * @param str 被切分的字符串
+ * @return 分割后的数据列表
+ */
+ public static List splitList(String str) {
+ return splitTo(str, Convert::toStr);
+ }
+
+ /**
+ * 切分字符串
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符
+ * @return 分割后的数据列表
+ */
+ public static List splitList(String str, String separator) {
+ return splitTo(str, separator, Convert::toStr);
+ }
+
+ /**
+ * 切分字符串自定义转换(分隔符默认逗号)
+ *
+ * @param str 被切分的字符串
+ * @param mapper 自定义转换
+ * @return 分割后的数据列表
+ */
+ public static List splitTo(String str, Function super Object, T> mapper) {
+ return splitTo(str, SEPARATOR, mapper);
+ }
+
+ /**
+ * 切分字符串自定义转换
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符
+ * @param mapper 自定义转换
+ * @return 分割后的数据列表
+ */
+ public static List splitTo(String str, String separator, Function super Object, T> mapper) {
+ if (isBlank(str)) {
+ return new ArrayList<>(0);
+ }
+ return StrUtil.split(str, separator)
+ .stream()
+ .filter(Objects::nonNull)
+ .map(mapper)
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ThreadUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ThreadUtil.java
new file mode 100755
index 0000000..20ec138
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ThreadUtil.java
@@ -0,0 +1,34 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.common.utils;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+public class ThreadUtil {
+
+ public static ScheduledThreadPoolExecutor newScheduled(int poolSize, String threadName) {
+ return new ScheduledThreadPoolExecutor(poolSize, (Runnable r) -> {
+ SecurityManager s = System.getSecurityManager();
+ ThreadGroup group = (s != null) ? s.getThreadGroup() :
+ Thread.currentThread().getThreadGroup();
+ Thread t = new Thread(group, r,
+ threadName,
+ 0);
+ if (t.isDaemon()) {
+ t.setDaemon(false);
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ });
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/TreeBuildUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/TreeBuildUtils.java
new file mode 100755
index 0000000..3be2d5f
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/TreeBuildUtils.java
@@ -0,0 +1,35 @@
+package cc.iotkit.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNodeConfig;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 扩展 hutool TreeUtil 封装系统树构建
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TreeBuildUtils extends TreeUtil {
+
+ /**
+ * 根据前端定制差异化字段
+ */
+ public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
+
+ public static List> build(List list, NodeParser nodeParser) {
+ if (CollUtil.isEmpty(list)) {
+ return Collections.emptyList();
+ }
+ K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
+ return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/UniqueIdUtil.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/UniqueIdUtil.java
new file mode 100755
index 0000000..1668fb1
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/UniqueIdUtil.java
@@ -0,0 +1,37 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+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;
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ValidatorUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ValidatorUtils.java
new file mode 100755
index 0000000..2e7a370
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ValidatorUtils.java
@@ -0,0 +1,28 @@
+package cc.iotkit.common.utils;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Set;
+
+/**
+ * Validator 校验框架工具
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+
+ private static final Validator VALID = SpringUtils.getBean(Validator.class);
+
+ public static void validate(T object, Class>... groups) {
+ Set> validate = VALID.validate(object, groups);
+ if (!validate.isEmpty()) {
+ throw new ConstraintViolationException("参数校验异常", validate);
+ }
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/file/FileUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/file/FileUtils.java
new file mode 100755
index 0000000..025fb11
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/file/FileUtils.java
@@ -0,0 +1,16 @@
+package cc.iotkit.common.utils.file;
+
+import cn.hutool.core.io.FileUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件处理工具类
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FileUtils extends FileUtil {
+
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/file/MimeTypeUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/file/MimeTypeUtils.java
new file mode 100755
index 0000000..2f24e72
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/file/MimeTypeUtils.java
@@ -0,0 +1,40 @@
+package cc.iotkit.common.utils.file;
+
+/**
+ * 媒体类型工具类
+ *
+ * @author ruoyi
+ */
+public class MimeTypeUtils {
+ public static final String IMAGE_PNG = "image/png";
+
+ public static final String IMAGE_JPG = "image/jpg";
+
+ public static final String IMAGE_JPEG = "image/jpeg";
+
+ public static final String IMAGE_BMP = "image/bmp";
+
+ public static final String IMAGE_GIF = "image/gif";
+
+ public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
+
+ public static final String[] FLASH_EXTENSION = {"swf", "flv"};
+
+ public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+ "asf", "rm", "rmvb"};
+
+ public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
+
+ public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+ // 图片
+ "bmp", "gif", "jpg", "jpeg", "png",
+ // word excel powerpoint
+ "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+ // 压缩文件
+ "rar", "zip", "gz", "bz2",
+ // 视频格式
+ "mp4", "avi", "rmvb",
+ // pdf
+ "pdf"};
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ip/AddressUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ip/AddressUtils.java
new file mode 100755
index 0000000..dd741dd
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ip/AddressUtils.java
@@ -0,0 +1,33 @@
+package cc.iotkit.common.utils.ip;
+
+import cc.iotkit.common.utils.StringUtils;
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.http.HtmlUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 获取地址类
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class AddressUtils {
+
+ // 未知地址
+ public static final String UNKNOWN = "XX XX";
+
+ public static String getRealAddressByIP(String ip) {
+ if (StringUtils.isBlank(ip)) {
+ return UNKNOWN;
+ }
+ // 内网不查询
+ ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
+ if (NetUtil.isInnerIP(ip)) {
+ return "内网IP";
+ }
+ return RegionUtils.getCityInfo(ip);
+ }
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/utils/ip/RegionUtils.java b/iot-common-core/src/main/java/cc/iotkit/common/utils/ip/RegionUtils.java
new file mode 100755
index 0000000..32abcd2
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/utils/ip/RegionUtils.java
@@ -0,0 +1,67 @@
+package cc.iotkit.common.utils.ip;
+
+import cc.iotkit.common.exception.BizException;
+import cc.iotkit.common.utils.file.FileUtils;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+
+import java.io.File;
+
+/**
+ * 根据ip地址定位工具类,离线方式
+ * 参考地址:集成 ip2region 实现离线IP地址定位库
+ *
+ * @author lishuyan
+ */
+@Slf4j
+public class RegionUtils {
+
+ private static final Searcher SEARCHER;
+
+ static {
+ String fileName = "/ip2region.xdb";
+ File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
+ if (!FileUtils.exist(existFile)) {
+ ClassPathResource fileStream = new ClassPathResource(fileName);
+ if (ObjectUtil.isEmpty(fileStream.getStream())) {
+ throw new BizException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
+ }
+ FileUtils.writeFromStream(fileStream.getStream(), existFile);
+ }
+
+ String dbPath = existFile.getPath();
+
+ // 1、从 dbPath 加载整个 xdb 到内存。
+ byte[] cBuff;
+ try {
+ cBuff = Searcher.loadContentFromFile(dbPath);
+ } catch (Exception e) {
+ throw new BizException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
+ }
+ // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
+ try {
+ SEARCHER = Searcher.newWithBuffer(cBuff);
+ } catch (Exception e) {
+ throw new BizException("RegionUtils初始化失败,原因:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 根据IP地址离线获取城市
+ */
+ public static String getCityInfo(String ip) {
+ try {
+ ip = ip.trim();
+ // 3、执行查询
+ String region = SEARCHER.search(ip);
+ return region.replace("0|", "").replace("|0", "");
+ } catch (Exception e) {
+ log.error("IP地址离线获取城市异常 {}", ip);
+ return "未知";
+ }
+ }
+
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/validate/AddGroup.java b/iot-common-core/src/main/java/cc/iotkit/common/validate/AddGroup.java
new file mode 100755
index 0000000..1537625
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/validate/AddGroup.java
@@ -0,0 +1,9 @@
+package cc.iotkit.common.validate;
+
+/**
+ * 校验分组 add
+ *
+ * @author Lion Li
+ */
+public interface AddGroup {
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/validate/DeleteGroup.java b/iot-common-core/src/main/java/cc/iotkit/common/validate/DeleteGroup.java
new file mode 100755
index 0000000..9f4b9c4
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/validate/DeleteGroup.java
@@ -0,0 +1,9 @@
+package cc.iotkit.common.validate;
+
+/**
+ * 校验分组 delete
+ *
+ * @author Lion Li
+ */
+public interface DeleteGroup {
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/validate/EditGroup.java b/iot-common-core/src/main/java/cc/iotkit/common/validate/EditGroup.java
new file mode 100755
index 0000000..8fe56f4
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/validate/EditGroup.java
@@ -0,0 +1,9 @@
+package cc.iotkit.common.validate;
+
+/**
+ * 校验分组 edit
+ *
+ * @author Lion Li
+ */
+public interface EditGroup {
+}
diff --git a/iot-common-core/src/main/java/cc/iotkit/common/validate/QueryGroup.java b/iot-common-core/src/main/java/cc/iotkit/common/validate/QueryGroup.java
new file mode 100755
index 0000000..f3f1d09
--- /dev/null
+++ b/iot-common-core/src/main/java/cc/iotkit/common/validate/QueryGroup.java
@@ -0,0 +1,9 @@
+package cc.iotkit.common.validate;
+
+/**
+ * 校验分组 query
+ *
+ * @author Lion Li
+ */
+public interface QueryGroup {
+}
diff --git a/iot-common-doc/pom.xml b/iot-common-doc/pom.xml
new file mode 100755
index 0000000..807471e
--- /dev/null
+++ b/iot-common-doc/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+ iot-iita-core
+ cc.iotkit
+ 1.0.0
+
+ 4.0.0
+
+ iot-common-doc
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.github.xiaoymin
+ knife4j-spring-boot-starter
+
+
+ org.mapstruct
+ mapstruct
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${java.version}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerAutoConfiguration.java b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerAutoConfiguration.java
new file mode 100755
index 0000000..418b628
--- /dev/null
+++ b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerAutoConfiguration.java
@@ -0,0 +1,15 @@
+package cc.iotkit.swagger.config;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @Author: 石恒
+ * @Date: 2023/5/6 22:03
+ * @Description:
+ */
+@Configuration
+@ComponentScan(basePackages = {"cc.iotkit.swagger"})
+public class SwaggerAutoConfiguration {
+
+}
diff --git a/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerConfig.java b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerConfig.java
new file mode 100755
index 0000000..ad57d23
--- /dev/null
+++ b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerConfig.java
@@ -0,0 +1,130 @@
+package cc.iotkit.swagger.config;
+
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+/**
+ * @Author: 石恒
+ * @Date: 2023/5/4 20:12
+ * @Description:
+ */
+@Component
+@EnableSwagger2WebMvc
+
+public class SwaggerConfig {
+
+ @Value("${spring.application.name:Swagger API}")
+ private String applicationName;
+
+ @Bean(value = "defaultApi2")
+ public Docket defaultApi2() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .groupName(applicationName)
+ .enable(true)
+ .apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title(applicationName)
+ .description("Swagger API Doc")
+ .build();
+ }
+
+ // 解决springboot升级到2.6.x之后,knife4j报错
+ @Bean
+ public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
+ return new BeanPostProcessor() {
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+// if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
+ if (bean instanceof WebMvcRequestHandlerProvider ) {
+ customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
+ }
+ return bean;
+ }
+
+ private void customizeSpringfoxHandlerMappings(List mappings) {
+ mappings.removeIf(mapping -> mapping.getPatternParser() != null);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List getHandlerMappings(Object bean) {
+ try {
+ Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
+ field.setAccessible(true);
+ return (List) field.get(bean);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ };
+ }
+
+// /**
+// * 解决springboot升级到2.6.x之后,knife4j报错
+// *
+// * @param webEndpointsSupplier the web endpoints supplier
+// * @param servletEndpointsSupplier the servlet endpoints supplier
+// * @param controllerEndpointsSupplier the controller endpoints supplier
+// * @param endpointMediaTypes the endpoint media types
+// * @param corsProperties the cors properties
+// * @param webEndpointProperties the web endpoint properties
+// * @param environment the environment
+// * @return the web mvc endpoint handler mapping
+// */
+// @Bean
+// public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
+// WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier,
+// ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes,
+// CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties,
+// Environment environment) {
+// List> allEndpoints = new ArrayList<>();
+// Collection webEndpoints = webEndpointsSupplier.getEndpoints();
+// allEndpoints.addAll(webEndpoints);
+// allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
+// allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
+// String basePath = webEndpointProperties.getBasePath();
+// EndpointMapping endpointMapping = new EndpointMapping(basePath);
+// boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties,
+// environment, basePath);
+// return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
+// corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
+// shouldRegisterLinksMapping, null);
+// }
+//
+// /**
+// * shouldRegisterLinksMapping
+// * @param webEndpointProperties webEndpointProperties
+// * @param environment environment
+// * @param basePath /
+// * @return boolean
+// */
+// private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties,
+// Environment environment, String basePath) {
+// return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
+// || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
+// }
+}
diff --git a/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerProperties.java b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerProperties.java
new file mode 100755
index 0000000..2430c99
--- /dev/null
+++ b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerProperties.java
@@ -0,0 +1,60 @@
+package cc.iotkit.swagger.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Author: 石恒
+ * @Date: 2023/5/6 22:26
+ * @Description:
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "swagger")
+public class SwaggerProperties {
+ private Boolean enabled;
+ private String title; //标题
+ private String group; //自定义组名
+ private String description = "在线文档"; //描述
+ private String version = "1.0"; //版本
+ private Contact contact = new Contact(); //联系人
+ private String basePackage = "com.example"; //swagger会解析的包路径
+ private List basePath = new ArrayList<>(); //swagger会解析的url规则
+ private List excludePath = new ArrayList<>();//在basePath基础上需要排除的url规则
+ private Map docket = new LinkedHashMap<>(); //分组文档
+ public String getGroup() {
+ if (group == null || "".equals(group)) {
+ return title;
+ }
+ return group;
+ }
+ @Data
+ public static class DocketInfo {
+ private String title = "在线文档"; //标题
+ private String group = ""; //自定义组名
+ private String description = "在线文档"; //描述
+ private String version = "1.0"; //版本
+ private Contact contact = new Contact(); //联系人
+ private String basePackage = "com.example"; //swagger会解析的包路径
+ private List basePath = new ArrayList<>(); //swagger会解析的url规则
+ private List excludePath = new ArrayList<>();//在basePath基础上需要排除的url
+ public String getGroup() {
+ if (group == null || "".equals(group)) {
+ return title;
+ }
+ return group;
+ }
+ }
+ @Data
+ public static class Contact {
+ private String name = ""; //联系人
+ private String url = ""; //联系人url
+ private String email = ""; //联系人email
+ }
+}
diff --git a/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerWebConfiguration.java b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerWebConfiguration.java
new file mode 100755
index 0000000..a7d3e16
--- /dev/null
+++ b/iot-common-doc/src/main/java/cc/iotkit/swagger/config/SwaggerWebConfiguration.java
@@ -0,0 +1,20 @@
+package cc.iotkit.swagger.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @Author: 石恒
+ * @Date: 2023/5/6 22:03
+ * @Description:
+ */
+@Configuration
+public class SwaggerWebConfiguration implements WebMvcConfigurer {
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("swagger-ui.html", "doc.html").addResourceLocations("classpath:/META-INF/resources/");
+ registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+ }
+}
diff --git a/iot-common-doc/src/main/resources/META-INF/spring.factories b/iot-common-doc/src/main/resources/META-INF/spring.factories
new file mode 100755
index 0000000..7b54cfd
--- /dev/null
+++ b/iot-common-doc/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ cc.iotkit.swagger.config.SwaggerAutoConfiguration,\
+ cc.iotkit.swagger.config.SwaggerWebConfiguration
\ No newline at end of file
diff --git a/iot-common-excel/pom.xml b/iot-common-excel/pom.xml
new file mode 100755
index 0000000..ed700c1
--- /dev/null
+++ b/iot-common-excel/pom.xml
@@ -0,0 +1,48 @@
+
+
+
+ iot-iita-core
+ cc.iotkit
+ 1.0.0
+
+ 4.0.0
+
+ iot-common-excel
+
+
+
+
+ cc.iotkit
+ iot-common-web
+
+
+
+ cc.iotkit
+ iot-common-core
+
+
+
+
+
+ com.alibaba
+ easyexcel
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${java.version}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/CellMerge.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/CellMerge.java
new file mode 100755
index 0000000..91d6a47
--- /dev/null
+++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/CellMerge.java
@@ -0,0 +1,25 @@
+package cc.iotkit.common.excel.annotation;
+
+
+import cc.iotkit.common.excel.core.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 列单元格合并(合并列相同项)
+ *
+ * 需搭配 {@link CellMergeStrategy} 策略使用
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+ /**
+ * col index
+ */
+ int index() default -1;
+
+}
diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/ExcelDictFormat.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/ExcelDictFormat.java
new file mode 100755
index 0000000..3c69551
--- /dev/null
+++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/ExcelDictFormat.java
@@ -0,0 +1,33 @@
+package cc.iotkit.common.excel.annotation;
+
+
+import cc.iotkit.common.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 字典格式化
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+ /**
+ * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+ */
+ String dictType() default "";
+
+ /**
+ * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+ */
+ String readConverterExp() default "";
+
+ /**
+ * 分隔符,读取字符串组内容
+ */
+ String separator() default StringUtils.SEPARATOR;
+
+}
diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/ExcelEnumFormat.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/ExcelEnumFormat.java
new file mode 100755
index 0000000..5a39c1a
--- /dev/null
+++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/annotation/ExcelEnumFormat.java
@@ -0,0 +1,30 @@
+package cc.iotkit.common.excel.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 枚举格式化
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+ /**
+ * 字典枚举类型
+ */
+ Class extends Enum>> enumClass();
+
+ /**
+ * 字典枚举类中对应的code属性名称,默认为code
+ */
+ String codeField() default "code";
+
+ /**
+ * 字典枚举类中对应的text属性名称,默认为text
+ */
+ String textField() default "text";
+
+}
diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelBigNumberConvert.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelBigNumberConvert.java
new file mode 100755
index 0000000..15d47df
--- /dev/null
+++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelBigNumberConvert.java
@@ -0,0 +1,52 @@
+package cc.iotkit.common.excel.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 大数值转换
+ * Excel 数值长度位15位 大于15位的数值转换位字符串
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter {
+
+ @Override
+ public Class supportJavaTypeKey() {
+ return Long.class;
+ }
+
+ @Override
+ public CellDataTypeEnum supportExcelTypeKey() {
+ return CellDataTypeEnum.STRING;
+ }
+
+ @Override
+ public Long convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+ return Convert.toLong(cellData.getData());
+ }
+
+ @Override
+ public WriteCellData