init
parent
e074b1026c
commit
8233338198
|
@ -1,18 +1,33 @@
|
||||||
# Build and Release Folders
|
# Compiled class file
|
||||||
bin-debug/
|
*.class
|
||||||
bin-release/
|
|
||||||
[Oo]bj/
|
|
||||||
[Bb]in/
|
|
||||||
|
|
||||||
# Other files and folders
|
# Log file
|
||||||
.settings/
|
log
|
||||||
|
*.log
|
||||||
|
|
||||||
# Executables
|
# BlueJ files
|
||||||
*.swf
|
*.ctxt
|
||||||
*.air
|
|
||||||
*.ipa
|
|
||||||
*.apk
|
|
||||||
|
|
||||||
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
|
# Mobile Tools for Java (J2ME)
|
||||||
# should NOT be excluded as they contain compiler settings and other important
|
.mtj.tmp/
|
||||||
# information for Eclipse / Flash Builder.
|
|
||||||
|
# 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
|
||||||
|
|
36
README.en.md
36
README.en.md
|
@ -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/)
|
|
|
@ -1,39 +1,6 @@
|
||||||
# iot-iita-core
|
# iot-iita-core
|
||||||
|
|
||||||
#### 介绍
|
#### 介绍
|
||||||
{**以下是 Gitee 平台说明,您可以替换此简介**
|
[iot-iita](https://gitee.com/open-iita/iotkit-parent) 中使用的核心库
|
||||||
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
|
|
||||||
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
|
|
||||||
|
|
||||||
#### 软件架构
|
编译iot-iita时请先install本仓库。
|
||||||
软件架构说明
|
|
||||||
|
|
||||||
|
|
||||||
#### 安装教程
|
|
||||||
|
|
||||||
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/)
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>iot-iita-core</artifactId>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>iot-common-core</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
此模块为通用业务逻辑或工具类
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JSON工具类 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring框架基本的核心工具 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context-support</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--常用工具类 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-http</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.linpeilie</groupId>
|
||||||
|
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.lionsoul</groupId>
|
||||||
|
<artifactId>ip2region</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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> T to(Class<T> tClass) {
|
||||||
|
return MapstructUtils.convert(this, tClass);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T> extends Request<T> implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页大小
|
||||||
|
*/
|
||||||
|
@Min(1)
|
||||||
|
@NotNull
|
||||||
|
private Integer pageSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前页数
|
||||||
|
*/
|
||||||
|
@Min(1)
|
||||||
|
@Max(100)
|
||||||
|
@NotNull
|
||||||
|
private Integer pageNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序 key为排序字段名 value为排序方向方向desc或者asc
|
||||||
|
*/
|
||||||
|
private Map<String, String> sortMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前记录起始索引 默认值
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_PAGE_NUM = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每页显示记录数 默认值
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
|
||||||
|
public static <T> PageRequest<T> of(T data) {
|
||||||
|
PageRequest<T> pageRequest = new PageRequest<>();
|
||||||
|
pageRequest.setPageSize(DEFAULT_PAGE_SIZE);
|
||||||
|
pageRequest.setPageNum(DEFAULT_PAGE_NUM);
|
||||||
|
pageRequest.setData(data);
|
||||||
|
pageRequest.setRequestId(IdUtil.simpleUUID());
|
||||||
|
return pageRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <DTO> PageRequest<DTO> to(Class<DTO> dtoClass) {
|
||||||
|
PageRequest<DTO> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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<T> {
|
||||||
|
|
||||||
|
private long total;
|
||||||
|
|
||||||
|
private List<T> rows;
|
||||||
|
|
||||||
|
public <VO> Paging<VO> to(Class<VO> voClass) {
|
||||||
|
return MapstructUtils.convert(this, voClass);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T> extends RequestEmpty implements Serializable {
|
||||||
|
|
||||||
|
@Valid
|
||||||
|
@NotNull
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public static <T> Request<T> of(T data) {
|
||||||
|
Request<T> request = new Request<>();
|
||||||
|
request.setData(data);
|
||||||
|
request.setRequestId(IdUtil.simpleUUID());
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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:";
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package cc.iotkit.common.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存组名称常量
|
||||||
|
* <p>
|
||||||
|
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
|
||||||
|
* <p>
|
||||||
|
* ttl 过期时间 如果设置为0则不过期 默认为0
|
||||||
|
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
|
||||||
|
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
|
||||||
|
* <p>
|
||||||
|
* 例子: 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";
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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:";
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package cc.iotkit.common.enums;
|
||||||
|
|
||||||
|
public interface IEnum {
|
||||||
|
/**
|
||||||
|
* 获取枚举的key
|
||||||
|
*/
|
||||||
|
Integer getKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取枚举的下标
|
||||||
|
*/
|
||||||
|
String getValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将参数反序列化为枚举
|
||||||
|
*
|
||||||
|
* @param param 当前值
|
||||||
|
* @param clazz 枚举类型
|
||||||
|
*/
|
||||||
|
static <T extends Enum<T> & IEnum> T parse(Integer param, Class<T> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cc.iotkit.common.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 参数配置服务
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface ConfigService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据参数 key 获取参数值
|
||||||
|
*
|
||||||
|
* @param configKey 参数 key
|
||||||
|
* @return 参数值
|
||||||
|
*/
|
||||||
|
String getConfigValue(String configKey);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cc.iotkit.common.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 部门服务
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface DeptService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过部门ID查询部门名称
|
||||||
|
*
|
||||||
|
* @param deptIds 部门ID串逗号分隔
|
||||||
|
* @return 部门名称串逗号分隔
|
||||||
|
*/
|
||||||
|
String selectDeptNameByIds(String deptIds);
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cc.iotkit.common.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 用户服务
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface UserService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过用户ID查询用户账户
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户账户
|
||||||
|
*/
|
||||||
|
String selectUserNameById(Long userId);
|
||||||
|
|
||||||
|
}
|
|
@ -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<T> {
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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<String> menuPermission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色权限
|
||||||
|
*/
|
||||||
|
private Set<String> rolePermission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色对象
|
||||||
|
*/
|
||||||
|
private List<RoleDTO> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String, URLClassLoader> classLoaders = new HashMap<>();
|
||||||
|
|
||||||
|
protected static <T> Class<T> findClass(String name, String clsName) throws ClassNotFoundException {
|
||||||
|
ClassLoader classLoader = classLoaders.get(name);
|
||||||
|
return (Class<T>) 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> 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<T> componentClass = findClass(name, className);
|
||||||
|
return componentClass.getDeclaredConstructor().newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> 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<T> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> T parse(String json, Class<T> cls) {
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return OBJECT_MAPPER.readValue(json, cls);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(String text, Class<T> clazz) {
|
||||||
|
if (StringUtils.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return OBJECT_MAPPER.readValue(text, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
|
||||||
|
if (PrimitiveArrayUtil.isEmpty(bytes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return OBJECT_MAPPER.readValue(bytes, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T parseObject(String text, TypeReference<T> 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<Dict> 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 <T> List<T> parseArray(String text, Class<T> 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> T objectToJavaBean(Object obj, Class<T> clazz) {
|
||||||
|
if (Objects.isNull(obj)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return OBJECT_MAPPER.convertValue(obj, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 工具类
|
||||||
|
* <p>参考文档:<a href="https://mapstruct.plus/guide/quick-start">mapstruct-plus</a></p>
|
||||||
|
*
|
||||||
|
* @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 <T, V> V convert(T source, Class<V> 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 <T, V> 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 <T, V> List<V> convert(List<T> sourceList, Class<V> 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> T convert(Map<String, Object> map, Class<T> 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 <T, V> Paging<V> convert(Paging<T> source, Class<V> 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> T copyNoNulls(T from, T to, String... fields) {
|
||||||
|
List<String> fieldList = Arrays.asList(fields);
|
||||||
|
|
||||||
|
Map<String, Object> 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<String, ?> toMap(Object bean) {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
new BeanMap(bean).forEach((key, value) -> {
|
||||||
|
if (key.equals("class")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String field = key.toString();
|
||||||
|
map.put(field, value);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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> 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 <E> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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> T getAopProxy(T invoker) {
|
||||||
|
return (T) AopContext.currentProxy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取spring上下文
|
||||||
|
*/
|
||||||
|
public static ApplicationContext context() {
|
||||||
|
return getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <E> List<E> filter(Collection<E> collection, Predicate<E> 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 <E> String join(Collection<E> collection, Function<E, String> function) {
|
||||||
|
return join(collection, function, StringUtils.SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将collection拼接
|
||||||
|
*
|
||||||
|
* @param collection 需要转化的集合
|
||||||
|
* @param function 拼接方法
|
||||||
|
* @param delimiter 拼接符
|
||||||
|
* @return 拼接后的list
|
||||||
|
*/
|
||||||
|
public static <E> String join(Collection<E> collection, Function<E, String> 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 <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return CollUtil.newArrayList();
|
||||||
|
}
|
||||||
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
|
return collection.stream().sorted(comparing).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将collection转化为类型不变的map<br>
|
||||||
|
* <B>{@code Collection<V> ----> Map<K,V>}</B>
|
||||||
|
*
|
||||||
|
* @param collection 需要转化的集合
|
||||||
|
* @param key V类型转化为K类型的lambda方法
|
||||||
|
* @param <V> collection中的泛型
|
||||||
|
* @param <K> map中的key类型
|
||||||
|
* @return 转化后的map
|
||||||
|
*/
|
||||||
|
public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return MapUtil.newHashMap();
|
||||||
|
}
|
||||||
|
return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Collection转化为map(value类型与collection的泛型不同)<br>
|
||||||
|
* <B>{@code Collection<E> -----> Map<K,V> }</B>
|
||||||
|
*
|
||||||
|
* @param collection 需要转化的集合
|
||||||
|
* @param key E类型转化为K类型的lambda方法
|
||||||
|
* @param value E类型转化为V类型的lambda方法
|
||||||
|
* @param <E> collection中的泛型
|
||||||
|
* @param <K> map中的key类型
|
||||||
|
* @param <V> map中的value类型
|
||||||
|
* @return 转化后的map
|
||||||
|
*/
|
||||||
|
public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return MapUtil.newHashMap();
|
||||||
|
}
|
||||||
|
return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将collection按照规则(比如有相同的班级id)分类成map<br>
|
||||||
|
* <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
|
||||||
|
*
|
||||||
|
* @param collection 需要分类的集合
|
||||||
|
* @param key 分类的规则
|
||||||
|
* @param <E> collection中的泛型
|
||||||
|
* @param <K> map中的key类型
|
||||||
|
* @return 分类后的map
|
||||||
|
*/
|
||||||
|
public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return MapUtil.newHashMap();
|
||||||
|
}
|
||||||
|
return collection
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br>
|
||||||
|
* <B>{@code Collection<E> ---> Map<T,Map<U,List<E>>> } </B>
|
||||||
|
*
|
||||||
|
* @param collection 需要分类的集合
|
||||||
|
* @param key1 第一个分类的规则
|
||||||
|
* @param key2 第二个分类的规则
|
||||||
|
* @param <E> 集合元素类型
|
||||||
|
* @param <K> 第一个map中的key类型
|
||||||
|
* @param <U> 第二个map中的key类型
|
||||||
|
* @return 分类后的map
|
||||||
|
*/
|
||||||
|
public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> 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<br>
|
||||||
|
* <B>{@code Collection<E> ---> Map<T,Map<U,E>> } </B>
|
||||||
|
*
|
||||||
|
* @param collection 需要分类的集合
|
||||||
|
* @param key1 第一个分类的规则
|
||||||
|
* @param key2 第二个分类的规则
|
||||||
|
* @param <T> 第一个map中的key类型
|
||||||
|
* @param <U> 第二个map中的key类型
|
||||||
|
* @param <E> collection中的泛型
|
||||||
|
* @return 分类后的map
|
||||||
|
*/
|
||||||
|
public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> 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集合,但是两者的泛型不同<br>
|
||||||
|
* <B>{@code Collection<E> ------> List<T> } </B>
|
||||||
|
*
|
||||||
|
* @param collection 需要转化的集合
|
||||||
|
* @param function collection中的泛型转化为list泛型的lambda表达式
|
||||||
|
* @param <E> collection中的泛型
|
||||||
|
* @param <T> List中的泛型
|
||||||
|
* @return 转化后的list
|
||||||
|
*/
|
||||||
|
public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
|
||||||
|
if (CollUtil.isEmpty(collection)) {
|
||||||
|
return CollUtil.newArrayList();
|
||||||
|
}
|
||||||
|
return collection
|
||||||
|
.stream()
|
||||||
|
.map(function)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将collection转化为Set集合,但是两者的泛型不同<br>
|
||||||
|
* <B>{@code Collection<E> ------> Set<T> } </B>
|
||||||
|
*
|
||||||
|
* @param collection 需要转化的集合
|
||||||
|
* @param function collection中的泛型转化为set泛型的lambda表达式
|
||||||
|
* @param <E> collection中的泛型
|
||||||
|
* @param <T> Set中的泛型
|
||||||
|
* @return 转化后的Set
|
||||||
|
*/
|
||||||
|
public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> 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 <K> map中的key类型
|
||||||
|
* @param <X> 第一个 map的value类型
|
||||||
|
* @param <Y> 第二个 map的value类型
|
||||||
|
* @param <V> 最终map的value类型
|
||||||
|
* @return 合并后的map
|
||||||
|
*/
|
||||||
|
public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> 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<K> key = new HashSet<>();
|
||||||
|
key.addAll(map1.keySet());
|
||||||
|
key.addAll(map2.keySet());
|
||||||
|
Map<K, V> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文本, {} 表示占位符<br>
|
||||||
|
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
|
||||||
|
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
|
||||||
|
* 例:<br>
|
||||||
|
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
|
||||||
|
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is {} for a<br>
|
||||||
|
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
|
||||||
|
*
|
||||||
|
* @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<String> 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<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {
|
||||||
|
List<String> 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<String> 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<String> splitList(String str) {
|
||||||
|
return splitTo(str, Convert::toStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切分字符串
|
||||||
|
*
|
||||||
|
* @param str 被切分的字符串
|
||||||
|
* @param separator 分隔符
|
||||||
|
* @return 分割后的数据列表
|
||||||
|
*/
|
||||||
|
public static List<String> splitList(String str, String separator) {
|
||||||
|
return splitTo(str, separator, Convert::toStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切分字符串自定义转换(分隔符默认逗号)
|
||||||
|
*
|
||||||
|
* @param str 被切分的字符串
|
||||||
|
* @param mapper 自定义转换
|
||||||
|
* @return 分割后的数据列表
|
||||||
|
*/
|
||||||
|
public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
|
||||||
|
return splitTo(str, SEPARATOR, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切分字符串自定义转换
|
||||||
|
*
|
||||||
|
* @param str 被切分的字符串
|
||||||
|
* @param separator 分隔符
|
||||||
|
* @param mapper 自定义转换
|
||||||
|
* @return 分割后的数据列表
|
||||||
|
*/
|
||||||
|
public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
|
||||||
|
if (isBlank(str)) {
|
||||||
|
return new ArrayList<>(0);
|
||||||
|
}
|
||||||
|
return StrUtil.split(str, separator)
|
||||||
|
.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(mapper)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
|
||||||
|
return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 <T> void validate(T object, Class<?>... groups) {
|
||||||
|
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
|
||||||
|
if (!validate.isEmpty()) {
|
||||||
|
throw new ConstraintViolationException("参数校验异常", validate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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"};
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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地址定位工具类,离线方式
|
||||||
|
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
|
||||||
|
*
|
||||||
|
* @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 "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package cc.iotkit.common.validate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验分组 add
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface AddGroup {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package cc.iotkit.common.validate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验分组 delete
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface DeleteGroup {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package cc.iotkit.common.validate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验分组 edit
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface EditGroup {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package cc.iotkit.common.validate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验分组 query
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface QueryGroup {
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>iot-iita-core</artifactId>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>iot-common-doc</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- SpringBoot Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.mapstruct</groupId>
|
||||||
|
<artifactId>mapstruct</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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 <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
|
||||||
|
mappings.removeIf(mapping -> mapping.getPatternParser() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
|
||||||
|
try {
|
||||||
|
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
|
||||||
|
field.setAccessible(true);
|
||||||
|
return (List<RequestMappingInfoHandlerMapping>) 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<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
|
||||||
|
// Collection<ExposableWebEndpoint> 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));
|
||||||
|
// }
|
||||||
|
}
|
|
@ -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<String> basePath = new ArrayList<>(); //swagger会解析的url规则
|
||||||
|
private List<String> excludePath = new ArrayList<>();//在basePath基础上需要排除的url规则
|
||||||
|
private Map<String, DocketInfo> 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<String> basePath = new ArrayList<>(); //swagger会解析的url规则
|
||||||
|
private List<String> 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
cc.iotkit.swagger.config.SwaggerAutoConfiguration,\
|
||||||
|
cc.iotkit.swagger.config.SwaggerWebConfiguration
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>iot-iita-core</artifactId>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>iot-common-excel</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--====================第三方库===================-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>easyexcel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package cc.iotkit.common.excel.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举格式化
|
||||||
|
*
|
||||||
|
* @author Liang
|
||||||
|
*/
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Inherited
|
||||||
|
public @interface ExcelEnumFormat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典枚举类型
|
||||||
|
*/
|
||||||
|
Class<? extends Enum<?>> enumClass();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典枚举类中对应的code属性名称,默认为code
|
||||||
|
*/
|
||||||
|
String codeField() default "code";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典枚举类中对应的text属性名称,默认为text
|
||||||
|
*/
|
||||||
|
String textField() default "text";
|
||||||
|
|
||||||
|
}
|
|
@ -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<Long> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Long> 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<Object> 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<Object> cellData = new WriteCellData<>(new BigDecimal(object));
|
||||||
|
cellData.setType(CellDataTypeEnum.NUMBER);
|
||||||
|
return cellData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Object> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Object> 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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Object> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Object> 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<Object, String> enumValueMap = beforeConvert(contentProperty);
|
||||||
|
String textValue = enumValueMap.get(codeValue);
|
||||||
|
return Convert.convert(contentProperty.getField().getType(), textValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
|
||||||
|
if (ObjectUtil.isNull(object)) {
|
||||||
|
return new WriteCellData<>("");
|
||||||
|
}
|
||||||
|
Map<Object, String> enumValueMap = beforeConvert(contentProperty);
|
||||||
|
String value = Convert.toStr(enumValueMap.get(object), "");
|
||||||
|
return new WriteCellData<>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
|
||||||
|
ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
|
||||||
|
Map<Object, String> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CellRangeAddress> 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<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
|
||||||
|
List<CellRangeAddress> cellList = new ArrayList<>();
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return cellList;
|
||||||
|
}
|
||||||
|
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
|
||||||
|
|
||||||
|
// 有注解的字段
|
||||||
|
List<Field> mergeFields = new ArrayList<>();
|
||||||
|
List<Integer> 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<Field, RepeatCell> 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否Validator检验,默认为是
|
||||||
|
*/
|
||||||
|
private Boolean isValidate = Boolean.TRUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* excel 表头数据
|
||||||
|
*/
|
||||||
|
private Map<Integer, String> headMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入回执
|
||||||
|
*/
|
||||||
|
private ExcelResult<T> 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("第{}行-第{}列-表头{}: 解析异常<br/>",
|
||||||
|
rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.error(errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exception instanceof ConstraintViolationException) {
|
||||||
|
ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
|
||||||
|
Set<ConstraintViolation<?>> 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<Integer, String> 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<T> getExcelResult() {
|
||||||
|
return excelResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<T> implements ExcelResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据对象list
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private List<T> list;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误信息列表
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private List<String> errorList;
|
||||||
|
|
||||||
|
public DefaultExcelResult() {
|
||||||
|
this.list = new ArrayList<>();
|
||||||
|
this.errorList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultExcelResult(List<T> list, List<String> errorList) {
|
||||||
|
this.list = list;
|
||||||
|
this.errorList = errorList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultExcelResult(ExcelResult<T> excelResult) {
|
||||||
|
this.list = excelResult.getList();
|
||||||
|
this.errorList = excelResult.getErrorList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> 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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package cc.iotkit.common.excel.core;
|
||||||
|
|
||||||
|
import com.alibaba.excel.read.listener.ReadListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel 导入监听
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface ExcelListener<T> extends ReadListener<T> {
|
||||||
|
|
||||||
|
ExcelResult<T> getExcelResult();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cc.iotkit.common.excel.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* excel返回对象
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface ExcelResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象列表
|
||||||
|
*/
|
||||||
|
List<T> getList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误列表
|
||||||
|
*/
|
||||||
|
List<String> getErrorList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入回执
|
||||||
|
*/
|
||||||
|
String getAnalysis();
|
||||||
|
}
|
|
@ -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 <T> List<T> importExcel(InputStream is, Class<T> clazz) {
|
||||||
|
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用校验监听器 异步导入 同步返回
|
||||||
|
*
|
||||||
|
* @param is 输入流
|
||||||
|
* @param clazz 对象类型
|
||||||
|
* @param isValidate 是否 Validator 检验 默认为是
|
||||||
|
* @return 转换后集合
|
||||||
|
*/
|
||||||
|
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
|
||||||
|
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
|
||||||
|
EasyExcel.read(is, clazz, listener).sheet().doRead();
|
||||||
|
return listener.getExcelResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用自定义监听器 异步导入 自定义返回
|
||||||
|
*
|
||||||
|
* @param is 输入流
|
||||||
|
* @param clazz 对象类型
|
||||||
|
* @param listener 自定义监听器
|
||||||
|
* @return 转换后集合
|
||||||
|
*/
|
||||||
|
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
|
||||||
|
EasyExcel.read(is, clazz, listener).sheet().doRead();
|
||||||
|
return listener.getExcelResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出excel
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param response 响应体
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> 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 <T> void exportExcel(List<T> list, String sheetName, Class<T> 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 <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
|
||||||
|
exportExcel(list, sheetName, clazz, false, os);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出excel
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param merge 是否合并单元格
|
||||||
|
* @param os 输出流
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> 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<Object> 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<Object> 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<String, Object> 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<String, Object> 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<String, Object> 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>iot-iita-core</artifactId>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>iot-common-log</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-satoken</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--====================第三方库===================-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1 @@
|
||||||
|
cc.iotkit.common.log.aspect.LogAspect
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>iot-iita-core</artifactId>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>iot-common-oss</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cc.iotkit</groupId>
|
||||||
|
<artifactId>iot-common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--====================第三方库===================-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.amazonaws</groupId>
|
||||||
|
<artifactId>aws-java-sdk-s3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -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<Long> SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云服务商
|
||||||
|
*/
|
||||||
|
String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https 状态
|
||||||
|
*/
|
||||||
|
String IS_HTTPS = "Y";
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue