diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md old mode 100644 new mode 100755 diff --git a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 5d947ca..4e2ac05 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,33 @@ -# Build and Release Folders -bin-debug/ -bin-release/ -[Oo]bj/ -[Bb]in/ +# Compiled class file +*.class -# Other files and folders -.settings/ +# Log file +log +*.log -# Executables -*.swf -*.air -*.ipa -*.apk +# BlueJ files +*.ctxt -# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` -# should NOT be excluded as they contain compiler settings and other important -# information for Eclipse / Flash Builder. +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +.idea +target +*.iml +data/elasticsearch +.init +*.db +.flattened-pom.xml + +.DS_Store +dependency-reduced-pom.xml diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.en.md b/README.en.md deleted file mode 100644 index d248f30..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# iot-iita-core - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 7e7f2c4..1f4cdd7 --- a/README.md +++ b/README.md @@ -1,39 +1,6 @@ # iot-iita-core #### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +[iot-iita](https://gitee.com/open-iita/iotkit-parent) 中使用的核心库 -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +编译iot-iita时请先install本仓库。 diff --git a/iot-common-core/pom.xml b/iot-common-core/pom.xml new file mode 100755 index 0000000..38d2d2d --- /dev/null +++ b/iot-common-core/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + iot-iita-core + cc.iotkit + 1.0.0 + + + iot-common-core + + + 此模块为通用业务逻辑或工具类 + + + + + commons-codec + commons-codec + + + + commons-io + commons-io + + + + + com.fasterxml.jackson.core + jackson-core + + + + com.fasterxml.jackson.core + jackson-databind + + + + commons-beanutils + commons-beanutils + + + + + org.springframework + spring-context-support + + + + + org.apache.commons + commons-lang3 + + + + cn.hutool + hutool-core + + + + cn.hutool + hutool-http + + + + cn.hutool + hutool-extra + + + + io.github.linpeilie + mapstruct-plus-spring-boot-starter + + + + jakarta.validation + jakarta.validation-api + + + + org.slf4j + slf4j-api + + + + org.lionsoul + ip2region + + + + org.projectlombok + lombok + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/BaseDto.java b/iot-common-core/src/main/java/cc/iotkit/common/api/BaseDto.java new file mode 100755 index 0000000..33925e1 --- /dev/null +++ b/iot-common-core/src/main/java/cc/iotkit/common/api/BaseDto.java @@ -0,0 +1,45 @@ +package cc.iotkit.common.api; + +import cc.iotkit.common.utils.MapstructUtils; +import lombok.Data; + +import java.util.Date; + +/** + * Entity基类 + * + * @author Lion Li + */ +@Data +public class BaseDto { + + /** + * 创建部门 + */ + + private Long createDept; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + private Date updateTime; + + public T to(Class tClass) { + return MapstructUtils.convert(this, tClass); + } +} diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/PageRequest.java b/iot-common-core/src/main/java/cc/iotkit/common/api/PageRequest.java new file mode 100755 index 0000000..266805b --- /dev/null +++ b/iot-common-core/src/main/java/cc/iotkit/common/api/PageRequest.java @@ -0,0 +1,92 @@ +package cc.iotkit.common.api; + +import cc.iotkit.common.utils.MapstructUtils; +import cn.hutool.core.util.IdUtil; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +import java.util.Map; + +import lombok.*; + +import java.io.Serializable; +import java.util.Objects; + +/** + * @author: Longjun.Tu + * @description: + * @date:created in 2023/5/10 23:15 + * @modificed by: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PageRequest extends Request implements Serializable { + + /** + * 分页大小 + */ + @Min(1) + @NotNull + private Integer pageSize; + + /** + * 当前页数 + */ + @Min(1) + @Max(100) + @NotNull + private Integer pageNum; + + /** + * 排序 key为排序字段名 value为排序方向方向desc或者asc + */ + private Map sortMap; + + /** + * 当前记录起始索引 默认值 + */ + public static final int DEFAULT_PAGE_NUM = 1; + + /** + * 每页显示记录数 默认值 + */ + public static final int DEFAULT_PAGE_SIZE = 20; + + + public static PageRequest of(T data) { + PageRequest pageRequest = new PageRequest<>(); + pageRequest.setPageSize(DEFAULT_PAGE_SIZE); + pageRequest.setPageNum(DEFAULT_PAGE_NUM); + pageRequest.setData(data); + pageRequest.setRequestId(IdUtil.simpleUUID()); + return pageRequest; + } + + public PageRequest to(Class dtoClass) { + PageRequest pageRequest = new PageRequest<>(); + if (Objects.nonNull(getData())) { + pageRequest.setData(MapstructUtils.convert(getData(), dtoClass)); + } + pageRequest.setPageNum(this.getPageNum()); + pageRequest.setPageSize(this.getPageSize()); + pageRequest.setRequestId(this.getRequestId()); + pageRequest.setSortMap(this.getSortMap()); + return pageRequest; + } + + public Integer getPageSize() { + return pageSize == null ? DEFAULT_PAGE_SIZE : pageSize; + } + + public Integer getPageNum() { + return pageNum == null ? DEFAULT_PAGE_NUM : pageNum; + } + + public Integer getOffset() { + return (getPageNum() - 1) * getPageSize(); + } +} diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/PageRequestEmpty.java b/iot-common-core/src/main/java/cc/iotkit/common/api/PageRequestEmpty.java new file mode 100755 index 0000000..44aaf6e --- /dev/null +++ b/iot-common-core/src/main/java/cc/iotkit/common/api/PageRequestEmpty.java @@ -0,0 +1,15 @@ +package cc.iotkit.common.api; + +import lombok.Data; + +/** + * @author: Longjun.Tu + * @description: + * @date:created in 2023/5/10 23:16 + * @modificed by: + */ +@Data +public class PageRequestEmpty { + private Integer pageNum = 1; + private Integer pageSize = 20; +} diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/Paging.java b/iot-common-core/src/main/java/cc/iotkit/common/api/Paging.java new file mode 100755 index 0000000..77ca1bd --- /dev/null +++ b/iot-common-core/src/main/java/cc/iotkit/common/api/Paging.java @@ -0,0 +1,31 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.common.api; + +import cc.iotkit.common.utils.MapstructUtils; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Paging { + + private long total; + + private List rows; + + public Paging to(Class voClass) { + return MapstructUtils.convert(this, voClass); + } +} diff --git a/iot-common-core/src/main/java/cc/iotkit/common/api/Request.java b/iot-common-core/src/main/java/cc/iotkit/common/api/Request.java new file mode 100755 index 0000000..ee5c746 --- /dev/null +++ b/iot-common-core/src/main/java/cc/iotkit/common/api/Request.java @@ -0,0 +1,29 @@ +package cc.iotkit.common.api; + +import cn.hutool.core.util.IdUtil; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author: Longjun.Tu + * @description: + * @date:created in 2023/5/10 23:14 + * @modificed by: + */ +@Data +public class Request extends RequestEmpty implements Serializable { + + @Valid + @NotNull + private T data; + + public static Request of(T data) { + Request 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 mapper) { + return splitTo(str, SEPARATOR, mapper); + } + + /** + * 切分字符串自定义转换 + * + * @param str 被切分的字符串 + * @param separator 分隔符 + * @param mapper 自定义转换 + * @return 分割后的数据列表 + */ + public static List splitTo(String str, String separator, Function 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} + ${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} + ${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> 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 convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNotNull(object)) { + String str = Convert.toStr(object); + if (str.length() > 15) { + return new WriteCellData<>(str); + } + } + WriteCellData cellData = new WriteCellData<>(new BigDecimal(object)); + cellData.setType(CellDataTypeEnum.NUMBER); + return cellData; + } + +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelDictConvert.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelDictConvert.java new file mode 100755 index 0000000..62e9673 --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelDictConvert.java @@ -0,0 +1,73 @@ +package cc.iotkit.common.excel.convert; + +import cc.iotkit.common.excel.annotation.ExcelDictFormat; +import cc.iotkit.common.excel.utils.ExcelUtil; +import cc.iotkit.common.service.DictService; +import cc.iotkit.common.utils.SpringUtils; +import cc.iotkit.common.utils.StringUtils; +import cn.hutool.core.annotation.AnnotationUtil; +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.lang.reflect.Field; + +/** + * 字典格式化转换处理 + * + * @author Lion Li + */ +@Slf4j +public class ExcelDictConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Object.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return null; + } + + @Override + public Object convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + ExcelDictFormat anno = getAnnotation(contentProperty.getField()); + String type = anno.dictType(); + String label = cellData.getStringValue(); + String value; + if (StringUtils.isBlank(type)) { + value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator()); + } else { + value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator()); + } + return Convert.convert(contentProperty.getField().getType(), value); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNull(object)) { + return new WriteCellData<>(""); + } + ExcelDictFormat anno = getAnnotation(contentProperty.getField()); + String type = anno.dictType(); + String value = Convert.toStr(object); + String label; + if (StringUtils.isBlank(type)) { + label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator()); + } else { + label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator()); + } + return new WriteCellData<>(label); + } + + private ExcelDictFormat getAnnotation(Field field) { + return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class); + } +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelEnumConvert.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelEnumConvert.java new file mode 100755 index 0000000..766320c --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/convert/ExcelEnumConvert.java @@ -0,0 +1,75 @@ +package cc.iotkit.common.excel.convert; + +import cc.iotkit.common.excel.annotation.ExcelEnumFormat; +import cc.iotkit.common.utils.ReflectUtils; +import cn.hutool.core.annotation.AnnotationUtil; +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.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * 枚举格式化转换处理 + * + * @author Liang + */ +@Slf4j +public class ExcelEnumConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + return Object.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return null; + } + + @Override + public Object convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + Object codeValue = cellData.getData(); + // 如果是空值 + if (ObjectUtil.isNull(codeValue)) { + return null; + } + Map enumValueMap = beforeConvert(contentProperty); + String textValue = enumValueMap.get(codeValue); + return Convert.convert(contentProperty.getField().getType(), textValue); + } + + @Override + public WriteCellData convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + if (ObjectUtil.isNull(object)) { + return new WriteCellData<>(""); + } + Map enumValueMap = beforeConvert(contentProperty); + String value = Convert.toStr(enumValueMap.get(object), ""); + return new WriteCellData<>(value); + } + + private Map beforeConvert(ExcelContentProperty contentProperty) { + ExcelEnumFormat anno = getAnnotation(contentProperty.getField()); + Map enumValueMap = new HashMap<>(); + Enum[] enumConstants = anno.enumClass().getEnumConstants(); + for (Enum enumConstant : enumConstants) { + Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField()); + String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField()); + enumValueMap.put(codeValue, textValue); + } + return enumValueMap; + } + + private ExcelEnumFormat getAnnotation(Field field) { + return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class); + } +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/CellMergeStrategy.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/CellMergeStrategy.java new file mode 100755 index 0000000..392a9de --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/CellMergeStrategy.java @@ -0,0 +1,118 @@ +package cc.iotkit.common.excel.core; + +import cc.iotkit.common.excel.annotation.CellMerge; +import cc.iotkit.common.utils.ReflectUtils; +import cn.hutool.core.collection.CollUtil; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.merge.AbstractMergeStrategy; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddress; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 列值重复合并策略 + * + * @author Lion Li + */ +@Slf4j +public class CellMergeStrategy extends AbstractMergeStrategy { + + private final List list; + private final boolean hasTitle; + private int rowIndex; + + public CellMergeStrategy(List list, boolean hasTitle) { + this.list = list; + this.hasTitle = hasTitle; + // 行合并开始下标 + this.rowIndex = hasTitle ? 1 : 0; + } + + @Override + protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + List cellList = handle(list, hasTitle); + // the judge is necessary + if (CollUtil.isNotEmpty(cellList) && cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) { + for (CellRangeAddress item : cellList) { + sheet.addMergedRegion(item); + } + } + } + + @SneakyThrows + private List handle(List list, boolean hasTitle) { + List cellList = new ArrayList<>(); + if (CollUtil.isEmpty(list)) { + return cellList; + } + Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName())); + + // 有注解的字段 + List mergeFields = new ArrayList<>(); + List mergeFieldsIndex = new ArrayList<>(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (field.isAnnotationPresent(CellMerge.class)) { + CellMerge cm = field.getAnnotation(CellMerge.class); + mergeFields.add(field); + mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index()); + if (hasTitle) { + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + rowIndex = Math.max(rowIndex, property.value().length); + } + } + } + + Map map = new HashMap<>(); + // 生成两两合并单元格 + for (int i = 0; i < list.size(); i++) { + for (int j = 0; j < mergeFields.size(); j++) { + Field field = mergeFields.get(j); + Object val = ReflectUtils.invokeGetter(list.get(i), field.getName()); + + int colNum = mergeFieldsIndex.get(j); + if (!map.containsKey(field)) { + map.put(field, new RepeatCell(val, i)); + } else { + RepeatCell repeatCell = map.get(field); + Object cellValue = repeatCell.getValue(); + if (cellValue == null || "".equals(cellValue)) { + // 空值跳过不合并 + continue; + } + if (!cellValue.equals(val)) { + if (i - repeatCell.getCurrent() > 1) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum)); + } + map.put(field, new RepeatCell(val, i)); + } else if ((i == list.size() - 1) && (i > repeatCell.getCurrent())) { + cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum)); + + } + } + } + } + return cellList; + } + + @Data + @AllArgsConstructor + static class RepeatCell { + + private Object value; + + private int current; + + } +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/DefaultExcelListener.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/DefaultExcelListener.java new file mode 100755 index 0000000..8c93728 --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/DefaultExcelListener.java @@ -0,0 +1,106 @@ +package cc.iotkit.common.excel.core; + +import cc.iotkit.common.utils.JsonUtils; +import cc.iotkit.common.utils.StreamUtils; +import cc.iotkit.common.utils.ValidatorUtils; +import cn.hutool.core.text.CharSequenceUtil; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.exception.ExcelAnalysisException; +import com.alibaba.excel.exception.ExcelDataConvertException; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.Set; + +/** + * Excel 导入监听 + * + * @author Yjoioooo + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor +public class DefaultExcelListener extends AnalysisEventListener implements ExcelListener { + + /** + * 是否Validator检验,默认为是 + */ + private Boolean isValidate = Boolean.TRUE; + + /** + * excel 表头数据 + */ + private Map headMap; + + /** + * 导入回执 + */ + private ExcelResult excelResult; + + public DefaultExcelListener(boolean isValidate) { + this.excelResult = new DefaultExcelResult<>(); + this.isValidate = isValidate; + } + + /** + * 处理异常 + * + * @param exception ExcelDataConvertException + * @param context Excel 上下文 + */ + @Override + public void onException(Exception exception, AnalysisContext context) throws Exception { + String errMsg = null; + if (exception instanceof ExcelDataConvertException) { + ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception; + // 如果是某一个单元格的转换异常 能获取到具体行号 + Integer rowIndex = excelDataConvertException.getRowIndex(); + Integer columnIndex = excelDataConvertException.getColumnIndex(); + errMsg = CharSequenceUtil.format("第{}行-第{}列-表头{}: 解析异常
", + rowIndex + 1, columnIndex + 1, headMap.get(columnIndex)); + if (log.isDebugEnabled()) { + log.error(errMsg); + } + } + if (exception instanceof ConstraintViolationException) { + ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception; + Set> constraintViolations = constraintViolationException.getConstraintViolations(); + String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", "); + errMsg = CharSequenceUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg); + if (log.isDebugEnabled()) { + log.error(errMsg); + } + } + excelResult.getErrorList().add(errMsg); + throw new ExcelAnalysisException(errMsg); + } + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + this.headMap = headMap; + log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap)); + } + + @Override + public void invoke(T data, AnalysisContext context) { + if (Boolean.TRUE.equals(isValidate)) { + ValidatorUtils.validate(data); + } + excelResult.getList().add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + log.debug("所有数据解析完成!"); + } + + @Override + public ExcelResult getExcelResult() { + return excelResult; + } + +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/DefaultExcelResult.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/DefaultExcelResult.java new file mode 100755 index 0000000..571d3ba --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/DefaultExcelResult.java @@ -0,0 +1,73 @@ +package cc.iotkit.common.excel.core; + +import cn.hutool.core.text.CharSequenceUtil; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 默认excel返回对象 + * + * @author Yjoioooo + * @author Lion Li + */ +public class DefaultExcelResult implements ExcelResult { + + /** + * 数据对象list + */ + @Setter + private List list; + + /** + * 错误信息列表 + */ + @Setter + private List errorList; + + public DefaultExcelResult() { + this.list = new ArrayList<>(); + this.errorList = new ArrayList<>(); + } + + public DefaultExcelResult(List list, List errorList) { + this.list = list; + this.errorList = errorList; + } + + public DefaultExcelResult(ExcelResult excelResult) { + this.list = excelResult.getList(); + this.errorList = excelResult.getErrorList(); + } + + @Override + public List getList() { + return list; + } + + @Override + public List getErrorList() { + return errorList; + } + + /** + * 获取导入回执 + * + * @return 导入回执 + */ + @Override + public String getAnalysis() { + int successCount = list.size(); + int errorCount = errorList.size(); + if (successCount == 0) { + return "读取失败,未解析到数据"; + } else { + if (errorCount == 0) { + return CharSequenceUtil.format("恭喜您,全部读取成功!共{}条", successCount); + } else { + return ""; + } + } + } +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/ExcelListener.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/ExcelListener.java new file mode 100755 index 0000000..cf12a66 --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/ExcelListener.java @@ -0,0 +1,14 @@ +package cc.iotkit.common.excel.core; + +import com.alibaba.excel.read.listener.ReadListener; + +/** + * Excel 导入监听 + * + * @author Lion Li + */ +public interface ExcelListener extends ReadListener { + + ExcelResult getExcelResult(); + +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/ExcelResult.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/ExcelResult.java new file mode 100755 index 0000000..fc4c772 --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/core/ExcelResult.java @@ -0,0 +1,26 @@ +package cc.iotkit.common.excel.core; + +import java.util.List; + +/** + * excel返回对象 + * + * @author Lion Li + */ +public interface ExcelResult { + + /** + * 对象列表 + */ + List getList(); + + /** + * 错误列表 + */ + List getErrorList(); + + /** + * 导入回执 + */ + String getAnalysis(); +} diff --git a/iot-common-excel/src/main/java/cc/iotkit/common/excel/utils/ExcelUtil.java b/iot-common-excel/src/main/java/cc/iotkit/common/excel/utils/ExcelUtil.java new file mode 100755 index 0000000..a77f06d --- /dev/null +++ b/iot-common-excel/src/main/java/cc/iotkit/common/excel/utils/ExcelUtil.java @@ -0,0 +1,327 @@ +package cc.iotkit.common.excel.utils; + +import cc.iotkit.common.excel.convert.ExcelBigNumberConvert; +import cc.iotkit.common.excel.core.CellMergeStrategy; +import cc.iotkit.common.excel.core.DefaultExcelListener; +import cc.iotkit.common.excel.core.ExcelListener; +import cc.iotkit.common.excel.core.ExcelResult; +import cc.iotkit.common.utils.StringUtils; +import cc.iotkit.common.web.utils.ServletUtils; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ClassPathResource; +import cn.hutool.core.util.IdUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.alibaba.excel.write.metadata.fill.FillWrapper; +import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Excel相关处理 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ExcelUtil { + + /** + * 同步导入(适用于小数据量) + * + * @param is 输入流 + * @return 转换后集合 + */ + public static List importExcel(InputStream is, Class clazz) { + return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync(); + } + + + /** + * 使用校验监听器 异步导入 同步返回 + * + * @param is 输入流 + * @param clazz 对象类型 + * @param isValidate 是否 Validator 检验 默认为是 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, Class clazz, boolean isValidate) { + DefaultExcelListener listener = new DefaultExcelListener<>(isValidate); + EasyExcel.read(is, clazz, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 使用自定义监听器 异步导入 自定义返回 + * + * @param is 输入流 + * @param clazz 对象类型 + * @param listener 自定义监听器 + * @return 转换后集合 + */ + public static ExcelResult importExcel(InputStream is, Class clazz, ExcelListener listener) { + EasyExcel.read(is, clazz, listener).sheet().doRead(); + return listener.getExcelResult(); + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param response 响应体 + */ + public static void exportExcel(List list, String sheetName, Class clazz, HttpServletResponse response) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, false, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param response 响应体 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, HttpServletResponse response) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, merge, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, OutputStream os) { + exportExcel(list, sheetName, clazz, false, os); + } + + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param os 输出流 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, OutputStream os) { + ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz) + .autoCloseStream(false) + // 自动适配 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .sheet(sheetName); + if (merge) { + // 合并处理器 + builder.registerWriteHandler(new CellMergeStrategy(list, true)); + } + builder.doWrite(list); + } + + /** + * 单表多数据模板导出 模板格式为 {.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplate(List data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplate(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 单表多数据模板导出 模板格式为 {.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplate(List data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + // 单表多数据导出 模板格式为 {.属性} + for (Object d : data) { + excelWriter.fill(d, writeSheet); + } + excelWriter.finish(); + } + + /** + * 多表多数据模板导出 模板格式为 {key.属性} + * + * @param filename 文件名 + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param response 响应体 + */ + public static void exportTemplateMultiList(Map data, String filename, String templatePath, HttpServletResponse response) { + try { + resetResponse(filename, response); + ServletOutputStream os = response.getOutputStream(); + exportTemplateMultiList(data, templatePath, os); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + + /** + * 多表多数据模板导出 模板格式为 {key.属性} + * + * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 + * 例如: excel/temp.xlsx + * 重点: 模板文件必须放置到启动类对应的 resource 目录下 + * @param data 模板需要的数据 + * @param os 输出流 + */ + public static void exportTemplateMultiList(Map data, String templatePath, OutputStream os) { + ClassPathResource templateResource = new ClassPathResource(templatePath); + ExcelWriter excelWriter = EasyExcel.write(os) + .withTemplate(templateResource.getStream()) + .autoCloseStream(false) + // 大数值自动转换 防止失真 + .registerConverter(new ExcelBigNumberConvert()) + .build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("数据为空"); + } + for (Map.Entry map : data.entrySet()) { + // 设置列表后续还有数据 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + if (map.getValue() instanceof Collection) { + // 多表导出必须使用 FillWrapper + excelWriter.fill(new FillWrapper(map.getKey(), (Collection) map.getValue()), fillConfig, writeSheet); + } else { + excelWriter.fill(map.getValue(), writeSheet); + } + } + excelWriter.finish(); + } + + /** + * 重置响应体 + */ + private static void resetResponse(String sheetName, HttpServletResponse response) { + String filename = encodingFilename(sheetName); + ServletUtils.setAttachmentResponseHeader(response, filename); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(StringUtils.SEPARATOR); + for (String item : convertSource) { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[0].equals(value)) { + propertyString.append(itemArray[1]).append(separator); + break; + } + } + } else { + if (itemArray[0].equals(propertyValue)) { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(StringUtils.SEPARATOR); + for (String item : convertSource) { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[1].equals(value)) { + propertyString.append(itemArray[0]).append(separator); + break; + } + } + } else { + if (itemArray[1].equals(propertyValue)) { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 编码文件名 + */ + public static String encodingFilename(String filename) { + return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx"; + } + +} diff --git a/iot-common-log/pom.xml b/iot-common-log/pom.xml new file mode 100755 index 0000000..03f6646 --- /dev/null +++ b/iot-common-log/pom.xml @@ -0,0 +1,69 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-log + + + + + cc.iotkit + iot-common-core + + + + cc.iotkit + iot-common-web + + + + cc.iotkit + iot-common-satoken + + + + + + org.aspectj + aspectjweaver + + + + org.springframework.boot + spring-boot-autoconfigure + + + + com.alibaba + transmittable-thread-local + + + + org.projectlombok + lombok + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/iot-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100755 index 0000000..393b972 --- /dev/null +++ b/iot-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cc.iotkit.common.log.aspect.LogAspect diff --git a/iot-common-oss/pom.xml b/iot-common-oss/pom.xml new file mode 100755 index 0000000..4109be1 --- /dev/null +++ b/iot-common-oss/pom.xml @@ -0,0 +1,46 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-oss + + + + cc.iotkit + iot-common-redis + + + + cc.iotkit + iot-common-core + + + + + + com.amazonaws + aws-java-sdk-s3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/constant/OssConstant.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/constant/OssConstant.java new file mode 100755 index 0000000..1afba48 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/constant/OssConstant.java @@ -0,0 +1,38 @@ +package cc.iotkit.common.oss.constant; + +import java.util.Arrays; +import java.util.List; + +/** + * 对象存储常量 + * + * @author Lion Li + */ +public interface OssConstant { + + /** + * 默认配置KEY + */ + String DEFAULT_CONFIG_KEY = "sys_oss:default_config"; + + /** + * 预览列表资源开关Key + */ + String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource"; + + /** + * 系统数据ids + */ + List SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L); + + /** + * 云服务商 + */ + String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"}; + + /** + * https 状态 + */ + String IS_HTTPS = "Y"; + +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/core/OssClient.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/core/OssClient.java new file mode 100755 index 0000000..fc22cb8 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/core/OssClient.java @@ -0,0 +1,260 @@ +package cc.iotkit.common.oss.core; + +import cc.iotkit.common.oss.constant.OssConstant; +import cc.iotkit.common.oss.entity.UploadResult; +import cc.iotkit.common.oss.enumd.AccessPolicyType; +import cc.iotkit.common.oss.enumd.PolicyType; +import cc.iotkit.common.oss.exception.OssException; +import cc.iotkit.common.oss.properties.OssProperties; +import cc.iotkit.common.utils.DateUtils; +import cc.iotkit.common.utils.StringUtils; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.HttpMethod; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; + +/** + * S3 存储协议 所有兼容S3协议的云厂商均支持 + * 阿里云 腾讯云 七牛云 minio + * + * @author Lion Li + */ +public class OssClient { + + private final String configKey; + + private final OssProperties properties; + + private final AmazonS3 client; + + public OssClient(String configKey, OssProperties ossProperties) { + this.configKey = configKey; + this.properties = ossProperties; + try { + AwsClientBuilder.EndpointConfiguration endpointConfig = + new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); + + AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); + AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); + ClientConfiguration clientConfig = new ClientConfiguration(); + if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) { + clientConfig.setProtocol(Protocol.HTTPS); + } else { + clientConfig.setProtocol(Protocol.HTTP); + } + AmazonS3ClientBuilder build = AmazonS3Client.builder() + .withEndpointConfiguration(endpointConfig) + .withClientConfiguration(clientConfig) + .withCredentials(credentialsProvider) + .disableChunkedEncoding(); + if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) { + // minio 使用https限制使用域名访问 需要此配置 站点填域名 + build.enablePathStyleAccess(); + } + this.client = build.build(); + + createBucket(); + } catch (Exception e) { + if (e instanceof OssException) { + throw e; + } + throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); + } + } + + public void createBucket() { + try { + String bucketName = properties.getBucketName(); + if (client.doesBucketExistV2(bucketName)) { + return; + } + CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); + AccessPolicyType accessPolicy = getAccessPolicy(); + createBucketRequest.setCannedAcl(accessPolicy.getAcl()); + client.createBucket(createBucketRequest); + client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); + } catch (Exception e) { + throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); + } + } + + public UploadResult upload(byte[] data, String path, String contentType) { + return upload(new ByteArrayInputStream(data), path, contentType); + } + + public UploadResult upload(InputStream inputStream, String path, String contentType) { + if (!(inputStream instanceof ByteArrayInputStream)) { + inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); + } + try { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(contentType); + metadata.setContentLength(inputStream.available()); + PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); + // 设置上传对象的 Acl 为公共读 + putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); + client.putObject(putObjectRequest); + } catch (Exception e) { + throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); + } + + public void delete(String path) { + path = path.replace(getUrl() + "/", ""); + try { + client.deleteObject(properties.getBucketName(), path); + } catch (Exception e) { + throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); + } + } + + public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { + return upload(data, getPath(properties.getPrefix(), suffix), contentType); + } + + public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) { + return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType); + } + + /** + * 获取文件元数据 + * + * @param path 完整文件路径 + */ + public ObjectMetadata getObjectMetadata(String path) { + path = path.replace(getUrl() + "/", ""); + S3Object object = client.getObject(properties.getBucketName(), path); + return object.getObjectMetadata(); + } + + public InputStream getObjectContent(String path) { + path = path.replace(getUrl() + "/", ""); + S3Object object = client.getObject(properties.getBucketName(), path); + return object.getObjectContent(); + } + + public String getUrl() { + String domain = properties.getDomain(); + String endpoint = properties.getEndpoint(); + String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://"; + // 云服务商直接返回 + if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { + if (StringUtils.isNotBlank(domain)) { + return header + domain; + } + return header + properties.getBucketName() + "." + endpoint; + } + // minio 单独处理 + if (StringUtils.isNotBlank(domain)) { + return header + domain + "/" + properties.getBucketName(); + } + return header + endpoint + "/" + properties.getBucketName(); + } + + public String getPath(String prefix, String suffix) { + // 生成uuid + String uuid = IdUtil.fastSimpleUUID(); + // 文件路径 + String path = DateUtils.datePath() + "/" + uuid; + if (StringUtils.isNotBlank(prefix)) { + path = prefix + "/" + path; + } + return path + suffix; + } + + + public String getConfigKey() { + return configKey; + } + + /** + * 获取私有URL链接 + * + * @param objectKey 对象KEY + * @param second 授权时间 + */ + public String getPrivateUrl(String objectKey, Integer second) { + GeneratePresignedUrlRequest generatePresignedUrlRequest = + new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) + .withMethod(HttpMethod.GET) + .withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); + URL url = client.generatePresignedUrl(generatePresignedUrlRequest); + return url.toString(); + } + + /** + * 检查配置是否相同 + */ + public boolean checkPropertiesSame(OssProperties properties) { + return this.properties.equals(properties); + } + + /** + * 获取当前桶权限类型 + * + * @return 当前桶权限类型code + */ + public AccessPolicyType getAccessPolicy() { + return AccessPolicyType.getByType(properties.getAccessPolicy()); + } + + private static String getPolicy(String bucketName, PolicyType policyType) { + String location = ""; + switch (policyType) { + case WRITE: + location = "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"; + break; + case READ_WRITE: + location = "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"; + break; + default: + location = "\"s3:GetBucketLocation\"\n"; + } + + StringBuilder builder = new StringBuilder(); + builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); + builder.append(location); + builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("\"\n},\n"); + if (policyType == PolicyType.READ) { + builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("\"\n},\n"); + } + String action = ""; + switch (policyType) { + case WRITE: + action = "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; + break; + case READ_WRITE: + action = "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"; + break; + default: + action = "\"s3:GetObject\",\n"; + } + builder.append("{\n\"Action\": "); + builder.append(action); + builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); + builder.append(bucketName); + builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); + return builder.toString(); + } + +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/entity/UploadResult.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/entity/UploadResult.java new file mode 100755 index 0000000..e486ec0 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/entity/UploadResult.java @@ -0,0 +1,24 @@ +package cc.iotkit.common.oss.entity; + +import lombok.Builder; +import lombok.Data; + +/** + * 上传返回体 + * + * @author Lion Li + */ +@Data +@Builder +public class UploadResult { + + /** + * 文件路径 + */ + private String url; + + /** + * 文件名 + */ + private String filename; +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/enumd/AccessPolicyType.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/enumd/AccessPolicyType.java new file mode 100755 index 0000000..b3c6018 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/enumd/AccessPolicyType.java @@ -0,0 +1,56 @@ +package cc.iotkit.common.oss.enumd; + +import cc.iotkit.common.exception.BizException; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 桶访问策略配置 + * + * @author 陈賝 + */ +@Getter +@AllArgsConstructor +public enum AccessPolicyType { + + /** + * private + */ + PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE), + + /** + * public + */ + PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ), + + /** + * custom + */ + CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ); + + /** + * 桶 权限类型 + */ + private final String type; + + /** + * 文件对象 权限类型 + */ + private final CannedAccessControlList acl; + + /** + * 桶策略类型 + */ + private final PolicyType policyType; + + public static AccessPolicyType getByType(String type) { + for (AccessPolicyType value : values()) { + if (value.getType().equals(type)) { + return value; + } + } + throw new BizException("'type' not found By " + type); + } + +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/enumd/PolicyType.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/enumd/PolicyType.java new file mode 100755 index 0000000..c838b05 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/enumd/PolicyType.java @@ -0,0 +1,35 @@ +package cc.iotkit.common.oss.enumd; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * minio策略配置 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum PolicyType { + + /** + * 只读 + */ + READ("read-only"), + + /** + * 只写 + */ + WRITE("write-only"), + + /** + * 读写 + */ + READ_WRITE("read-write"); + + /** + * 类型 + */ + private final String type; + +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/exception/OssException.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/exception/OssException.java new file mode 100755 index 0000000..fd3fdc8 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/exception/OssException.java @@ -0,0 +1,18 @@ +package cc.iotkit.common.oss.exception; + + +/** + * OSS异常类 + * + * @author Lion Li + */ +public class OssException extends RuntimeException { + + + private static final long serialVersionUID = 1L; + + public OssException(String msg) { + super(msg); + } + +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/factory/OssFactory.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/factory/OssFactory.java new file mode 100755 index 0000000..bade134 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/factory/OssFactory.java @@ -0,0 +1,64 @@ +package cc.iotkit.common.oss.factory; + + +import cc.iotkit.common.constant.CacheNames; +import cc.iotkit.common.oss.constant.OssConstant; +import cc.iotkit.common.oss.core.OssClient; +import cc.iotkit.common.oss.exception.OssException; +import cc.iotkit.common.oss.properties.OssProperties; +import cc.iotkit.common.redis.utils.CacheUtils; +import cc.iotkit.common.redis.utils.RedisUtils; +import cc.iotkit.common.utils.JsonUtils; +import cc.iotkit.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 文件上传Factory + * + * @author Lion Li + */ +@Slf4j +public class OssFactory { + + private static final Map CLIENT_CACHE = new ConcurrentHashMap<>(); + + /** + * 获取默认实例 + */ + public static OssClient instance() { + // 获取redis 默认类型 + String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY); + if (StringUtils.isEmpty(configKey)) { + throw new OssException("文件存储服务类型无法找到!"); + } + return instance(configKey); + } + + /** + * 根据类型获取实例 + */ + public static OssClient instance(String configKey) { + String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey); + if (json == null) { + throw new OssException("系统异常, '" + configKey + "'配置信息不存在!"); + } + OssProperties properties = JsonUtils.parseObject(json, OssProperties.class); + OssClient client = CLIENT_CACHE.get(configKey); + if (client == null) { + CLIENT_CACHE.put(configKey, new OssClient(configKey, properties)); + log.info("创建OSS实例 key => {}", configKey); + return CLIENT_CACHE.get(configKey); + } + // 配置不相同则重新构建 + if (!client.checkPropertiesSame(properties)) { + CLIENT_CACHE.put(configKey, new OssClient(configKey, properties)); + log.info("重载OSS实例 key => {}", configKey); + return CLIENT_CACHE.get(configKey); + } + return client; + } + +} diff --git a/iot-common-oss/src/main/java/cc/iotkit/common/oss/properties/OssProperties.java b/iot-common-oss/src/main/java/cc/iotkit/common/oss/properties/OssProperties.java new file mode 100755 index 0000000..ab372c9 --- /dev/null +++ b/iot-common-oss/src/main/java/cc/iotkit/common/oss/properties/OssProperties.java @@ -0,0 +1,58 @@ +package cc.iotkit.common.oss.properties; + +import lombok.Data; + +/** + * OSS对象存储 配置属性 + * + * @author Lion Li + */ +@Data +public class OssProperties { + + /** + * 访问站点 + */ + private String endpoint; + + /** + * 自定义域名 + */ + private String domain; + + /** + * 前缀 + */ + private String prefix; + + /** + * ACCESS_KEY + */ + private String accessKey; + + /** + * SECRET_KEY + */ + private String secretKey; + + /** + * 存储空间名 + */ + private String bucketName; + + /** + * 存储区域 + */ + private String region; + + /** + * 是否https(Y=是,N=否) + */ + private String isHttps; + + /** + * 桶权限类型(0private 1public 2custom) + */ + private String accessPolicy; + +} diff --git a/iot-common-redis/pom.xml b/iot-common-redis/pom.xml new file mode 100755 index 0000000..0961750 --- /dev/null +++ b/iot-common-redis/pom.xml @@ -0,0 +1,49 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-redis + + + + + cc.iotkit + iot-common-core + + + + + + + org.redisson + redisson-spring-boot-starter + + + + com.baomidou + lock4j-redisson-spring-boot-starter + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/config/RedisConfig.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/config/RedisConfig.java new file mode 100755 index 0000000..ea0bfd7 --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/config/RedisConfig.java @@ -0,0 +1,130 @@ +package cc.iotkit.common.redis.config; + +import cc.iotkit.common.redis.config.properties.RedissonProperties; +import cc.iotkit.common.redis.handler.KeyPrefixHandler; +import cc.iotkit.common.redis.manager.PlusSpringCacheManager; +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.redisson.codec.JsonJacksonCodec; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; + +/** + * redis配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration +@EnableCaching +@EnableConfigurationProperties(RedissonProperties.class) +public class RedisConfig { + + @Autowired + private RedissonProperties redissonProperties; + + @Autowired + private ObjectMapper objectMapper; + + @Bean + public RedissonAutoConfigurationCustomizer redissonCustomizer() { + return config -> { + config.setThreads(redissonProperties.getThreads()) + .setNettyThreads(redissonProperties.getNettyThreads()) + .setCodec(new JsonJacksonCodec(objectMapper)); + RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig(); + if (ObjectUtil.isNotNull(singleServerConfig)) { + // 使用单机模式 + config.useSingleServer() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(singleServerConfig.getTimeout()) + .setClientName(singleServerConfig.getClientName()) + .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize()) + .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize()) + .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize()); + } + // 集群配置方式 参考下方注释 + RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig(); + if (ObjectUtil.isNotNull(clusterServersConfig)) { + config.useClusterServers() + //设置redis key前缀 + .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix())) + .setTimeout(clusterServersConfig.getTimeout()) + .setClientName(clusterServersConfig.getClientName()) + .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout()) + .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize()) + .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize()) + .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize()) + .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize()) + .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize()) + .setReadMode(clusterServersConfig.getReadMode()) + .setSubscriptionMode(clusterServersConfig.getSubscriptionMode()); + } + log.info("初始化 redis 配置"); + }; + } + + /** + * 自定义缓存管理器 整合spring-cache + */ + @Bean + public CacheManager cacheManager() { + return new PlusSpringCacheManager(); + } + + /** + * redis集群配置 yml + * + * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉) + * spring: + * redis: + * cluster: + * nodes: + * - 192.168.0.100:6379 + * - 192.168.0.101:6379 + * - 192.168.0.102:6379 + * # 密码 + * password: + * # 连接超时时间 + * timeout: 10s + * # 是否开启ssl + * ssl: false + * + * redisson: + * # 线程池数量 + * threads: 16 + * # Netty线程池数量 + * nettyThreads: 32 + * # 集群配置 + * clusterServersConfig: + * # 客户端名称 + * clientName: ${ruoyi.name} + * # master最小空闲连接数 + * masterConnectionMinimumIdleSize: 32 + * # master连接池大小 + * masterConnectionPoolSize: 64 + * # slave最小空闲连接数 + * slaveConnectionMinimumIdleSize: 32 + * # slave连接池大小 + * slaveConnectionPoolSize: 64 + * # 连接空闲超时,单位:毫秒 + * idleConnectionTimeout: 10000 + * # 命令等待超时,单位:毫秒 + * timeout: 3000 + * # 发布和订阅连接池大小 + * subscriptionConnectionPoolSize: 50 + * # 读取模式 + * readMode: "SLAVE" + * # 订阅模式 + * subscriptionMode: "MASTER" + */ + +} diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/config/properties/RedissonProperties.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/config/properties/RedissonProperties.java new file mode 100755 index 0000000..af7a606 --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/config/properties/RedissonProperties.java @@ -0,0 +1,135 @@ +package cc.iotkit.common.redis.config.properties; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.redisson.config.ReadMode; +import org.redisson.config.SubscriptionMode; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Redisson 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "redisson") +public class RedissonProperties { + + /** + * redis缓存key前缀 + */ + private String keyPrefix; + + /** + * 线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int threads; + + /** + * Netty线程池数量,默认值 = 当前处理核数量 * 2 + */ + private int nettyThreads; + + /** + * 单机服务配置 + */ + private SingleServerConfig singleServerConfig; + + /** + * 集群服务配置 + */ + private ClusterServersConfig clusterServersConfig; + + @Data + @NoArgsConstructor + public static class SingleServerConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * 最小空闲连接数 + */ + private int connectionMinimumIdleSize; + + /** + * 连接池大小 + */ + private int connectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + } + + @Data + @NoArgsConstructor + public static class ClusterServersConfig { + + /** + * 客户端名称 + */ + private String clientName; + + /** + * master最小空闲连接数 + */ + private int masterConnectionMinimumIdleSize; + + /** + * master连接池大小 + */ + private int masterConnectionPoolSize; + + /** + * slave最小空闲连接数 + */ + private int slaveConnectionMinimumIdleSize; + + /** + * slave连接池大小 + */ + private int slaveConnectionPoolSize; + + /** + * 连接空闲超时,单位:毫秒 + */ + private int idleConnectionTimeout; + + /** + * 命令等待超时,单位:毫秒 + */ + private int timeout; + + /** + * 发布和订阅连接池大小 + */ + private int subscriptionConnectionPoolSize; + + /** + * 读取模式 + */ + private ReadMode readMode; + + /** + * 订阅模式 + */ + private SubscriptionMode subscriptionMode; + + } + +} diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/handler/KeyPrefixHandler.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/handler/KeyPrefixHandler.java new file mode 100755 index 0000000..b41eba0 --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/handler/KeyPrefixHandler.java @@ -0,0 +1,50 @@ +package cc.iotkit.common.redis.handler; + +import cc.iotkit.common.utils.StringUtils; +import org.redisson.api.NameMapper; + +/** + * redis缓存key前缀处理 + * + * @author ye + * @date 2022/7/14 17:44 + * @since 4.3.0 + */ +public class KeyPrefixHandler implements NameMapper { + + private final String keyPrefix; + + public KeyPrefixHandler(String keyPrefix) { + //前缀为空 则返回空前缀 + this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":"; + } + + /** + * 增加前缀 + */ + @Override + public String map(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) { + return keyPrefix + name; + } + return name; + } + + /** + * 去除前缀 + */ + @Override + public String unmap(String name) { + if (StringUtils.isBlank(name)) { + return null; + } + if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) { + return name.substring(keyPrefix.length()); + } + return name; + } + +} diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/manager/PlusSpringCacheManager.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/manager/PlusSpringCacheManager.java new file mode 100755 index 0000000..5804050 --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/manager/PlusSpringCacheManager.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cc.iotkit.common.redis.manager; + +import cc.iotkit.common.redis.utils.RedisUtils; +import org.redisson.api.RMap; +import org.redisson.api.RMapCache; +import org.redisson.spring.cache.CacheConfig; +import org.redisson.spring.cache.RedissonCache; +import org.springframework.boot.convert.DurationStyle; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A {@link org.springframework.cache.CacheManager} implementation + * backed by Redisson instance. + *

+ * 修改 RedissonSpringCacheManager 源码 + * 重写 cacheName 处理方法 支持多参数 + * + * @author Nikita Koksharov + * + */ +@SuppressWarnings("unchecked") +public class PlusSpringCacheManager implements CacheManager { + + private boolean dynamic = true; + + private boolean allowNullValues = true; + + private boolean transactionAware = true; + + Map configMap = new ConcurrentHashMap<>(); + ConcurrentMap instanceMap = new ConcurrentHashMap<>(); + + /** + * Creates CacheManager supplied by Redisson instance + */ + public PlusSpringCacheManager() { + } + + + /** + * Defines possibility of storing {@code null} values. + *

+ * Default is true + * + * @param allowNullValues stores if true + */ + public void setAllowNullValues(boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + + /** + * Defines if cache aware of Spring-managed transactions. + * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase. + *

+ * Default is false + * + * @param transactionAware cache is transaction aware if true + */ + public void setTransactionAware(boolean transactionAware) { + this.transactionAware = transactionAware; + } + + /** + * Defines 'fixed' cache names. + * A new cache instance will not be created in dynamic for non-defined names. + *

+ * `null` parameter setups dynamic mode + * + * @param names of caches + */ + public void setCacheNames(Collection names) { + if (names != null) { + for (String name : names) { + getCache(name); + } + dynamic = false; + } else { + dynamic = true; + } + } + + /** + * Set cache config mapped by cache name + * + * @param config object + */ + public void setConfig(Map config) { + this.configMap = (Map) config; + } + + protected CacheConfig createDefaultConfig() { + return new CacheConfig(); + } + + @Override + public Cache getCache(String name) { + Cache cache = instanceMap.get(name); + if (cache != null) { + return cache; + } + if (!dynamic) { + return cache; + } + + CacheConfig config = configMap.get(name); + if (config == null) { + config = createDefaultConfig(); + configMap.put(name, config); + } + + // 重写 cacheName 支持多参数 + String[] array = StringUtils.delimitedListToStringArray(name, "#"); + name = array[0]; + if (array.length > 1) { + config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis()); + } + if (array.length > 2) { + config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis()); + } + if (array.length > 3) { + config.setMaxSize(Integer.parseInt(array[3])); + } + + if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) { + return createMap(name, config); + } + + return createMapCache(name, config); + } + + private Cache createMap(String name, CacheConfig config) { + RMap map = RedisUtils.getClient().getMap(name); + + Cache cache = new RedissonCache(map, allowNullValues); + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } + return cache; + } + + private Cache createMapCache(String name, CacheConfig config) { + RMapCache map = RedisUtils.getClient().getMapCache(name); + + Cache cache = new RedissonCache(map, config, allowNullValues); + if (transactionAware) { + cache = new TransactionAwareCacheDecorator(cache); + } + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; + } else { + map.setMaxSize(config.getMaxSize()); + } + return cache; + } + + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(configMap.keySet()); + } + + +} diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/CacheUtils.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/CacheUtils.java new file mode 100755 index 0000000..9141b76 --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/CacheUtils.java @@ -0,0 +1,75 @@ +package cc.iotkit.common.redis.utils; + +import cc.iotkit.common.utils.SpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.redisson.api.RMap; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import java.util.Set; + +/** + * 缓存操作工具类 {@link } + * + * @author Michelle.Chung + * @date 2022/8/13 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked"}) +public class CacheUtils { + + private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class); + + /** + * 获取缓存组内所有的KEY + * + * @param cacheNames 缓存组名称 + */ + public static Set keys(String cacheNames) { + RMap rmap = (RMap) CACHE_MANAGER.getCache(cacheNames).getNativeCache(); + return rmap.keySet(); + } + + /** + * 获取缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static T get(String cacheNames, Object key) { + Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key); + return wrapper != null ? (T) wrapper.get() : null; + } + + /** + * 保存缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + * @param value 缓存值 + */ + public static void put(String cacheNames, Object key, Object value) { + CACHE_MANAGER.getCache(cacheNames).put(key, value); + } + + /** + * 删除缓存值 + * + * @param cacheNames 缓存组名称 + * @param key 缓存key + */ + public static void evict(String cacheNames, Object key) { + CACHE_MANAGER.getCache(cacheNames).evict(key); + } + + /** + * 清空缓存值 + * + * @param cacheNames 缓存组名称 + */ + public static void clear(String cacheNames) { + CACHE_MANAGER.getCache(cacheNames).clear(); + } + +} diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/QueueUtils.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/QueueUtils.java new file mode 100755 index 0000000..3250a4b --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/QueueUtils.java @@ -0,0 +1,180 @@ +package cc.iotkit.common.redis.utils; + +import cc.iotkit.common.utils.SpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.redisson.api.*; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * 分布式队列工具 + * 轻量级队列 重量级数据量 请使用 MQ + * 要求 redis 5.X 以上 + * + * @author Lion Li + * @version 3.6.0 新增 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class QueueUtils { + + private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class); + + + /** + * 获取客户端实例 + */ + public static RedissonClient getClient() { + return CLIENT; + } + + /** + * 添加普通队列数据 + * + * @param queueName 队列名 + * @param data 数据 + */ + public static boolean addQueueObject(String queueName, T data) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.offer(data); + } + + /** + * 通用获取一个队列数据 没有数据返回 null(不支持延迟队列) + * + * @param queueName 队列名 + */ + public static T getQueueObject(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.poll(); + } + + /** + * 通用删除队列数据(不支持延迟队列) + */ + public static boolean removeQueueObject(String queueName, T data) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.remove(data); + } + + /** + * 通用销毁队列 所有阻塞监听 报错(不支持延迟队列) + */ + public static boolean destroyQueue(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + return queue.delete(); + } + + /** + * 添加延迟队列数据 默认毫秒 + * + * @param queueName 队列名 + * @param data 数据 + * @param time 延迟时间 + */ + public static void addDelayedQueueObject(String queueName, T data, long time) { + addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS); + } + + /** + * 添加延迟队列数据 + * + * @param queueName 队列名 + * @param data 数据 + * @param time 延迟时间 + * @param timeUnit 单位 + */ + public static void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + delayedQueue.offer(data, time, timeUnit); + } + + /** + * 获取一个延迟队列数据 没有数据返回 null + * + * @param queueName 队列名 + */ + public static T getDelayedQueueObject(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + return delayedQueue.poll(); + } + + /** + * 删除延迟队列数据 + */ + public static boolean removeDelayedQueueObject(String queueName, T data) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + return delayedQueue.remove(data); + } + + /** + * 销毁延迟队列 所有阻塞监听 报错 + */ + public static void destroyDelayedQueue(String queueName) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + RDelayedQueue delayedQueue = CLIENT.getDelayedQueue(queue); + delayedQueue.destroy(); + } + + /** + * 添加优先队列数据 + * + * @param queueName 队列名 + * @param data 数据 + */ + public static boolean addPriorityQueueObject(String queueName, T data) { + RPriorityBlockingQueue priorityBlockingQueue = CLIENT.getPriorityBlockingQueue(queueName); + return priorityBlockingQueue.offer(data); + } + + /** + * 尝试设置 有界队列 容量 用于限制数量 + * + * @param queueName 队列名 + * @param capacity 容量 + */ + public static boolean trySetBoundedQueueCapacity(String queueName, int capacity) { + RBoundedBlockingQueue boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName); + return boundedBlockingQueue.trySetCapacity(capacity); + } + + /** + * 尝试设置 有界队列 容量 用于限制数量 + * + * @param queueName 队列名 + * @param capacity 容量 + * @param destroy 已存在是否销毁 + */ + public static boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) { + RBoundedBlockingQueue boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName); + if (boundedBlockingQueue.isExists() && destroy) { + destroyQueue(queueName); + } + return boundedBlockingQueue.trySetCapacity(capacity); + } + + /** + * 添加有界队列数据 + * + * @param queueName 队列名 + * @param data 数据 + * @return 添加成功 true 已达到界限 false + */ + public static boolean addBoundedQueueObject(String queueName, T data) { + RBoundedBlockingQueue boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName); + return boundedBlockingQueue.offer(data); + } + + /** + * 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等) + */ + public static void subscribeBlockingQueue(String queueName, Consumer consumer) { + RBlockingQueue queue = CLIENT.getBlockingQueue(queueName); + queue.subscribeOnElements(consumer); + } + +} diff --git a/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/RedisUtils.java b/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/RedisUtils.java new file mode 100755 index 0000000..b6171c6 --- /dev/null +++ b/iot-common-redis/src/main/java/cc/iotkit/common/redis/utils/RedisUtils.java @@ -0,0 +1,462 @@ +package cc.iotkit.common.redis.utils; + +import cc.iotkit.common.utils.SpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.redisson.api.*; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * redis 工具类 + * + * @author Lion Li + * @version 3.1.0 新增 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +public class RedisUtils { + + private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class); + + /** + * 限流 + * + * @param key 限流key + * @param rateType 限流类型 + * @param rate 速率 + * @param rateInterval 速率间隔 + * @return -1 表示失败 + */ + public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) { + RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); + rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS); + if (rateLimiter.tryAcquire()) { + return rateLimiter.availablePermits(); + } else { + return -1L; + } + } + + /** + * 获取客户端实例 + */ + public static RedissonClient getClient() { + return CLIENT; + } + + /** + * 发布通道消息 + * + * @param channelKey 通道key + * @param msg 发送数据 + * @param consumer 自定义处理 + */ + public static void publish(String channelKey, T msg, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + consumer.accept(msg); + } + + public static void publish(String channelKey, T msg) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.publish(msg); + } + + /** + * 订阅通道接收消息 + * + * @param channelKey 通道key + * @param clazz 消息类型 + * @param consumer 自定义处理 + */ + public static void subscribe(String channelKey, Class clazz, Consumer consumer) { + RTopic topic = CLIENT.getTopic(channelKey); + topic.addListener(clazz, (channel, msg) -> consumer.accept(msg)); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public static void setCacheObject(final String key, final T value) { + setCacheObject(key, value, false); + } + + /** + * 缓存基本的对象,保留当前对象 TTL 有效期 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90) + * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案 + */ + public static void setCacheObject(final String key, final T value, final boolean isSaveTtl) { + RBucket bucket = CLIENT.getBucket(key); + if (isSaveTtl) { + try { + bucket.setAndKeepTTL(value); + } catch (Exception e) { + long timeToLive = bucket.remainTimeToLive(); + setCacheObject(key, value, Duration.ofMillis(timeToLive)); + } + } else { + bucket.set(value); + } + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param duration 时间 + */ + public static void setCacheObject(final String key, final T value, final Duration duration) { + RBatch batch = CLIENT.createBatch(); + RBucketAsync bucket = batch.getBucket(key); + bucket.setAsync(value); + bucket.expireAsync(duration); + batch.execute(); + } + + /** + * 注册对象监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addObjectListener(final String key, final ObjectListener listener) { + RBucket result = CLIENT.getBucket(key); + result.addListener(listener); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final long timeout) { + return expire(key, Duration.ofSeconds(timeout)); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param duration 超时时间 + * @return true=设置成功;false=设置失败 + */ + public static boolean expire(final String key, final Duration duration) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.expire(duration); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public static T getCacheObject(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.get(); + } + + /** + * 获得key剩余存活时间 + * + * @param key 缓存键值 + * @return 剩余存活时间 + */ + public static long getTimeToLive(final String key) { + RBucket rBucket = CLIENT.getBucket(key); + return rBucket.remainTimeToLive(); + } + + /** + * 删除单个对象 + * + * @param key 缓存的键值 + */ + public static boolean deleteObject(final String key) { + return CLIENT.getBucket(key).delete(); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + */ + public static void deleteObject(final Collection collection) { + RBatch batch = CLIENT.createBatch(); + collection.forEach(t -> { + batch.getBucket(t.toString()).deleteAsync(); + }); + batch.execute(); + } + + /** + * 检查缓存对象是否存在 + * + * @param key 缓存的键值 + */ + public static boolean isExistsObject(final String key) { + return CLIENT.getBucket(key).isExists(); + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public static boolean setCacheList(final String key, final List dataList) { + RList rList = CLIENT.getList(key); + return rList.addAll(dataList); + } + + /** + * 注册List监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addListListener(final String key, final ObjectListener listener) { + RList rList = CLIENT.getList(key); + rList.addListener(listener); + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public static List getCacheList(final String key) { + RList rList = CLIENT.getList(key); + return rList.readAll(); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public static boolean setCacheSet(final String key, final Set dataSet) { + RSet rSet = CLIENT.getSet(key); + return rSet.addAll(dataSet); + } + + /** + * 注册Set监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addSetListener(final String key, final ObjectListener listener) { + RSet rSet = CLIENT.getSet(key); + rSet.addListener(listener); + } + + /** + * 获得缓存的set + * + * @param key 缓存的key + * @return set对象 + */ + public static Set getCacheSet(final String key) { + RSet rSet = CLIENT.getSet(key); + return rSet.readAll(); + } + + /** + * 缓存Map + * + * @param key 缓存的键值 + * @param dataMap 缓存的数据 + */ + public static void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + RMap rMap = CLIENT.getMap(key); + rMap.putAll(dataMap); + } + } + + /** + * 注册Map监听器 + *

+ * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置 + * + * @param key 缓存的键值 + * @param listener 监听器配置 + */ + public static void addMapListener(final String key, final ObjectListener listener) { + RMap rMap = CLIENT.getMap(key); + rMap.addListener(listener); + } + + /** + * 获得缓存的Map + * + * @param key 缓存的键值 + * @return map对象 + */ + public static Map getCacheMap(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(rMap.keySet()); + } + + /** + * 获得缓存Map的key列表 + * + * @param key 缓存的键值 + * @return key列表 + */ + public static Set getCacheMapKeySet(final String key) { + RMap rMap = CLIENT.getMap(key); + return rMap.keySet(); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public static void setCacheMapValue(final String key, final String hKey, final T value) { + RMap rMap = CLIENT.getMap(key); + rMap.put(hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T getCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.get(hKey); + } + + /** + * 删除Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public static T delCacheMapValue(final String key, final String hKey) { + RMap rMap = CLIENT.getMap(key); + return rMap.remove(hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public static Map getMultiCacheMapValue(final String key, final Set hKeys) { + RMap rMap = CLIENT.getMap(key); + return rMap.getAll(hKeys); + } + + /** + * 设置原子值 + * + * @param key Redis键 + * @param value 值 + */ + public static void setAtomicValue(String key, long value) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + atomic.set(value); + } + + /** + * 获取原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long getAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.get(); + } + + /** + * 递增原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long incrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.incrementAndGet(); + } + + /** + * 递减原子值 + * + * @param key Redis键 + * @return 当前值 + */ + public static long decrAtomicValue(String key) { + RAtomicLong atomic = CLIENT.getAtomicLong(key); + return atomic.decrementAndGet(); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public static Collection keys(final String pattern) { + Stream stream = CLIENT.getKeys().getKeysStreamByPattern(pattern); + return stream.collect(Collectors.toList()); + } + + /** + * 删除缓存的基本对象列表 + * + * @param pattern 字符串前缀 + */ + public static void deleteKeys(final String pattern) { + CLIENT.getKeys().deleteByPattern(pattern); + } + + /** + * 检查redis中是否存在key + * + * @param key 键 + */ + public static Boolean hasKey(String key) { + RKeys rKeys = CLIENT.getKeys(); + return rKeys.countExists(key) > 0; + } +} diff --git a/iot-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/iot-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100755 index 0000000..cdda983 --- /dev/null +++ b/iot-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cc.iotkit.common.redis.config.RedisConfig diff --git a/iot-common-satoken/pom.xml b/iot-common-satoken/pom.xml new file mode 100755 index 0000000..b7b19a1 --- /dev/null +++ b/iot-common-satoken/pom.xml @@ -0,0 +1,65 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-satoken + + + + + cc.iotkit + iot-common-core + + + + cc.iotkit + iot-common-redis + + + + cc.iotkit + iot-common-web + + + + + + + cn.dev33 + sa-token-spring-boot-starter + + + + cn.dev33 + sa-token-dao-redis-jackson + + + + + cn.dev33 + sa-token-jwt + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/config/SaTokenConfig.java b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/config/SaTokenConfig.java new file mode 100755 index 0000000..0647195 --- /dev/null +++ b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/config/SaTokenConfig.java @@ -0,0 +1,61 @@ +package cc.iotkit.common.satoken.config; + +import cc.iotkit.common.satoken.core.dao.PlusSaTokenDao; +import cc.iotkit.common.satoken.core.service.SaPermissionImpl; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.stp.StpInterface; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.ArrayList; +import java.util.List; + +/** + * sa-token 配置 + * + * @author Lion Li + */ +@Configuration +public class SaTokenConfig implements WebMvcConfigurer { + + /** + * 权限接口实现(使用bean注入方便用户替换) + */ + @Bean + public StpInterface stpInterface() { + return new SaPermissionImpl(); + } + + /** + * 自定义dao层存储 + */ + @Bean + public SaTokenDao saTokenDao() { + return new PlusSaTokenDao(); + } + + // 注册拦截器 + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。 + + List swaggerUrls = List.of("/doc.html","/favicon.ico", "/webjars/**", "/resources/**" + , "/swagger-resources/**", "/swagger-ui.html/**"); + + List loginUrls = List.of("/code", "/auth/tenant/list", "/auth/login"); + List openApiUrls = List.of( "/openapi/v1/getToken"); + + List excludeUrls = new ArrayList<>(); + excludeUrls.addAll(loginUrls); + excludeUrls.addAll(swaggerUrls); + excludeUrls.addAll(openApiUrls); + + registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())) + .addPathPatterns("/**") + .excludePathPatterns(excludeUrls); + } +} diff --git a/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/core/dao/PlusSaTokenDao.java b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/core/dao/PlusSaTokenDao.java new file mode 100755 index 0000000..d35ce39 --- /dev/null +++ b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/core/dao/PlusSaTokenDao.java @@ -0,0 +1,176 @@ +package cc.iotkit.common.satoken.core.dao; + +import cc.iotkit.common.redis.utils.RedisUtils; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一) + * + * @author Lion Li + */ +public class PlusSaTokenDao implements SaTokenDao { + + /** + * 获取Value,如无返空 + */ + @Override + public String get(String key) { + return RedisUtils.getCacheObject(key); + } + + /** + * 写入Value,并设定存活时间 (单位: 秒) + */ + @Override + public void set(String key, String value, long timeout) { + if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if (timeout == NEVER_EXPIRE) { + RedisUtils.setCacheObject(key, value); + } else { + RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout)); + } + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + RedisUtils.deleteObject(key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + long timeout = RedisUtils.getTimeToLive(key); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + RedisUtils.expire(key, Duration.ofSeconds(timeout)); + } + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return RedisUtils.getCacheObject(key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if (timeout == NEVER_EXPIRE) { + RedisUtils.setCacheObject(key, object); + } else { + RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout)); + } + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if (expire == NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + RedisUtils.deleteObject(key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + long timeout = RedisUtils.getTimeToLive(key); + return timeout < 0 ? timeout : timeout / 1000; + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if (timeout == NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if (expire == NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + RedisUtils.expire(key, Duration.ofSeconds(timeout)); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + Collection keys = RedisUtils.keys(prefix + "*" + keyword + "*"); + List list = new ArrayList<>(keys); + return SaFoxUtil.searchList(list, start, size, sortType); + } +} diff --git a/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/core/service/SaPermissionImpl.java b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/core/service/SaPermissionImpl.java new file mode 100755 index 0000000..544696f --- /dev/null +++ b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/core/service/SaPermissionImpl.java @@ -0,0 +1,47 @@ +package cc.iotkit.common.satoken.core.service; + +import cc.iotkit.common.enums.UserType; +import cc.iotkit.common.satoken.utils.LoginHelper; +import cc.iotkit.common.undefined.LoginUser; +import cn.dev33.satoken.stp.StpInterface; + +import java.util.ArrayList; +import java.util.List; + +/** + * sa-token 权限管理实现类 + * + * @author Lion Li + */ +public class SaPermissionImpl implements StpInterface { + + /** + * 获取菜单权限列表 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + LoginUser loginUser = LoginHelper.getLoginUser(); + UserType userType = UserType.getUserType(loginUser.getUserType()); + if (userType == UserType.SYS_USER) { + return new ArrayList<>(loginUser.getMenuPermission()); + } else if (userType == UserType.APP_USER) { + // 其他端 自行根据业务编写 + } + return new ArrayList<>(); + } + + /** + * 获取角色权限列表 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + LoginUser loginUser = LoginHelper.getLoginUser(); + UserType userType = UserType.getUserType(loginUser.getUserType()); + if (userType == UserType.SYS_USER) { + return new ArrayList<>(loginUser.getRolePermission()); + } else if (userType == UserType.APP_USER) { + // 其他端 自行根据业务编写 + } + return new ArrayList<>(); + } +} diff --git a/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/listener/UserActionListener.java b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/listener/UserActionListener.java new file mode 100755 index 0000000..0b4e1a1 --- /dev/null +++ b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/listener/UserActionListener.java @@ -0,0 +1,139 @@ +package cc.iotkit.common.satoken.listener; + +import cc.iotkit.common.constant.CacheConstants; +import cc.iotkit.common.enums.UserType; +import cc.iotkit.common.redis.utils.RedisUtils; +import cc.iotkit.common.satoken.utils.LoginHelper; +import cc.iotkit.common.undefined.LoginUser; +import cc.iotkit.common.undefined.UserOnlineDTO; +import cc.iotkit.common.utils.ip.AddressUtils; +import cc.iotkit.common.web.utils.ServletUtils; +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +/** + * 用户行为 侦听器的实现 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class UserActionListener implements SaTokenListener { + + private final SaTokenConfig tokenConfig; + + /** + * 每次登录时触发 + */ + @Override + public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { + UserType userType = UserType.getUserType(loginId.toString()); + if (userType == UserType.SYS_USER) { + UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = ServletUtils.getClientIP(); + LoginUser user = LoginHelper.getLoginUser(); + UserOnlineDTO dto = new UserOnlineDTO(); + dto.setIpaddr(ip); + dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + dto.setBrowser(userAgent.getBrowser().getName()); + dto.setOs(userAgent.getOs().getName()); + dto.setLoginTime(System.currentTimeMillis()); + dto.setTokenId(tokenValue); + dto.setUserName(user.getUsername()); + dto.setDeptName(user.getDeptName()); + if(tokenConfig.getTimeout() == -1) { + RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto); + } else { + RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout())); + } + log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue); + } else if (userType == UserType.APP_USER) { + // app端 自行根据业务编写 + } + } + + /** + * 每次注销时触发 + */ + @Override + public void doLogout(String loginType, Object loginId, String tokenValue) { + RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); + log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue); + } + + /** + * 每次被踢下线时触发 + */ + @Override + public void doKickout(String loginType, Object loginId, String tokenValue) { + RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); + log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue); + } + + /** + * 每次被顶下线时触发 + */ + @Override + public void doReplaced(String loginType, Object loginId, String tokenValue) { + RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue); + log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue); + } + + /** + * 每次被封禁时触发 + */ + @Override + public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) { + } + + /** + * 每次被解封时触发 + */ + @Override + public void doUntieDisable(String loginType, Object loginId, String service) { + } + + /** + * 每次打开二级认证时触发 + */ + @Override + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCloseSafe(String loginType, String tokenValue, String service) { + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCreateSession(String id) { + } + + /** + * 每次注销Session时触发 + */ + @Override + public void doLogoutSession(String id) { + } + + /** + * 每次Token续期时触发 + */ + @Override + public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { + } +} diff --git a/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/utils/AuthUtil.java b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/utils/AuthUtil.java new file mode 100755 index 0000000..8fc59b4 --- /dev/null +++ b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/utils/AuthUtil.java @@ -0,0 +1,51 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.common.satoken.utils; + +import cc.iotkit.common.constant.Constants; +import cc.iotkit.common.utils.CodecUtil; +import cn.dev33.satoken.stp.StpUtil; +import org.apache.commons.lang3.RandomUtils; + +import java.util.List; + +public class AuthUtil { + + public static String getUserId() { + return String.valueOf(LoginHelper.getUserId()); + } + + public static List getUserRoles() { + return StpUtil.getRoleList(); + } + + public static boolean isAdmin() { + return LoginHelper.isSuperAdmin(); + } + + public static boolean isClientUser() { + return AuthUtil.getUserRoles().contains(Constants.ROLE_CLIENT); + } + + public static boolean hasWriteRole() { + return AuthUtil.getUserRoles().contains(Constants.ROLE_WRITE); + } + + public static String enCryptPwd(String pwd) throws Exception { + return CodecUtil.aesEncrypt(CodecUtil.md5Str(pwd) + ":" + + RandomUtils.nextInt(1000, 9999), Constants.ACCOUNT_SECRET); + } + + public static boolean checkPwd(String pwd, String secret) throws Exception { + String code = CodecUtil.aesDecrypt(secret, Constants.ACCOUNT_SECRET); + String[] arr = code.split(":"); + return arr.length > 0 && CodecUtil.md5Str(pwd).equals(arr[0]); + } +} diff --git a/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/utils/LoginHelper.java b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/utils/LoginHelper.java new file mode 100755 index 0000000..07a7fa3 --- /dev/null +++ b/iot-common-satoken/src/main/java/cc/iotkit/common/satoken/utils/LoginHelper.java @@ -0,0 +1,185 @@ +package cc.iotkit.common.satoken.utils; + +import cc.iotkit.common.constant.TenantConstants; +import cc.iotkit.common.constant.UserConstants; +import cc.iotkit.common.enums.DeviceType; +import cc.iotkit.common.enums.UserType; +import cc.iotkit.common.undefined.LoginUser; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaStorage; +import cn.dev33.satoken.exception.InvalidContextException; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; +import java.util.Set; + +/** + * 登录鉴权助手 + *

+ * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app + * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios + * 可以组成 用户类型与设备类型多对多的 权限灵活控制 + *

+ * 多用户体系 针对 多种用户类型 但权限控制不一致 + * 可以组成 多用户类型表与多设备类型 分别控制权限 + * + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LoginHelper { + + public static final String LOGIN_USER_KEY = "loginUser"; + public static final String TENANT_KEY = "tenantId"; + public static final String USER_KEY = "userId"; + + /** + * 登录系统 + * + * @param loginUser 登录用户信息 + */ + public static void login(LoginUser loginUser) { + loginByDevice(loginUser, null); + } + + /** + * 登录系统 基于 设备类型 + * 针对相同用户体系不同设备 + * + * @param loginUser 登录用户信息 + */ + public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) { + SaStorage storage = SaHolder.getStorage(); + storage.set(LOGIN_USER_KEY, loginUser); + storage.set(TENANT_KEY, loginUser.getTenantId()); + storage.set(USER_KEY, loginUser.getUserId()); + SaLoginModel model = new SaLoginModel(); + if (ObjectUtil.isNotNull(deviceType)) { + model.setDevice(deviceType.getDevice()); + } + StpUtil.login(loginUser.getLoginId(), + model.setExtra(TENANT_KEY, loginUser.getTenantId()) + .setExtra(USER_KEY, loginUser.getUserId())); + StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser); + } + + /** + * 获取用户(多级缓存) + */ + public static LoginUser getLoginUser() { + LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY); + if (loginUser != null) { + return loginUser; + } + loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY); + SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser); + return loginUser; + } + + /** + * 获取用户基于token + */ + public static LoginUser getLoginUser(String token) { + return (LoginUser) StpUtil.getTokenSessionByToken(token).get(LOGIN_USER_KEY); + } + + /** + * 获取用户id + */ + public static Long getUserId() { + try { + LoginUser user = LoginUser.from(StpUtil.getLoginIdAsString()); + if (user == null) { + return null; + } + return user.getUserId(); + } catch (Exception e) { + return null; + } + } + + /** + * 获取租户ID + */ + public static String getTenantId() { + try { + LoginUser loginUser = getLoginUser(); + if (loginUser == null) { + Object tenantId = SaHolder.getStorage().get(TENANT_KEY); + if (tenantId == null) { + return null; + } + return tenantId.toString(); + } + return loginUser.getTenantId(); + } catch (Exception e) { + //skip + } + return null; + } + + /** + * 设置租户ID + * + * @param tenantId 租户ID + */ + public static void setTenantId(String tenantId) { + SaHolder.getStorage().set(TENANT_KEY, tenantId); + } + + /** + * 获取部门ID + */ + public static Long getDeptId() { + return getLoginUser().getDeptId(); + } + + /** + * 获取用户账户 + */ + public static String getUsername() { + return getLoginUser().getUsername(); + } + + /** + * 获取用户类型 + */ + public static UserType getUserType() { + String loginId = StpUtil.getLoginIdAsString(); + return UserType.getUserType(loginId); + } + + /** + * 是否为超级管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isSuperAdmin(Long userId) { + return UserConstants.SUPER_ADMIN_ID.equals(userId); + } + + public static boolean isSuperAdmin() { + return isSuperAdmin(getUserId()); + } + + /** + * 是否为超级管理员 + * + * @param rolePermission 角色权限标识组 + * @return 结果 + */ + public static boolean isTenantAdmin(Set rolePermission) { + return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY); + } + + public static boolean isTenantAdmin() { + return isTenantAdmin(getLoginUser().getRolePermission()); + } + +} diff --git a/iot-common-tenant/pom.xml b/iot-common-tenant/pom.xml new file mode 100755 index 0000000..e62d761 --- /dev/null +++ b/iot-common-tenant/pom.xml @@ -0,0 +1,109 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-tenant + + + + + cc.iotkit + iot-common-core + + + + cc.iotkit + iot-common-redis + + + + cc.iotkit + iot-common-satoken + + + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.hibernate + hibernate-core + + + + org.aspectj + aspectjrt + + + + org.aspectj + aspectjweaver + + + + org.projectlombok + lombok + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.14.0 + + ${java.version} + ${java.version} + ${java.version} + true + true + ignore + UTF-8 + false + true + + + + org.hibernate + hibernate-core + + + + + + + + compile + + test-compile + + + + + + + + + \ No newline at end of file diff --git a/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/aspect/TenantFilterAspect.java b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/aspect/TenantFilterAspect.java new file mode 100755 index 0000000..8bb0339 --- /dev/null +++ b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/aspect/TenantFilterAspect.java @@ -0,0 +1,35 @@ +package cc.iotkit.common.tenant.aspect; + + +import cc.iotkit.common.satoken.utils.LoginHelper; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.hibernate.Session; + +/** + * 类描述... + * + * @author Tiger Chen + * created on 2023/7/14 20:53 + */ + +@Aspect +public class TenantFilterAspect { + + @Pointcut("execution (* org.hibernate.internal.SessionFactoryImpl.SessionBuilderImpl.openSession(..))") + public void openSession() { + } + + @AfterReturning(pointcut = "openSession()", returning = "session") + public void afterOpenSession(Object session) { + if (session instanceof Session) { + String tenantId = LoginHelper.getTenantId(); + if (tenantId != null && !tenantId.equals("000000")) { + org.hibernate.Filter filter = ((Session) session).enableFilter("tenantFilter"); + filter.setParameter("tenantId", tenantId); + } + } + } + +} diff --git a/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/dao/TenantAware.java b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/dao/TenantAware.java new file mode 100755 index 0000000..3c5d57c --- /dev/null +++ b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/dao/TenantAware.java @@ -0,0 +1,6 @@ +package cc.iotkit.common.tenant.dao; + + +public interface TenantAware { + void setTenantId(String tenantId); +} diff --git a/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/entiry/BaseTenantEntity.java b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/entiry/BaseTenantEntity.java new file mode 100755 index 0000000..ee62e36 --- /dev/null +++ b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/entiry/BaseTenantEntity.java @@ -0,0 +1,82 @@ +package cc.iotkit.common.tenant.entiry; + +import cc.iotkit.common.tenant.dao.TenantAware; +import cc.iotkit.common.tenant.listener.TenantListener; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.annotations.Filter; +import org.hibernate.annotations.FilterDef; +import org.hibernate.annotations.ParamDef; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; + +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; +import java.util.Date; + +/** + * 类描述... + * + * @author Tiger Chen + * created on 2023/7/15 20:53 + */ + + +@MappedSuperclass +@Data +@FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")}) +@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId") +@EntityListeners(TenantListener.class) +public abstract class BaseTenantEntity implements TenantAware, Serializable { + private static final long serialVersionUID = 1L; + + @Id + private Long id; + + @Size(max = 30) + @Column(name = "tenant_id") + private String tenantId; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 创建者 + */ + @CreatedBy + @Column(name = "create_by", updatable = false) + private Long createBy; + + /** + * 创建时间 + */ + @CreatedDate + @Column(name = "create_time", updatable = false) + private Date createTime; + + /** + * 更新者 + */ + @LastModifiedBy + @Column(name = "update_by") + private Long updateBy; + + /** + * 更新时间 + */ + @LastModifiedDate + @Column(name = "update_time") + private Date updateTime; + + public BaseTenantEntity(String tenantId) { + this.tenantId = tenantId; + } + +} \ No newline at end of file diff --git a/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/helper/TenantHelper.java b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/helper/TenantHelper.java new file mode 100755 index 0000000..9f03bcd --- /dev/null +++ b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/helper/TenantHelper.java @@ -0,0 +1,85 @@ +package cc.iotkit.common.tenant.helper; + +import cc.iotkit.common.constant.GlobalConstants; +import cc.iotkit.common.redis.utils.RedisUtils; +import cc.iotkit.common.satoken.utils.LoginHelper; +import cc.iotkit.common.utils.StringUtils; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.spring.SpringMVCUtil; +import com.alibaba.ttl.TransmittableThreadLocal; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 租户助手 + * + * @author Lion Li + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TenantHelper { + + private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant"; + + private static final ThreadLocal TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>(); + + /** + * 设置动态租户(一直有效 需要手动清理) + *

+ * 如果为非web环境 那么只在当前线程内生效 + */ + public static void setDynamic(String tenantId) { + if (!SpringMVCUtil.isWeb()) { + TEMP_DYNAMIC_TENANT.set(tenantId); + return; + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + RedisUtils.setCacheObject(cacheKey, tenantId); + SaHolder.getStorage().set(cacheKey, tenantId); + } + + /** + * 获取动态租户(一直有效 需要手动清理) + *

+ * 如果为非web环境 那么只在当前线程内生效 + */ + public static String getDynamic() { + if (!SpringMVCUtil.isWeb()) { + return TEMP_DYNAMIC_TENANT.get(); + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + String tenantId = (String) SaHolder.getStorage().get(cacheKey); + if (StringUtils.isNotBlank(tenantId)) { + return tenantId; + } + tenantId = RedisUtils.getCacheObject(cacheKey); + SaHolder.getStorage().set(cacheKey, tenantId); + return tenantId; + } + + /** + * 清除动态租户 + */ + public static void clearDynamic() { + if (!SpringMVCUtil.isWeb()) { + TEMP_DYNAMIC_TENANT.remove(); + return; + } + String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId(); + RedisUtils.deleteObject(cacheKey); + SaHolder.getStorage().delete(cacheKey); + } + + /** + * 获取当前租户id(动态租户优先) + */ + public static String getTenantId() { + String tenantId = TenantHelper.getDynamic(); + if (StringUtils.isBlank(tenantId)) { + tenantId = LoginHelper.getTenantId(); + } + return tenantId; + } + +} diff --git a/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/listener/TenantListener.java b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/listener/TenantListener.java new file mode 100755 index 0000000..eb48098 --- /dev/null +++ b/iot-common-tenant/src/main/java/cc/iotkit/common/tenant/listener/TenantListener.java @@ -0,0 +1,31 @@ +package cc.iotkit.common.tenant.listener; + + +import cc.iotkit.common.satoken.utils.LoginHelper; +import cc.iotkit.common.tenant.dao.TenantAware; +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.PrePersist; +import javax.persistence.PreRemove; +import javax.persistence.PreUpdate; + +/** + * 类描述... + * + * @author Tiger Chen + * created on 2023/7/14 20:50 + */ + +@Slf4j +public class TenantListener { + + @PreUpdate + @PreRemove + @PrePersist + public void setTenant(TenantAware entity) { + final String tenantId = LoginHelper.getTenantId(); + if (!"000000".equals(tenantId) && tenantId != null) { + entity.setTenantId(tenantId); + } + } +} diff --git a/iot-common-tenant/src/main/resources/META-INF/aop.xml b/iot-common-tenant/src/main/resources/META-INF/aop.xml new file mode 100755 index 0000000..3c6a441 --- /dev/null +++ b/iot-common-tenant/src/main/resources/META-INF/aop.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/iot-common-thing/pom.xml b/iot-common-thing/pom.xml new file mode 100755 index 0000000..9046d96 --- /dev/null +++ b/iot-common-thing/pom.xml @@ -0,0 +1,24 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-thing + + + + + org.projectlombok + lombok + provided + + + + + \ No newline at end of file diff --git a/iot-common-thing/src/main/java/cc/iotkit/common/thing/ThingModelMessage.java b/iot-common-thing/src/main/java/cc/iotkit/common/thing/ThingModelMessage.java new file mode 100755 index 0000000..ac10162 --- /dev/null +++ b/iot-common-thing/src/main/java/cc/iotkit/common/thing/ThingModelMessage.java @@ -0,0 +1,96 @@ +/* + * +---------------------------------------------------------------------- + * | 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; + +import java.util.HashMap; +import java.util.Map; + +/** + * 物模型消息 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ThingModelMessage { + + public static final String TYPE_LIFETIME = "lifetime"; + public static final String TYPE_STATE = "state"; + public static final String TYPE_PROPERTY = "property"; + + public static final String TYPE_EVENT = "event"; + public static final String TYPE_SERVICE = "service"; + + public static final String TYPE_OTA = "ota"; + public static final String TYPE_CONFIG = "config"; + public static final String ID_PROPERTY_GET = "get"; + public static final String ID_PROPERTY_SET = "set"; + public static final String ID_PROPERTY_REPORT = "report"; + public static final String ID_CONFIG_GET = "get"; + public static final String ID_CONFIG_SET = "set"; + public static final String ID_DEREGISTER = "deregister"; + + private String id; + + private String mid; + + private String deviceId; + + private String productKey; + + private String deviceName; + + /** + * 所属用户ID + */ + private String uid; + + /** + * 消息类型 + * lifetime:生命周期 + * state:状态 + * property:属性 + * event:事件 + * service:服务 + */ + private String type; + + private String identifier; + + /** + * 消息状态码 + */ + private int code; + + private Object data; + + /** + * 时间戳,设备上的事件或数据产生的本地时间 + */ + private Long occurred; + + /** + * 消息上报时间 + */ + private Long time; + + public Map dataToMap() { + Map mapData = new HashMap<>(); + if (data instanceof Map) { + ((Map) data).forEach((key, value) -> mapData.put(key.toString(), value)); + } + return mapData; + } +} diff --git a/iot-common-web/pom.xml b/iot-common-web/pom.xml new file mode 100755 index 0000000..6cd38f8 --- /dev/null +++ b/iot-common-web/pom.xml @@ -0,0 +1,65 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-web + + + + + cc.iotkit + iot-common-core + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + cn.hutool + hutool-captcha + + + + com.alibaba + transmittable-thread-local + + + + cn.dev33 + sa-token-core + + + + org.projectlombok + lombok + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/BigNumberSerializer.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/BigNumberSerializer.java new file mode 100755 index 0000000..60f4360 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/BigNumberSerializer.java @@ -0,0 +1,42 @@ +package cc.iotkit.common.web.config; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.ser.std.NumberSerializer; + +import java.io.IOException; + +/** + * 超出 JS 最大最小值 处理 + * + * @author Lion Li + */ +@JacksonStdImpl +public class BigNumberSerializer extends NumberSerializer { + + /** + * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来 + */ + private static final long MAX_SAFE_INTEGER = 9007199254740991L; + private static final long MIN_SAFE_INTEGER = -9007199254740991L; + + /** + * 提供实例 + */ + public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class); + + public BigNumberSerializer(Class rawType) { + super(rawType); + } + + @Override + public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { + // 超出范围 序列化位字符串 + if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { + super.serialize(value, gen, provider); + } else { + gen.writeString(value.toString()); + } + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/CaptchaConfig.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/CaptchaConfig.java new file mode 100755 index 0000000..b37b7e8 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/CaptchaConfig.java @@ -0,0 +1,65 @@ +package cc.iotkit.common.web.config; + +import cc.iotkit.common.web.config.properties.CaptchaProperties; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.CircleCaptcha; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.captcha.ShearCaptcha; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; + +import java.awt.*; + +/** + * 验证码配置 + * + * @author Lion Li + */ +@AutoConfiguration +@EnableConfigurationProperties(CaptchaProperties.class) +public class CaptchaConfig { + + private static final int WIDTH = 160; + private static final int HEIGHT = 60; + private static final Color BACKGROUND = Color.PINK; + private static final Font FONT = new Font("Arial", Font.BOLD, 48); + + /** + * 圆圈干扰验证码 + */ + @Lazy + @Bean + public CircleCaptcha circleCaptcha() { + CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT); + captcha.setBackground(BACKGROUND); + captcha.setFont(FONT); + return captcha; + } + + /** + * 线段干扰的验证码 + */ + @Lazy + @Bean + public LineCaptcha lineCaptcha() { + LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT); + captcha.setBackground(BACKGROUND); + captcha.setFont(FONT); + return captcha; + } + + /** + * 扭曲干扰验证码 + */ + @Lazy + @Bean + public ShearCaptcha shearCaptcha() { + ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT); + captcha.setBackground(BACKGROUND); + captcha.setFont(FONT); + return captcha; + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/CrossConfig.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/CrossConfig.java new file mode 100755 index 0000000..6ae6aa7 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/CrossConfig.java @@ -0,0 +1,28 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.common.web.config; + + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CrossConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") + .allowCredentials(true) + .maxAge(3600) + .allowedHeaders("*"); + } +} \ No newline at end of file diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/FilterConfig.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/FilterConfig.java new file mode 100755 index 0000000..a8fe0ca --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/FilterConfig.java @@ -0,0 +1,53 @@ +package cc.iotkit.common.web.config; + +import cc.iotkit.common.utils.StringUtils; +import cc.iotkit.common.web.config.properties.XssProperties; +import cc.iotkit.common.web.filter.RepeatableFilter; +import cc.iotkit.common.web.filter.XssFilter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +import javax.servlet.DispatcherType; +import java.util.HashMap; +import java.util.Map; + +/** + * Filter配置 + * + * @author Lion Li + */ +@AutoConfiguration +@EnableConfigurationProperties(XssProperties.class) +public class FilterConfig { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration(XssProperties xssProperties) { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR)); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap<>(); + initParameters.put("excludes", xssProperties.getExcludes()); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Bean + public FilterRegistrationBean someFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/JacksonConfig.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/JacksonConfig.java new file mode 100755 index 0000000..c48530a --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/JacksonConfig.java @@ -0,0 +1,46 @@ +package cc.iotkit.common.web.config; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.annotation.Bean; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.TimeZone; + +/** + * jackson 配置 + * + * @author Lion Li + */ +@Slf4j +@AutoConfiguration(before = JacksonAutoConfiguration.class) +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer customizer() { + return builder -> { + // 全局配置序列化返回 JSON 处理 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); + javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + builder.modules(javaTimeModule); + builder.timeZone(TimeZone.getDefault()); + log.info("初始化 jackson 配置"); + }; + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/ResourcesConfig.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/ResourcesConfig.java new file mode 100755 index 0000000..fe34a80 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/ResourcesConfig.java @@ -0,0 +1,52 @@ +package cc.iotkit.common.web.config; + +import cc.iotkit.common.web.interceptor.PlusWebInvokeTimeInterceptor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 通用配置 + * + * @author Lion Li + */ +@AutoConfiguration +public class ResourcesConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 全局访问性能拦截 + registry.addInterceptor(new PlusWebInvokeTimeInterceptor()); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/TenantConfig.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/TenantConfig.java new file mode 100755 index 0000000..1689670 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/TenantConfig.java @@ -0,0 +1,22 @@ +package cc.iotkit.common.web.config; + +import cc.iotkit.common.web.interceptor.TenantInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 注册租户ID拦截器. + * + * @author Tiger Chen + * created on 2023/7/15 14:48 + */ + +@Configuration +public class TenantConfig implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new TenantInterceptor()); + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/properties/CaptchaProperties.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/properties/CaptchaProperties.java new file mode 100755 index 0000000..6d0a131 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/properties/CaptchaProperties.java @@ -0,0 +1,40 @@ +package cc.iotkit.common.web.config.properties; + +import cc.iotkit.common.web.enums.CaptchaCategory; +import cc.iotkit.common.web.enums.CaptchaType; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 验证码 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "captcha") +@Component +public class CaptchaProperties { + + private Boolean enable; + + /** + * 验证码类型 + */ + private CaptchaType type; + + /** + * 验证码类别 + */ + private CaptchaCategory category; + + /** + * 数字验证码位数 + */ + private Integer numberLength; + + /** + * 字符验证码长度 + */ + private Integer charLength; +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/config/properties/XssProperties.java b/iot-common-web/src/main/java/cc/iotkit/common/web/config/properties/XssProperties.java new file mode 100755 index 0000000..4f8e9a0 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/config/properties/XssProperties.java @@ -0,0 +1,30 @@ +package cc.iotkit.common.web.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * xss过滤 配置属性 + * + * @author Lion Li + */ +@Data +@ConfigurationProperties(prefix = "xss") +public class XssProperties { + + /** + * 过滤开关 + */ + private String enabled; + + /** + * 排除链接(多个用逗号分隔) + */ + private String excludes; + + /** + * 匹配链接 + */ + private String urlPatterns; + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/core/BaseController.java b/iot-common-web/src/main/java/cc/iotkit/common/web/core/BaseController.java new file mode 100755 index 0000000..43fd7db --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/core/BaseController.java @@ -0,0 +1,56 @@ +package cc.iotkit.common.web.core; + + +import cc.iotkit.common.exception.ViewException; +import cc.iotkit.common.utils.StringUtils; + +/** + * web层通用数据处理 + * + * @author Lion Li + */ +public class BaseController { + + public static void fail() { + throw new ViewException("操作失败"); + } + + public static void fail(String msg) { + throw new ViewException(ViewException.CODE_FAILED, msg); + } + + public static void fail(T data) { + throw new ViewException(ViewException.CODE_FAILED, "操作失败", data); + } + + public static void fail(String msg, T data) { + throw new ViewException(ViewException.CODE_FAILED, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + */ + public static void warn(String msg) { + throw new ViewException(ViewException.CODE_WARN, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + */ + public static void warn(String msg, T data) { + throw new ViewException(ViewException.CODE_WARN, msg, data); + } + + /** + * 页面跳转 + */ + public String redirect(String url) { + return StringUtils.format("redirect:{}", url); + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/enums/CaptchaCategory.java b/iot-common-web/src/main/java/cc/iotkit/common/web/enums/CaptchaCategory.java new file mode 100755 index 0000000..ca5bddd --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/enums/CaptchaCategory.java @@ -0,0 +1,35 @@ +package cc.iotkit.common.web.enums; + +import cn.hutool.captcha.AbstractCaptcha; +import cn.hutool.captcha.CircleCaptcha; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.captcha.ShearCaptcha; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 验证码类别 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum CaptchaCategory { + + /** + * 线段干扰 + */ + LINE(LineCaptcha.class), + + /** + * 圆圈干扰 + */ + CIRCLE(CircleCaptcha.class), + + /** + * 扭曲干扰 + */ + SHEAR(ShearCaptcha.class); + + private final Class clazz; +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/enums/CaptchaType.java b/iot-common-web/src/main/java/cc/iotkit/common/web/enums/CaptchaType.java new file mode 100755 index 0000000..0159008 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/enums/CaptchaType.java @@ -0,0 +1,29 @@ +package cc.iotkit.common.web.enums; + +import cc.iotkit.common.web.utils.UnsignedMathGenerator; +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.captcha.generator.RandomGenerator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 验证码类型 + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum CaptchaType { + + /** + * 数字 + */ + MATH(UnsignedMathGenerator.class), + + /** + * 字符 + */ + CHAR(RandomGenerator.class); + + private final Class clazz; +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/filter/RepeatableFilter.java b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/RepeatableFilter.java new file mode 100755 index 0000000..03e538f --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/RepeatableFilter.java @@ -0,0 +1,40 @@ +package cc.iotkit.common.web.filter; + +import cc.iotkit.common.utils.StringUtils; +import org.springframework.http.MediaType; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() { + + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/filter/RepeatedlyRequestWrapper.java b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/RepeatedlyRequestWrapper.java new file mode 100755 index 0000000..00ca72c --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,67 @@ +package cc.iotkit.common.web.filter; + +import cc.iotkit.common.constant.Constants; +import cn.hutool.core.io.IoUtil; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = IoUtil.readBytes(request.getInputStream(), false); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() throws IOException { + return bais.read(); + } + + @Override + public int available() throws IOException { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/filter/XssFilter.java b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/XssFilter.java new file mode 100755 index 0000000..28e931a --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/XssFilter.java @@ -0,0 +1,61 @@ +package cc.iotkit.common.web.filter; + +import cc.iotkit.common.utils.StringUtils; +import org.springframework.http.HttpMethod; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter { + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) { + String[] url = tempExcludes.split(StringUtils.SEPARATOR); + excludes.addAll(Arrays.asList(url)); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() { + + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/filter/XssHttpServletRequestWrapper.java b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/XssHttpServletRequestWrapper.java new file mode 100755 index 0000000..914b383 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,97 @@ +package cc.iotkit.common.web.filter; + +import cc.iotkit.common.utils.StringUtils; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HtmlUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values != null) { + int length = values.length; + String[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) { + // 防xss攻击和过滤前后空格 + escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim(); + } + return escapseValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 非json类型,直接返回 + if (!isJsonRequest()) { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = StrUtil.str(IoUtil.readBytes(super.getInputStream(), false), StandardCharsets.UTF_8); + if (StringUtils.isEmpty(json)) { + return super.getInputStream(); + } + + // xss过滤 + json = HtmlUtil.cleanHtmlTag(json).trim(); + byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + final ByteArrayInputStream bis = IoUtil.toStream(jsonBytes); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public int available() throws IOException { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + */ + public boolean isJsonRequest() { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/handler/GlobalExceptionHandler.java b/iot-common-web/src/main/java/cc/iotkit/common/web/handler/GlobalExceptionHandler.java new file mode 100755 index 0000000..c830f11 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/handler/GlobalExceptionHandler.java @@ -0,0 +1,72 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.common.web.handler; + +import cc.iotkit.common.exception.BizException; +import cc.iotkit.common.exception.ViewException; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletResponse; + +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + @ResponseBody + public RequestResult handleException(Exception e, HttpServletResponse response) { + log.error("handler exception", e); + if (e instanceof NotLoginException) { + response.setStatus(401); + return new RequestResult(401, "未授权的请求"); + } + + if (e instanceof NotPermissionException || e instanceof NotRoleException) { + response.setStatus(403); + return new RequestResult(403, "没有权限"); + } + if (e instanceof BizException) { + BizException bizException = (BizException) e; + response.setStatus(200); + return new RequestResult(bizException.getCode(), bizException.getMessage()); + } + if (e instanceof ViewException) { + response.setStatus(200); + return new RequestResult(((ViewException) e).getCode(), e.getMessage()); + } + + if (e.getMessage().contains("Unauthorized")) { + response.setStatus(403); + return new RequestResult(403, "没有权限"); + } + response.setStatus(500); + return new RequestResult(500, e.getMessage()); + } + + @NoArgsConstructor + @AllArgsConstructor + @Data + public static class RequestResult { + private int code; + private String message; + } + +} + + diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/handler/ResponseResultHandler.java b/iot-common-web/src/main/java/cc/iotkit/common/web/handler/ResponseResultHandler.java new file mode 100755 index 0000000..63c5263 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/handler/ResponseResultHandler.java @@ -0,0 +1,59 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.common.web.handler; + +import cc.iotkit.common.api.Response; +import cn.dev33.satoken.util.SaResult; +import cn.hutool.core.util.IdUtil; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.util.Map; + +@ControllerAdvice(basePackages = {"cc.iotkit"}) +public class ResponseResultHandler implements ResponseBodyAdvice { + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return !converterType.equals(StringHttpMessageConverter.class); + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, + MediaType selectedContentType, + Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + if (body instanceof GlobalExceptionHandler.RequestResult) { + GlobalExceptionHandler.RequestResult requestResult = (GlobalExceptionHandler.RequestResult) body; + return new Response(requestResult.getCode(), requestResult.getMessage(), + "", IdUtil.simpleUUID()); + } else if (body instanceof SaResult) { + SaResult result = (SaResult) body; + return new Response(result.getCode(), result.getMsg(), result.getData(), IdUtil.simpleUUID()); + } else if (body instanceof Map) { + Map map = (Map) body; + //spring mvc内部异常 + if (map.containsKey("timestamp") && map.containsKey("status") && map.containsKey("error")) { + return new Response((Integer) map.get("status"), (String) map.get("error"), + "", IdUtil.simpleUUID()); + } + } else if (body instanceof Response) { + return body; + } + + return new Response(200, "", body, IdUtil.simpleUUID()); + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/interceptor/PlusWebInvokeTimeInterceptor.java b/iot-common-web/src/main/java/cc/iotkit/common/web/interceptor/PlusWebInvokeTimeInterceptor.java new file mode 100755 index 0000000..43ac313 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/interceptor/PlusWebInvokeTimeInterceptor.java @@ -0,0 +1,105 @@ +package cc.iotkit.common.web.interceptor; + +import cc.iotkit.common.api.Request; +import cc.iotkit.common.utils.JsonUtils; +import cc.iotkit.common.utils.SpringUtils; +import cc.iotkit.common.utils.StringUtils; +import cc.iotkit.common.web.filter.RepeatedlyRequestWrapper; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import com.alibaba.ttl.TransmittableThreadLocal; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import org.slf4j.MDC; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.util.Map; +import java.util.Objects; + +/** + * web的调用时间统计拦截器 + * dev环境有效 + * + * @author Lion Li + * @since 3.3.0 + */ +@Slf4j +public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor { + + private final static String prodProfile = "prod"; + + private final TransmittableThreadLocal invokeTimeTL = new TransmittableThreadLocal<>(); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!prodProfile.equals(SpringUtils.getActiveProfile())) { + String url = request.getMethod() + " " + request.getRequestURI(); + log.info("request handler:{}, threadId:{}", url, Thread.currentThread().getId()); + + // 打印请求参数 + if (isJsonRequest(request)) { + String jsonParam = ""; + if (request instanceof RepeatedlyRequestWrapper) { + BufferedReader reader = request.getReader(); + jsonParam = IoUtil.read(reader); + Request req = JsonUtils.parseObject(jsonParam, Request.class); + MDC.put("requestId", req.getRequestId()); + } + log.debug("开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam); + } else { + Map parameterMap = request.getParameterMap(); + if (MapUtil.isNotEmpty(parameterMap)) { + String parameters = JsonUtils.toJsonString(parameterMap); + String[] requestIds = parameterMap.get("requestId"); + if (Objects.nonNull(requestIds) && requestIds.length > 0) { + MDC.put("requestId", requestIds[0]); + } + log.debug("开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters); + } else { + log.debug("开始请求 => URL[{}],无参数", url); + } + } + + StopWatch stopWatch = new StopWatch(); + invokeTimeTL.set(stopWatch); + stopWatch.start(); + } + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + if (!prodProfile.equals(SpringUtils.getActiveProfile())) { + StopWatch stopWatch = invokeTimeTL.get(); + stopWatch.stop(); + log.debug("结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime()); + invokeTimeTL.remove(); + MDC.clear(); + } + } + + /** + * 判断本次请求的数据类型是否为json + * + * @param request request + * @return boolean + */ + private boolean isJsonRequest(HttpServletRequest request) { + String contentType = request.getContentType(); + if (contentType != null) { + return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE); + } + return false; + } + +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/interceptor/TenantInterceptor.java b/iot-common-web/src/main/java/cc/iotkit/common/web/interceptor/TenantInterceptor.java new file mode 100755 index 0000000..5114b04 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/interceptor/TenantInterceptor.java @@ -0,0 +1,38 @@ +package cc.iotkit.common.web.interceptor; + +import cn.dev33.satoken.context.SaHolder; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 新增租户拦截器,拦截请求头中的租户id + * + * @author Tiger Chen + * created on 2023/7/15 14:26 + */ + + +public class TenantInterceptor implements HandlerInterceptor { + + public static final String TENANT_ID = "Tenant-Id"; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if (request.getHeader(TENANT_ID) != null) { + String tenantId = request.getHeader(TENANT_ID); + SaHolder.getStorage().set("tenantId", tenantId); + } + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/utils/ServletUtils.java b/iot-common-web/src/main/java/cc/iotkit/common/web/utils/ServletUtils.java new file mode 100755 index 0000000..2ac44a1 --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/utils/ServletUtils.java @@ -0,0 +1,218 @@ +package cc.iotkit.common.web.utils; + +import cc.iotkit.common.utils.StringUtils; +import cn.hutool.core.convert.Convert; +import cn.hutool.http.HttpStatus; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * 客户端工具类 + * + * @author ruoyi + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ServletUtils { + + /** + * 获取String参数 + */ + public static String getParameter(String name) { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR)); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) { + try { + response.setStatus(HttpStatus.HTTP_OK); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + response.getWriter().print(string); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) { + + String accept = request.getHeader("accept"); + if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml"); + } + + public static String getClientIP() { + return getRequest().getRemoteAddr(); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) { + return URLEncoder.encode(str, StandardCharsets.UTF_8); + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) { + return URLDecoder.decode(str, StandardCharsets.UTF_8); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) { + String percentEncodedFileName = percentEncode(realFileName); + String contentDispositionValue = String.format("attachment; filename=%s;filename*=utf-8''%s", percentEncodedFileName, percentEncodedFileName); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue); + response.setHeader("download-filename", percentEncodedFileName); + } +} diff --git a/iot-common-web/src/main/java/cc/iotkit/common/web/utils/UnsignedMathGenerator.java b/iot-common-web/src/main/java/cc/iotkit/common/web/utils/UnsignedMathGenerator.java new file mode 100755 index 0000000..abc810f --- /dev/null +++ b/iot-common-web/src/main/java/cc/iotkit/common/web/utils/UnsignedMathGenerator.java @@ -0,0 +1,84 @@ +package cc.iotkit.common.web.utils; + +import cc.iotkit.common.utils.StringUtils; +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.core.math.Calculator; +import cn.hutool.core.text.CharPool; +import cn.hutool.core.util.RandomUtil; + +/** + * 无符号计算生成器 + * + * @author Lion Li + */ +public class UnsignedMathGenerator implements CodeGenerator { + private static final long serialVersionUID = -5514819971774091076L; + + private static final String OPERATORS = "+-*"; + + /** + * 参与计算数字最大长度 + */ + private final int numberLength; + + /** + * 构造 + */ + public UnsignedMathGenerator() { + this(2); + } + + /** + * 构造 + * + * @param numberLength 参与计算最大数字位数 + */ + public UnsignedMathGenerator(int numberLength) { + this.numberLength = numberLength; + } + + @Override + public String generate() { + final int limit = getLimit(); + int a = RandomUtil.randomInt(limit); + int b = RandomUtil.randomInt(limit); + String max = Integer.toString(Math.max(a,b)); + String min = Integer.toString(Math.min(a,b)); + max = StringUtils.rightPad(max, this.numberLength, CharPool.SPACE); + min = StringUtils.rightPad(min, this.numberLength, CharPool.SPACE); + + return max + RandomUtil.randomChar(OPERATORS) + min + '='; + } + + @Override + public boolean verify(String code, String userInputCode) { + int result; + try { + result = Integer.parseInt(userInputCode); + } catch (NumberFormatException e) { + // 用户输入非数字 + return false; + } + + final int calculateResult = (int) Calculator.conversion(code); + return result == calculateResult; + } + + /** + * 获取验证码长度 + * + * @return 验证码长度 + */ + public int getLength() { + return this.numberLength * 2 + 2; + } + + /** + * 根据长度获取参与计算数字最大值 + * + * @return 最大值 + */ + private int getLimit() { + return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength)); + } +} diff --git a/iot-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/iot-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100755 index 0000000..7c027df --- /dev/null +++ b/iot-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,4 @@ +cc.iotkit.common.web.config.CaptchaConfig +cc.iotkit.common.web.config.FilterConfig +#cc.iotkit.common.web.config.I18nConfig +cc.iotkit.common.web.config.ResourcesConfig diff --git a/iot-common-websocket/pom.xml b/iot-common-websocket/pom.xml new file mode 100755 index 0000000..3d09fcc --- /dev/null +++ b/iot-common-websocket/pom.xml @@ -0,0 +1,27 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-common-websocket + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-message-bus/iot-message-core/pom.xml b/iot-message-bus/iot-message-core/pom.xml new file mode 100755 index 0000000..ab7cceb --- /dev/null +++ b/iot-message-bus/iot-message-core/pom.xml @@ -0,0 +1,27 @@ + + + + iot-message-bus + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-message-core + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/ConsumerHandler.java b/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/ConsumerHandler.java new file mode 100755 index 0000000..55c329d --- /dev/null +++ b/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/ConsumerHandler.java @@ -0,0 +1,16 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.mq; + +public interface ConsumerHandler { + + void handler(T msg); + +} diff --git a/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/MqConsumer.java b/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/MqConsumer.java new file mode 100755 index 0000000..c3678d0 --- /dev/null +++ b/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/MqConsumer.java @@ -0,0 +1,16 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.mq; + +public interface MqConsumer { + + void consume(String topic, ConsumerHandler handler); + +} diff --git a/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/MqProducer.java b/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/MqProducer.java new file mode 100755 index 0000000..f1e8bf4 --- /dev/null +++ b/iot-message-bus/iot-message-core/src/main/java/cc/iotkit/mq/MqProducer.java @@ -0,0 +1,16 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.mq; + +public interface MqProducer { + + void publish(String topic, T msg); + +} diff --git a/iot-message-bus/iot-message-event-bus/pom.xml b/iot-message-bus/iot-message-event-bus/pom.xml new file mode 100755 index 0000000..6405b2b --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/pom.xml @@ -0,0 +1,48 @@ + + + + iot-message-bus + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-message-event-bus + + + + + cc.iotkit + iot-message-core + + + + cc.iotkit + iot-common-thing + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/BeanCodec.java b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/BeanCodec.java new file mode 100755 index 0000000..d436c43 --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/BeanCodec.java @@ -0,0 +1,54 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.vertx; + +import cc.iotkit.common.utils.JsonUtils; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.eventbus.MessageCodec; +import io.vertx.core.json.Json; + +public class BeanCodec implements MessageCodec { + + private final Class beanType; + + public BeanCodec(Class cls) { + beanType = cls; + } + + @Override + public void encodeToWire(Buffer buffer, T o) { + String json = Json.encode(o); + Buffer encoded = Buffer.buffer(json); + buffer.appendInt(encoded.length()); + buffer.appendBuffer(encoded); + } + + @Override + public T decodeFromWire(int pos, Buffer buffer) { + int length = buffer.getInt(pos); + pos += 4; + return Json.decodeValue(buffer.slice(pos, pos + length), beanType); + } + + @Override + public T transform(T o) { + return JsonUtils.parseObject(JsonUtils.toJsonString(o), beanType); + } + + @Override + public String name() { + return beanType.getSimpleName(); + } + + @Override + public byte systemCodecID() { + return -1; + } +} diff --git a/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxManager.java b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxManager.java new file mode 100755 index 0000000..ef13aa1 --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxManager.java @@ -0,0 +1,21 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.vertx; + +import io.vertx.core.Vertx; + +public class VertxManager { + + private static final Vertx INSTANCE = Vertx.vertx(); + + public static Vertx getVertx() { + return INSTANCE; + } +} diff --git a/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxMqConsumer.java b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxMqConsumer.java new file mode 100755 index 0000000..eea4a04 --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxMqConsumer.java @@ -0,0 +1,63 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.vertx; + +import cc.iotkit.mq.ConsumerHandler; +import cc.iotkit.mq.MqConsumer; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Handler; +import io.vertx.core.eventbus.EventBus; +import io.vertx.core.eventbus.Message; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.CountDownLatch; + +@Slf4j +public class VertxMqConsumer implements MqConsumer { + + private final MqConsumerVerticle consumerVerticle; + + private final CountDownLatch countDownLatch = new CountDownLatch(1); + + @SneakyThrows + public VertxMqConsumer(Class cls) { + consumerVerticle = new MqConsumerVerticle<>(cls); + VertxManager.getVertx().deployVerticle(consumerVerticle, stringAsyncResult -> countDownLatch.countDown()); + //等待初始化穿完成 + countDownLatch.await(); + } + + @Override + public void consume(String topic, ConsumerHandler handler) { + consumerVerticle.consume(topic, handler); + } + + public static class MqConsumerVerticle extends AbstractVerticle { + + private final Class cls; + private EventBus eventBus; + + public MqConsumerVerticle(Class cls) { + this.cls = cls; + } + + @Override + public void start() { + eventBus = vertx.eventBus(); + eventBus.registerCodec(new BeanCodec<>(cls)); + } + + public void consume(String topic, ConsumerHandler handler) { + eventBus.consumer(topic, (Handler>) msg -> handler.handler(msg.body())); + } + } + +} diff --git a/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxMqProducer.java b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxMqProducer.java new file mode 100755 index 0000000..60e78cf --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/VertxMqProducer.java @@ -0,0 +1,51 @@ +package cc.iotkit.vertx; + +import cc.iotkit.mq.MqProducer; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.eventbus.DeliveryOptions; +import io.vertx.core.eventbus.EventBus; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.CountDownLatch; + +@Slf4j +public class VertxMqProducer implements MqProducer { + + private final MqProducerVerticle producerVerticle; + + private final CountDownLatch countDownLatch = new CountDownLatch(1); + + @SneakyThrows + public VertxMqProducer(Class cls) { + producerVerticle = new MqProducerVerticle<>(cls); + VertxManager.getVertx().deployVerticle(producerVerticle, stringAsyncResult -> countDownLatch.countDown()); + //等待初始化完成 + countDownLatch.await(); + } + + @Override + public void publish(String topic, T msg) { + producerVerticle.publish(topic, msg); + } + + public static class MqProducerVerticle extends AbstractVerticle { + + private final Class cls; + private EventBus eventBus; + + public MqProducerVerticle(Class cls) { + this.cls = cls; + } + + @Override + public void start() { + eventBus = vertx.eventBus(); + eventBus.registerCodec(new BeanCodec<>(cls)); + } + + public void publish(String topic, T msg) { + eventBus.publish(topic, msg, new DeliveryOptions().setCodecName(cls.getSimpleName())); + } + } +} diff --git a/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/config/VertxConfig.java b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/config/VertxConfig.java new file mode 100755 index 0000000..d313996 --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/src/main/java/cc/iotkit/vertx/config/VertxConfig.java @@ -0,0 +1,27 @@ +package cc.iotkit.vertx.config; + +import cc.iotkit.common.thing.ThingModelMessage; +import cc.iotkit.mq.MqConsumer; +import cc.iotkit.mq.MqProducer; +import cc.iotkit.vertx.VertxMqConsumer; +import cc.iotkit.vertx.VertxMqProducer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class VertxConfig { + + @ConditionalOnMissingBean + @Bean + public MqProducer getThingModelMessageProducer() { + return new VertxMqProducer<>(ThingModelMessage.class); + } + + @ConditionalOnMissingBean + @Bean + public MqConsumer getThingModelMessageConsumer() { + return new VertxMqConsumer<>(ThingModelMessage.class); + } + +} diff --git a/iot-message-bus/iot-message-event-bus/src/test/java/test/MsgPubConsumeTest.java b/iot-message-bus/iot-message-event-bus/src/test/java/test/MsgPubConsumeTest.java new file mode 100755 index 0000000..5aa3ad2 --- /dev/null +++ b/iot-message-bus/iot-message-event-bus/src/test/java/test/MsgPubConsumeTest.java @@ -0,0 +1,75 @@ +package test; + +import cc.iotkit.mq.ConsumerHandler; +import cc.iotkit.vertx.VertxMqConsumer; +import cc.iotkit.vertx.VertxMqProducer; +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; + +public class MsgPubConsumeTest { + + + public static void main(String[] args) { + + Vertx vertx = Vertx.vertx(); + VertxMqConsumer.MqConsumerVerticle consumerVerticle = new VertxMqConsumer.MqConsumerVerticle<>(Bean1.class); + vertx.deployVerticle(consumerVerticle, new Handler>() { + @Override + public void handle(AsyncResult stringAsyncResult) { + consumerVerticle.consume("aaa", new ConsumerHandler() { + @Override + public void handler(Bean1 msg) { + System.out.println("c1:" + msg.getName()); + } + }); + consumerVerticle.consume("aaa", new ConsumerHandler() { + @Override + public void handler(Bean1 msg) { + System.out.println("c2:" + msg.getName()); + } + }); + } + }); + + + VertxMqProducer.MqProducerVerticle producerVerticle = new VertxMqProducer.MqProducerVerticle<>(Bean1.class); + vertx.deployVerticle(producerVerticle, new Handler>() { + @Override + public void handle(AsyncResult stringAsyncResult) { + producerVerticle.publish("aaa", new Bean1("test", 1)); + System.out.println("publish"); + } + }); + } + + public static class Bean1 { + private String name; + private int age; + + public Bean1() { + } + + public Bean1(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + } + +} diff --git a/iot-message-bus/iot-message-rocketmq/pom.xml b/iot-message-bus/iot-message-rocketmq/pom.xml new file mode 100755 index 0000000..45820c4 --- /dev/null +++ b/iot-message-bus/iot-message-rocketmq/pom.xml @@ -0,0 +1,54 @@ + + + + iot-message-bus + cc.iotkit + 1.0.0 + + 4.0.0 + + iot-message-rocketmq + + + + + cc.iotkit + iot-common-thing + + + + cc.iotkit + iot-message-core + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.apache.rocketmq + rocketmq-client + 4.9.4 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-message-bus/iot-message-rocketmq/readme.md b/iot-message-bus/iot-message-rocketmq/readme.md new file mode 100755 index 0000000..8af19e7 --- /dev/null +++ b/iot-message-bus/iot-message-rocketmq/readme.md @@ -0,0 +1,11 @@ +### 支持rocketMq作为消息总线 + +版本:0.4.2 + +rocketMq版本:4.9.4 + +####开启方式: + +1、application.yml中打开注释支持rocketMq作为消息总线 + +2、pom.xml中打开注释使用rocketmq消息总线 diff --git a/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/RocketMqConsumer.java b/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/RocketMqConsumer.java new file mode 100755 index 0000000..d95f685 --- /dev/null +++ b/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/RocketMqConsumer.java @@ -0,0 +1,46 @@ +package cc.iotkit.rocketmq; + +import cc.iotkit.common.utils.JsonUtils; +import cc.iotkit.mq.ConsumerHandler; +import cc.iotkit.mq.MqConsumer; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.common.message.MessageExt; + +import java.nio.charset.StandardCharsets; + +@Slf4j +public class RocketMqConsumer implements MqConsumer { + + private String nameServer; + + private final Class msgType; + + public RocketMqConsumer(String nameServer, Class cls) { + this.nameServer = nameServer; + this.msgType = cls; + } + + @Override + public void consume(String topic, ConsumerHandler handler) { + try { + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(handler.getClass() + .getName().replace(".", "")); + consumer.setNamesrvAddr(nameServer); + consumer.subscribe(topic, "*"); + consumer.registerMessageListener((MessageListenerConcurrently) (messages, context) -> { + for (MessageExt message : messages) { + T msg = JsonUtils.parseObject(new String(message.getBody(), StandardCharsets.UTF_8), msgType); + handler.handler(msg); + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + }); + consumer.start(); + } catch (Throwable e) { + log.error("consume error", e); + } + } + +} diff --git a/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/RocketMqProducer.java b/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/RocketMqProducer.java new file mode 100755 index 0000000..d9a9117 --- /dev/null +++ b/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/RocketMqProducer.java @@ -0,0 +1,36 @@ +package cc.iotkit.rocketmq; + +import cc.iotkit.common.enums.ErrCode; +import cc.iotkit.common.exception.BizException; +import cc.iotkit.common.utils.JsonUtils; +import cc.iotkit.mq.MqProducer; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.common.message.Message; + +import java.nio.charset.StandardCharsets; + +public class RocketMqProducer implements MqProducer { + + private final DefaultMQProducer producer; + + public RocketMqProducer(String nameServer, String group) { + try { + producer = new DefaultMQProducer(group); + producer.setNamesrvAddr(nameServer); + producer.start(); + } catch (Throwable e) { + throw new BizException(ErrCode.INIT_PRODUCER_ERROR, e); + } + } + + @Override + public void publish(String topic, T msg) { + try { + producer.send(new Message(topic, + JsonUtils.toJsonString(msg).getBytes(StandardCharsets.UTF_8))); + } catch (Throwable e) { + throw new BizException(ErrCode.SEND_MSG_ERROR, e); + } + } + +} diff --git a/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/config/RocketMqConfig.java b/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/config/RocketMqConfig.java new file mode 100755 index 0000000..890fe9a --- /dev/null +++ b/iot-message-bus/iot-message-rocketmq/src/main/java/cc/iotkit/rocketmq/config/RocketMqConfig.java @@ -0,0 +1,35 @@ +package cc.iotkit.rocketmq.config; + +import cc.iotkit.common.thing.ThingModelMessage; +import cc.iotkit.mq.MqConsumer; +import cc.iotkit.mq.MqProducer; +import cc.iotkit.rocketmq.RocketMqConsumer; +import cc.iotkit.rocketmq.RocketMqProducer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RocketMqConfig { + + @Value("${rocketmq.name-server}") + private String nameServer; + + @Value("${rocketmq.producer.group}") + private String group; + + @ConditionalOnMissingBean + @Bean + public MqProducer getThingModelMessageProducer() { + return new RocketMqProducer<>(nameServer, group); + } + + @ConditionalOnMissingBean + @Bean + public MqConsumer getThingModelMessageConsumer() { + return new RocketMqConsumer<>(nameServer, ThingModelMessage.class); + } + + +} diff --git a/iot-message-bus/pom.xml b/iot-message-bus/pom.xml new file mode 100755 index 0000000..a44c8ad --- /dev/null +++ b/iot-message-bus/pom.xml @@ -0,0 +1,51 @@ + + + + iot-iita-core + cc.iotkit + 1.0.0 + + 4.0.0 + pom + + 消息队列适配模块,用于提供默认消息队列和适配不同消息队列接入 + + + iot-message-bus + + + iot-message-core + iot-message-event-bus + iot-message-rocketmq + + + + + + cc.iotkit + iot-common-core + + + + + + org.projectlombok + lombok + provided + + + + org.slf4j + slf4j-api + + + + io.vertx + vertx-core + + + + + diff --git a/iot-script-engine/pom.xml b/iot-script-engine/pom.xml new file mode 100755 index 0000000..3b6adfb --- /dev/null +++ b/iot-script-engine/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + iot-iita-core + cc.iotkit + 1.0.0 + + + iot-script-engine + + + + 此模块为脚本引擎 + + + + + 11 + 11 + UTF-8 + + + + + + cc.iotkit + iot-common-core + + + + + + + org.graalvm.sdk + graal-sdk + + + + org.graalvm.js + js + + + + org.graalvm.js + js-scriptengine + + + + org.apache.commons + commons-lang3 + + + + org.slf4j + slf4j-api + + + + commons-beanutils + commons-beanutils + + + + org.projectlombok + lombok + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/iot-script-engine/src/main/java/cc/iotkit/script/IScriptEngine.java b/iot-script-engine/src/main/java/cc/iotkit/script/IScriptEngine.java new file mode 100755 index 0000000..5161bbe --- /dev/null +++ b/iot-script-engine/src/main/java/cc/iotkit/script/IScriptEngine.java @@ -0,0 +1,15 @@ +package cc.iotkit.script; + +import com.fasterxml.jackson.core.type.TypeReference; + +public interface IScriptEngine { + + void setScript(String key); + + void putScriptEnv(String key, Object val); + + void invokeMethod(String methodName, Object... args); + + T invokeMethod(TypeReference type, String methodName, Object... args); + +} diff --git a/iot-script-engine/src/main/java/cc/iotkit/script/JavaScriptEngine.java b/iot-script-engine/src/main/java/cc/iotkit/script/JavaScriptEngine.java new file mode 100755 index 0000000..fb46365 --- /dev/null +++ b/iot-script-engine/src/main/java/cc/iotkit/script/JavaScriptEngine.java @@ -0,0 +1,80 @@ +/* + * +---------------------------------------------------------------------- + * | Copyright (c) 奇特物联 2021-2022 All rights reserved. + * +---------------------------------------------------------------------- + * | Licensed 未经许可不能去掉「奇特物联」相关版权 + * +---------------------------------------------------------------------- + * | Author: xw2sy@163.com + * +---------------------------------------------------------------------- + */ +package cc.iotkit.script; + + +import cc.iotkit.common.utils.JsonUtils; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.HostAccess; +import org.graalvm.polyglot.Value; + +/** + * @author sjg + */ +@Slf4j +public class JavaScriptEngine implements IScriptEngine { + + private final Context context = Context.newBuilder("js").allowHostAccess(HostAccess.ALL).build(); + + private Value jsScript; + + @Override + public void setScript(String script) { + jsScript = context.eval("js", String.format( + "new (function () {\n%s; " + + " this.invoke=function(f,args){" + + " for(i in args){" + + " args[i]=JSON.parse(args[i]);" + + " }" + + " return JSON.stringify(this[f].apply(this,args));" + + " }; " + + "})()", script)); + } + + @Override + public void putScriptEnv(String key, Object value) { + context.getBindings("js").putMember(key, value); + } + + @Override + public void invokeMethod(String methodName, Object... args) { + invokeMethod(new TypeReference() { + }, methodName, args); + } + + @Override + public T invokeMethod(TypeReference type, String methodName, Object... args) { + Value member = jsScript.getMember("invoke"); + + StringBuilder sbArgs = new StringBuilder("["); + //将入参转成json + for (int i = 0; i < args.length; i++) { + args[i] = JsonUtils.toJsonString(args[i]); + sbArgs.append(i == args.length - 1 ? "," : "").append(args[i]); + } + sbArgs.append("]"); + + //通过调用invoke方法将目标方法返回结果转成json + Value rst = member.execute(methodName, args); + + String json = rst.asString(); + log.info("invoke script {},args:{}, result:{}", methodName, sbArgs, json); + + //没有返回值 + if (json == null || "null".equals(json)) { + return null; + } + + return JsonUtils.parseObject(json, type); + } + +} \ No newline at end of file diff --git a/iot-script-engine/src/main/java/cc/iotkit/script/ScriptEngineFactory.java b/iot-script-engine/src/main/java/cc/iotkit/script/ScriptEngineFactory.java new file mode 100755 index 0000000..0aa6375 --- /dev/null +++ b/iot-script-engine/src/main/java/cc/iotkit/script/ScriptEngineFactory.java @@ -0,0 +1,46 @@ +package cc.iotkit.script; + + +import com.fasterxml.jackson.core.type.TypeReference; + +/** + * @author sjg + */ +public class ScriptEngineFactory { + + public static IScriptEngine getScriptEngine(String type) { + if (type == null) { + type = "js"; + } + switch (type) { + case "python": + case "lua": + return new IScriptEngine() { + + @Override + public void setScript(String key) { + + } + + @Override + public void putScriptEnv(String key, Object val) { + + } + + @Override + public void invokeMethod(String methodName, Object... args) { + + } + + @Override + public T invokeMethod(TypeReference type, String methodName, Object... args) { + return null; + } + }; + case "js": + default: + return new JavaScriptEngine(); + } + } + +} diff --git a/iot-script-engine/src/main/java/cc/iotkit/script/ScriptException.java b/iot-script-engine/src/main/java/cc/iotkit/script/ScriptException.java new file mode 100755 index 0000000..86e17a6 --- /dev/null +++ b/iot-script-engine/src/main/java/cc/iotkit/script/ScriptException.java @@ -0,0 +1,7 @@ +package cc.iotkit.script; + +public class ScriptException extends Exception{ + public ScriptException(String message) { + super(message); + } +} diff --git a/pom.xml b/pom.xml new file mode 100755 index 0000000..26418c1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,327 @@ + + + 4.0.0 + + cc.iotkit + iot-iita-core + 1.0.0 + pom + + + iot-common-core + iot-script-engine + iot-message-bus + iot-common-doc + iot-common-excel + iot-common-log + iot-common-oss + iot-common-redis + iot-common-satoken + iot-common-tenant + iot-common-web + iot-common-websocket + iot-common-thing + + + + 11 + 2.7.11 + 4.2.2 + 1.34.0 + 21.1.0 + 1.9.3 + 2.13.1 + 5.8.18 + 3.17.4 + 2.2.4 + 1.3.1 + 1.18.26 + + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + + + + org.springframework.boot + spring-boot-starter-aop + ${spring-boot.version} + + + + org.springframework + spring-context-support + 5.3.27 + + + + org.hibernate + hibernate-core + 5.6.15.Final + + + + org.aspectj + aspectjweaver + 1.9.6 + + + + org.aspectj + aspectjrt + 1.9.6 + + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + + com.baomidou + lock4j-redisson-spring-boot-starter + ${lock4j.version} + + + + io.github.linpeilie + mapstruct-plus-spring-boot-starter + ${mapstruct-plus.version} + + + + cn.dev33 + sa-token-spring-boot-starter + ${satoken.version} + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 2.0.9 + + + + cn.dev33 + sa-token-dao-redis-jackson + ${satoken.version} + + + + cn.dev33 + sa-token-jwt + ${satoken.version} + + + cn.hutool + hutool-all + + + + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + + org.lionsoul + ip2region + 2.7.0 + + + + org.graalvm.sdk + graal-sdk + ${graalvm.version} + + + + org.graalvm.js + js + ${graalvm.version} + + + + org.graalvm.js + js-scriptengine + ${graalvm.version} + + + + commons-beanutils + commons-beanutils + ${beanutils.version} + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + io.vertx + vertx-core + ${vertx.version} + + + + cn.hutool + hutool-core + ${hutool.version} + + + + cn.hutool + hutool-http + ${hutool.version} + + + + cn.hutool + hutool-extra + ${hutool.version} + + + + cn.hutool + hutool-captcha + ${hutool.version} + + + + com.alibaba + transmittable-thread-local + 2.14.2 + + + + com.alibaba + easyexcel + 3.2.1 + + + org.apache.poi + poi-ooxml-schemas + + + + + + cn.dev33 + sa-token-core + ${satoken.version} + + + + commons-codec + commons-codec + 1.14 + + + + commons-io + commons-io + 2.11.0 + + + + org.apache.commons + commons-lang3 + 3.7 + + + + org.slf4j + slf4j-api + 1.7.7 + + + + com.amazonaws + aws-java-sdk-s3 + 1.12.400 + + + + org.projectlombok + lombok + ${lombok.version} + + + + cc.iotkit + iot-common-core + ${project.version} + + + + cc.iotkit + iot-message-core + ${project.version} + + + + cc.iotkit + iot-common-web + ${project.version} + + + + cc.iotkit + iot-common-thing + ${project.version} + + + + cc.iotkit + iot-common-redis + ${project.version} + + + + cc.iotkit + iot-common-satoken + ${project.version} + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file