commit
79d013398a
382 changed files with 36009 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||
.idea |
|||
**/target/ |
|||
**/logs/ |
|||
*.log |
|||
@ -0,0 +1,62 @@ |
|||
<?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>box</artifactId> |
|||
<groupId>com.mathvision</groupId> |
|||
<version>1.0-SNAPSHOT</version> |
|||
</parent> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<artifactId>box-common</artifactId> |
|||
<description>通用工具</description> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-web</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-validation</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>org.apache.poi</groupId> |
|||
<artifactId>poi-ooxml</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.apache.poi</groupId> |
|||
<artifactId>poi-scratchpad</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>com.auth0</groupId> |
|||
<artifactId>java-jwt</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- pagehelper 分页插件 --> |
|||
<dependency> |
|||
<groupId>com.github.pagehelper</groupId> |
|||
<artifactId>pagehelper-spring-boot-starter</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>com.opencsv</groupId> |
|||
<artifactId>opencsv</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.squareup.okhttp3</groupId> |
|||
<artifactId>okhttp</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>com.github.xiaoymin</groupId> |
|||
<artifactId>knife4j-spring-boot-starter</artifactId> |
|||
</dependency> |
|||
|
|||
</dependencies> |
|||
|
|||
</project> |
|||
@ -0,0 +1,15 @@ |
|||
package com.mathvision.box.common.annotation; |
|||
|
|||
import java.lang.annotation.*; |
|||
|
|||
/** |
|||
* 自定义鉴权注解 |
|||
* |
|||
* @author fy |
|||
*/ |
|||
@Target(ElementType.METHOD) |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Documented |
|||
public @interface Auth { |
|||
String permission() default ""; |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package com.mathvision.box.common.annotation; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.FIELD) |
|||
public @interface CsvColumn { |
|||
String header() default ""; |
|||
int order() default 0; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package com.mathvision.box.common.annotation; |
|||
|
|||
import java.lang.annotation.*; |
|||
|
|||
/** |
|||
* 自定义操作日志注解 |
|||
* |
|||
* @author fy |
|||
*/ |
|||
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
|
|||
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
|
|||
@Documented |
|||
public @interface Log { |
|||
|
|||
String logType() default ""; // 日志类型
|
|||
|
|||
String logContent() default ""; // 日志内容
|
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
package com.mathvision.box.common.annotation.aes; |
|||
|
|||
import java.lang.annotation.*; |
|||
|
|||
/** |
|||
* AES解密 参数校验 |
|||
*/ |
|||
@Target(ElementType.FIELD) |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
public @interface AesDecrypt { |
|||
String message() default "字段需AES加密传输"; |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
package com.mathvision.box.common.annotation.aes; |
|||
|
|||
/** |
|||
* AES加密 |
|||
*/ |
|||
public @interface AesEncrypt { |
|||
} |
|||
@ -0,0 +1,186 @@ |
|||
package com.mathvision.box.common.annotation.excel; |
|||
|
|||
import com.mathvision.box.common.utils.poi.ExcelHandlerAdapter; |
|||
import org.apache.poi.ss.usermodel.HorizontalAlignment; |
|||
import org.apache.poi.ss.usermodel.IndexedColors; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
import java.math.BigDecimal; |
|||
|
|||
/** |
|||
* 自定义导出Excel数据注解 |
|||
*/ |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.FIELD) |
|||
public @interface Excel |
|||
{ |
|||
/** |
|||
* 导出时在excel中排序 |
|||
*/ |
|||
public int sort() default Integer.MAX_VALUE; |
|||
|
|||
/** |
|||
* 导出到Excel中的名字. |
|||
*/ |
|||
public String name() default ""; |
|||
|
|||
/** |
|||
* 日期格式, 如: yyyy-MM-dd |
|||
*/ |
|||
public String dateFormat() default ""; |
|||
|
|||
/** |
|||
* 如果是字典类型,请设置字典的type值 (如: sys_user_sex) |
|||
*/ |
|||
public String dictType() default ""; |
|||
|
|||
/** |
|||
* 读取内容转表达式 (如: 0=男,1=女,2=未知) |
|||
*/ |
|||
public String readConverterExp() default ""; |
|||
|
|||
/** |
|||
* 分隔符,读取字符串组内容 |
|||
*/ |
|||
public String separator() default ","; |
|||
|
|||
/** |
|||
* BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) |
|||
*/ |
|||
public int scale() default -1; |
|||
|
|||
/** |
|||
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN |
|||
*/ |
|||
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; |
|||
|
|||
/** |
|||
* 导出时在excel中每个列的高度 |
|||
*/ |
|||
public double height() default 14; |
|||
|
|||
/** |
|||
* 导出时在excel中每个列的宽度 |
|||
*/ |
|||
public double width() default 16; |
|||
|
|||
/** |
|||
* 文字后缀,如% 90 变成90% |
|||
*/ |
|||
public String suffix() default ""; |
|||
|
|||
/** |
|||
* 当值为空时,字段的默认值 |
|||
*/ |
|||
public String defaultValue() default ""; |
|||
|
|||
/** |
|||
* 提示信息 |
|||
*/ |
|||
public String prompt() default ""; |
|||
|
|||
/** |
|||
* 设置只能选择不能输入的列内容. |
|||
*/ |
|||
public String[] combo() default {}; |
|||
|
|||
/** |
|||
* 是否需要纵向合并单元格,应对需求:含有list集合单元格) |
|||
*/ |
|||
public boolean needMerge() default false; |
|||
|
|||
/** |
|||
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. |
|||
*/ |
|||
public boolean isExport() default true; |
|||
|
|||
/** |
|||
* 另一个类中的属性名称,支持多级获取,以小数点隔开 |
|||
*/ |
|||
public String targetAttr() default ""; |
|||
|
|||
/** |
|||
* 是否自动统计数据,在最后追加一行统计数据总和 |
|||
*/ |
|||
public boolean isStatistics() default false; |
|||
|
|||
/** |
|||
* 导出类型(0数字 1字符串 2图片) |
|||
*/ |
|||
public ColumnType cellType() default ColumnType.STRING; |
|||
|
|||
/** |
|||
* 导出列头背景颜色 |
|||
*/ |
|||
public IndexedColors headerBackgroundColor() default IndexedColors.SKY_BLUE; |
|||
|
|||
/** |
|||
* 导出列头字体颜色 |
|||
*/ |
|||
public IndexedColors headerColor() default IndexedColors.WHITE; |
|||
|
|||
/** |
|||
* 导出单元格背景颜色 |
|||
*/ |
|||
public IndexedColors backgroundColor() default IndexedColors.WHITE; |
|||
|
|||
/** |
|||
* 导出单元格字体颜色 |
|||
*/ |
|||
public IndexedColors color() default IndexedColors.BLACK; |
|||
|
|||
/** |
|||
* 导出字段对齐方式 |
|||
*/ |
|||
public HorizontalAlignment align() default HorizontalAlignment.CENTER; |
|||
|
|||
/** |
|||
* 自定义数据处理器 |
|||
*/ |
|||
public Class<?> handler() default ExcelHandlerAdapter.class; |
|||
|
|||
/** |
|||
* 自定义数据处理器参数 |
|||
*/ |
|||
public String[] args() default {}; |
|||
|
|||
/** |
|||
* 字段类型(0:导出导入;1:仅导出;2:仅导入) |
|||
*/ |
|||
Type type() default Type.ALL; |
|||
|
|||
public enum Type |
|||
{ |
|||
ALL(0), EXPORT(1), IMPORT(2); |
|||
private final int value; |
|||
|
|||
Type(int value) |
|||
{ |
|||
this.value = value; |
|||
} |
|||
|
|||
public int value() |
|||
{ |
|||
return this.value; |
|||
} |
|||
} |
|||
|
|||
public enum ColumnType |
|||
{ |
|||
NUMERIC(0), STRING(1), IMAGE(2); |
|||
private final int value; |
|||
|
|||
ColumnType(int value) |
|||
{ |
|||
this.value = value; |
|||
} |
|||
|
|||
public int value() |
|||
{ |
|||
return this.value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package com.mathvision.box.common.annotation.excel; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* Excel注解集 |
|||
* |
|||
*/ |
|||
@Target(ElementType.FIELD) |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
public @interface Excels |
|||
{ |
|||
Excel[] value(); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.mathvision.box.common.annotation.tree; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* @Description:树节点ID注解 |
|||
* @Version: 1.0 |
|||
* @Author: yyy |
|||
*/ |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.FIELD) |
|||
public @interface TreeNodeId { |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.mathvision.box.common.annotation.tree; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* @Description:树节点名称注解 |
|||
* @Version: 1.0 |
|||
* @Author: yyy |
|||
*/ |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.FIELD) |
|||
public @interface TreeNodeName { |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.mathvision.box.common.annotation.tree; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* @Description:树节点父ID注解 |
|||
* @Version: 1.0 |
|||
* @Author: yyy |
|||
*/ |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(ElementType.FIELD) |
|||
public @interface TreeNodeParentId { |
|||
} |
|||
@ -0,0 +1,252 @@ |
|||
package com.mathvision.box.common.config; |
|||
|
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:全局配置类 |
|||
*/ |
|||
@Component |
|||
@ConfigurationProperties(prefix = "app") |
|||
public class AppConfig { |
|||
/** 项目名称 */ |
|||
private static String name = ""; |
|||
|
|||
/** 版本 */ |
|||
private static String version = ""; |
|||
|
|||
/** 版权 */ |
|||
private static String copyright = ""; |
|||
|
|||
/** 版权年份 */ |
|||
private static String copyrightYear = ""; |
|||
|
|||
/** 上传路径 */ |
|||
private static String profile = ""; |
|||
|
|||
/** 定时清除-数据的保留天数 */ |
|||
private static Integer dataCleanupRetentionDays = 30; |
|||
|
|||
/** 定时清除-清除数据的保存csv的批量处理数 */ |
|||
private static Integer dataBatchSize = 100; |
|||
|
|||
// 实例变量用于注入
|
|||
@Value("${app.name}") |
|||
private String tempName; |
|||
|
|||
@Value("${app.version}") |
|||
private String tempVersion; |
|||
|
|||
@Value("${app.copyright}") |
|||
private String tempCopyright; |
|||
|
|||
@Value("${app.copyrightYear}") |
|||
private String tempCopyrightYear; |
|||
|
|||
@Value("${app.profile}") |
|||
private String tempProfile; |
|||
|
|||
@Value("${app.data.cleanup.retention-days}") |
|||
private Integer tempDataCleanupRetentionDays; |
|||
|
|||
@Value("${app.data.batch-size}") |
|||
private Integer tempDataBatchSize; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
if (tempName != null) { |
|||
name = tempName; |
|||
} |
|||
|
|||
if (tempVersion != null) { |
|||
version = tempVersion; |
|||
} |
|||
|
|||
if (tempCopyright != null) { |
|||
copyright = tempCopyright; |
|||
} |
|||
|
|||
if (tempCopyrightYear != null) { |
|||
copyrightYear = tempCopyrightYear; |
|||
} |
|||
|
|||
if (tempProfile != null) { |
|||
profile = tempProfile; |
|||
} |
|||
|
|||
if (tempDataCleanupRetentionDays != null) { |
|||
dataCleanupRetentionDays = tempDataCleanupRetentionDays; |
|||
} |
|||
|
|||
if (tempDataBatchSize != null) { |
|||
dataBatchSize = tempDataBatchSize; |
|||
} |
|||
} |
|||
|
|||
public static String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public static void setName(String name) { |
|||
AppConfig.name = name; |
|||
} |
|||
|
|||
public static String getVersion() { |
|||
return version; |
|||
} |
|||
|
|||
public static void setVersion(String version) { |
|||
AppConfig.version = version; |
|||
} |
|||
|
|||
public static String getCopyright() { |
|||
return copyright; |
|||
} |
|||
|
|||
public static void setCopyright(String copyright) { |
|||
AppConfig.copyright = copyright; |
|||
} |
|||
|
|||
public static String getCopyrightYear() { |
|||
return copyrightYear; |
|||
} |
|||
|
|||
public static void setCopyrightYear(String copyrightYear) { |
|||
AppConfig.copyrightYear = copyrightYear; |
|||
} |
|||
|
|||
public static String getProfile() { |
|||
return profile; |
|||
} |
|||
|
|||
public static void setProfile(String profile) { |
|||
AppConfig.profile = profile; |
|||
} |
|||
|
|||
public static Integer getDataCleanupRetentionDays() { |
|||
return dataCleanupRetentionDays; |
|||
} |
|||
|
|||
public static void setDataCleanupRetentionDays(Integer dataCleanupRetentionDays) { |
|||
AppConfig.dataCleanupRetentionDays = dataCleanupRetentionDays; |
|||
} |
|||
|
|||
public static Integer getDataBatchSize() { |
|||
return dataBatchSize; |
|||
} |
|||
|
|||
public static void setDataBatchSize(Integer dataBatchSize) { |
|||
AppConfig.dataBatchSize = dataBatchSize; |
|||
} |
|||
|
|||
/** |
|||
* 获取导入上传路径 |
|||
*/ |
|||
public static String getImportPath() |
|||
{ |
|||
return getProfile() + "/import"; |
|||
} |
|||
|
|||
/** |
|||
* 获取头像上传路径 |
|||
*/ |
|||
public static String getAvatarPath() |
|||
{ |
|||
return getProfile() + "/avatar"; |
|||
} |
|||
|
|||
/** |
|||
* 获取下载路径 |
|||
*/ |
|||
public static String getDownloadPath() |
|||
{ |
|||
return getProfile() + "/download/"; |
|||
} |
|||
|
|||
/** |
|||
* 获取上传路径 |
|||
*/ |
|||
public static String getUploadPath() |
|||
{ |
|||
return getProfile() + "/upload"; |
|||
} |
|||
|
|||
/** |
|||
* 获取长期存储CSV数据文件路径 |
|||
*/ |
|||
public static String getCsvPath() |
|||
{ |
|||
return getProfile() + "/csv/"; |
|||
} |
|||
|
|||
/** |
|||
* 告警数据文件路径 |
|||
*/ |
|||
public static String getAlarmPath() |
|||
{ |
|||
return getProfile() + "/alarm/"; |
|||
} |
|||
|
|||
|
|||
// getter 和 setter for temp variables
|
|||
public String getTempName() { |
|||
return tempName; |
|||
} |
|||
|
|||
public void setTempName(String tempName) { |
|||
this.tempName = tempName; |
|||
} |
|||
|
|||
public String getTempVersion() { |
|||
return tempVersion; |
|||
} |
|||
|
|||
public void setTempVersion(String tempVersion) { |
|||
this.tempVersion = tempVersion; |
|||
} |
|||
|
|||
public String getTempCopyright() { |
|||
return tempCopyright; |
|||
} |
|||
|
|||
public void setTempCopyright(String tempCopyright) { |
|||
this.tempCopyright = tempCopyright; |
|||
} |
|||
|
|||
public String getTempCopyrightYear() { |
|||
return tempCopyrightYear; |
|||
} |
|||
|
|||
public void setTempCopyrightYear(String tempCopyrightYear) { |
|||
this.tempCopyrightYear = tempCopyrightYear; |
|||
} |
|||
|
|||
public String getTempProfile() { |
|||
return tempProfile; |
|||
} |
|||
|
|||
public void setTempProfile(String tempProfile) { |
|||
this.tempProfile = tempProfile; |
|||
} |
|||
|
|||
public Integer getTempDataCleanupRetentionDays() { |
|||
return tempDataCleanupRetentionDays; |
|||
} |
|||
|
|||
public void setTempDataCleanupRetentionDays(Integer tempDataCleanupRetentionDays) { |
|||
this.tempDataCleanupRetentionDays = tempDataCleanupRetentionDays; |
|||
} |
|||
|
|||
public Integer getTempDataBatchSize() { |
|||
return tempDataBatchSize; |
|||
} |
|||
|
|||
public void setTempDataBatchSize(Integer tempDataBatchSize) { |
|||
this.tempDataBatchSize = tempDataBatchSize; |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.mathvision.box.common.config; |
|||
|
|||
import com.mathvision.box.common.utils.common.ServletUtils; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:服务相关配置 |
|||
*/ |
|||
@Component |
|||
public class ServerConfig |
|||
{ |
|||
/** |
|||
* 获取完整的请求路径,包括:域名,端口,上下文访问路径 |
|||
* |
|||
* @return 服务地址 |
|||
*/ |
|||
public String getUrl() |
|||
{ |
|||
HttpServletRequest request = ServletUtils.getRequest(); |
|||
return getDomain(request); |
|||
} |
|||
|
|||
public static String getDomain(HttpServletRequest request) |
|||
{ |
|||
StringBuffer url = request.getRequestURL(); |
|||
String contextPath = request.getServletContext().getContextPath(); |
|||
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
package com.mathvision.box.common.constant; |
|||
|
|||
import com.google.common.collect.Lists; |
|||
import org.springframework.http.MediaType; |
|||
|
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/21 |
|||
* @Description:通用常量信息 |
|||
*/ |
|||
public class Constants { |
|||
public static final String SESSION_USER_KEY = "user"; |
|||
|
|||
public static final List<MediaType> SUPPORTED_MEDIA_TYPES = Lists.newArrayList( |
|||
MediaType.APPLICATION_JSON, |
|||
MediaType.APPLICATION_ATOM_XML, |
|||
MediaType.APPLICATION_FORM_URLENCODED, |
|||
MediaType.APPLICATION_OCTET_STREAM, |
|||
MediaType.APPLICATION_PDF, |
|||
MediaType.APPLICATION_RSS_XML, |
|||
MediaType.APPLICATION_XHTML_XML, |
|||
MediaType.APPLICATION_XML, |
|||
MediaType.IMAGE_GIF, |
|||
MediaType.IMAGE_JPEG, |
|||
MediaType.IMAGE_PNG, |
|||
MediaType.TEXT_EVENT_STREAM, |
|||
MediaType.TEXT_HTML, |
|||
MediaType.TEXT_MARKDOWN, |
|||
MediaType.TEXT_PLAIN, |
|||
MediaType.TEXT_XML |
|||
); |
|||
|
|||
/** |
|||
* UTF-8 字符集 |
|||
*/ |
|||
public static final String UTF8 = "UTF-8"; |
|||
|
|||
/** |
|||
* GBK 字符集 |
|||
*/ |
|||
public static final String GBK = "GBK"; |
|||
|
|||
/** |
|||
* 系统语言 |
|||
*/ |
|||
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; |
|||
|
|||
/** |
|||
* http请求 |
|||
*/ |
|||
public static final String HTTP = "http://"; |
|||
|
|||
/** |
|||
* https请求 |
|||
*/ |
|||
public static final String HTTPS = "https://"; |
|||
|
|||
/** |
|||
* 通用成功标识 |
|||
*/ |
|||
public static final String SUCCESS = "0"; |
|||
|
|||
/** |
|||
* 通用失败标识 |
|||
*/ |
|||
public static final String FAIL = "1"; |
|||
|
|||
/** |
|||
* 登录成功 |
|||
*/ |
|||
public static final String LOGIN_SUCCESS = "Success"; |
|||
|
|||
/** |
|||
* 注销 |
|||
*/ |
|||
public static final String LOGOUT = "Logout"; |
|||
|
|||
/** |
|||
* 注册 |
|||
*/ |
|||
public static final String REGISTER = "Register"; |
|||
|
|||
/** |
|||
* 登录失败 |
|||
*/ |
|||
public static final String LOGIN_FAIL = "Error"; |
|||
|
|||
/** |
|||
* 系统用户授权缓存 |
|||
*/ |
|||
public static final String SYS_AUTH_CACHE = "sys-authCache"; |
|||
|
|||
/** |
|||
* 参数管理 cache name |
|||
*/ |
|||
public static final String SYS_CONFIG_CACHE = "sys-config"; |
|||
|
|||
/** |
|||
* 参数管理 cache key |
|||
*/ |
|||
public static final String SYS_CONFIG_KEY = "sys_config:"; |
|||
|
|||
/** |
|||
* 字典管理 cache name |
|||
*/ |
|||
public static final String SYS_DICT_CACHE = "sys-dict"; |
|||
|
|||
/** |
|||
* 字典管理 cache key |
|||
*/ |
|||
public static final String SYS_DICT_KEY = "sys_dict:"; |
|||
|
|||
/** |
|||
* 资源映射路径 前缀 |
|||
*/ |
|||
public static final String RESOURCE_PREFIX = "/file"; |
|||
|
|||
/** |
|||
* RMI 远程方法调用 |
|||
*/ |
|||
public static final String LOOKUP_RMI = "rmi:"; |
|||
|
|||
/** |
|||
* LDAP 远程方法调用 |
|||
*/ |
|||
public static final String LOOKUP_LDAP = "ldap:"; |
|||
|
|||
/** |
|||
* LDAPS 远程方法调用 |
|||
*/ |
|||
public static final String LOOKUP_LDAPS = "ldaps:"; |
|||
|
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
package com.mathvision.box.common.core.controller; |
|||
|
|||
|
|||
import com.github.pagehelper.PageHelper; |
|||
import com.github.pagehelper.PageInfo; |
|||
import com.mathvision.box.common.core.domain.Result; |
|||
import com.mathvision.box.common.core.domain.ResultCode; |
|||
import com.mathvision.box.common.core.domain.entity.UserCache; |
|||
import com.mathvision.box.common.core.page.PageDomain; |
|||
import com.mathvision.box.common.core.page.TableDataInfo; |
|||
import com.mathvision.box.common.core.page.TableSupport; |
|||
import com.mathvision.box.common.utils.common.DateUtils; |
|||
import com.mathvision.box.common.utils.common.PageUtils; |
|||
import com.mathvision.box.common.utils.common.ServletUtils; |
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
import com.mathvision.box.common.utils.security.SessionHolder; |
|||
import com.mathvision.box.common.utils.sql.SqlUtil; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.bind.WebDataBinder; |
|||
import org.springframework.web.bind.annotation.InitBinder; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import javax.servlet.http.HttpSession; |
|||
import java.beans.PropertyEditorSupport; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:web层通用数据处理 |
|||
*/ |
|||
@Component |
|||
public class BaseController { |
|||
protected final Logger logger = LoggerFactory.getLogger(this.getClass()); |
|||
|
|||
|
|||
/** |
|||
* 将前台传递过来的日期格式的字符串,自动转化为Date类型 |
|||
*/ |
|||
@InitBinder |
|||
public void initBinder(WebDataBinder binder) { |
|||
// Date 类型转换
|
|||
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { |
|||
@Override |
|||
public void setAsText(String text) { |
|||
setValue(DateUtils.parseDate(text)); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 设置请求分页数据 |
|||
*/ |
|||
protected void startPage() { |
|||
PageUtils.startPage(); |
|||
} |
|||
|
|||
/** |
|||
* 设置请求排序数据 |
|||
*/ |
|||
protected void startOrderBy() { |
|||
PageDomain pageDomain = TableSupport.buildPageRequest(); |
|||
if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) { |
|||
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); |
|||
PageHelper.orderBy(orderBy); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 清理分页的线程变量 |
|||
*/ |
|||
protected void clearPage() { |
|||
PageUtils.clearPage(); |
|||
} |
|||
|
|||
/** |
|||
* 获取request |
|||
*/ |
|||
public HttpServletRequest getRequest() { |
|||
return ServletUtils.getRequest(); |
|||
} |
|||
|
|||
/** |
|||
* 获取response |
|||
*/ |
|||
public HttpServletResponse getResponse() { |
|||
return ServletUtils.getResponse(); |
|||
} |
|||
|
|||
/** |
|||
* 获取session |
|||
*/ |
|||
public HttpSession getSession() { |
|||
return getRequest().getSession(); |
|||
} |
|||
|
|||
/** |
|||
* 响应请求分页数据 |
|||
*/ |
|||
@SuppressWarnings({"rawtypes", "unchecked"}) |
|||
protected TableDataInfo getDataTable(List<?> list) { |
|||
TableDataInfo rspData = new TableDataInfo(); |
|||
rspData.setCode(0); |
|||
rspData.setRows(list); |
|||
rspData.setTotal(new PageInfo(list).getTotal()); |
|||
return rspData; |
|||
} |
|||
|
|||
protected TableDataInfo getDataTable(List<?> list, int total) { |
|||
TableDataInfo rspData = new TableDataInfo(); |
|||
rspData.setCode(0); |
|||
rspData.setRows(list); |
|||
rspData.setTotal(total); |
|||
return rspData; |
|||
} |
|||
|
|||
/** |
|||
* 响应返回结果 |
|||
* |
|||
* @param rows 影响行数 |
|||
* @return 操作结果 |
|||
*/ |
|||
protected Result toResult(int rows) { |
|||
return rows > 0 ? success() : error(); |
|||
} |
|||
|
|||
/** |
|||
* 响应返回结果 |
|||
* |
|||
* @param result 结果 |
|||
* @return 操作结果 |
|||
*/ |
|||
protected Result toResult(boolean result) { |
|||
return result ? success() : error(); |
|||
} |
|||
|
|||
/** |
|||
* 返回成功 |
|||
*/ |
|||
public Result success() { |
|||
return Result.SUCCESS; |
|||
} |
|||
|
|||
/** |
|||
* 返回失败消息 |
|||
*/ |
|||
public Result error() { |
|||
return Result.FAILURE; |
|||
} |
|||
|
|||
/** |
|||
* 返回成功消息 |
|||
*/ |
|||
public Result success(String message) { |
|||
return new Result(ResultCode.success, message); |
|||
} |
|||
|
|||
/** |
|||
* 返回成功数据 |
|||
*/ |
|||
public static Result success(Object data) { |
|||
return new Result(ResultCode.success, data); |
|||
} |
|||
|
|||
/** |
|||
* 返回失败消息 |
|||
*/ |
|||
public Result error(String message) { |
|||
return new Result(ResultCode.operate_failure.getCode(), message); |
|||
} |
|||
|
|||
/** |
|||
* 返回错误码消息 |
|||
*/ |
|||
public Result error(ResultCode code, String message) { |
|||
return new Result(code, message); |
|||
} |
|||
|
|||
/** |
|||
* 页面跳转 |
|||
*/ |
|||
public String redirect(String url) { |
|||
return StringUtils.format("redirect:{}", url); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户缓存信息 |
|||
*/ |
|||
public UserCache getUser() { |
|||
return SessionHolder.get(); |
|||
} |
|||
|
|||
/** |
|||
* 设置用户缓存信息 |
|||
*/ |
|||
public void setUser(UserCache user) { |
|||
SessionHolder.set(user); |
|||
} |
|||
|
|||
/** |
|||
* 获取登录用户id |
|||
*/ |
|||
public Long getUserId() { |
|||
return getUser().getUserId(); |
|||
} |
|||
|
|||
/** |
|||
* 获取登录用户名 |
|||
*/ |
|||
public String getLoginName() { |
|||
return getUser().getLoginName(); |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
package com.mathvision.box.common.core.domain; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.ToString; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/21 |
|||
* @Description:统一返回结果 |
|||
*/ |
|||
@ToString |
|||
public class Result<T> implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
public static final Result NOT_SUPPORT = new Result(ResultCode.not_support_operate); |
|||
public static final Result NOT_PRIVILEGED = new Result(ResultCode.not_privileged); |
|||
public static final Result INVALID_USER_PASS = new Result(ResultCode.invalidUserOrPassword); |
|||
public static final Result ILLEGAL_ARGUMENT = new Result(ResultCode.illegal_argument); |
|||
public static final Result FAILURE = new Result(ResultCode.operate_failure); |
|||
public static final Result ERROR = new Result(ResultCode.system_error); |
|||
public static final Result NOT_LOGIN = new Result(ResultCode.need_login); |
|||
public static final Result SUCCESS = new Result(ResultCode.success); |
|||
public static final Result INVALID_LICENSE = new Result(ResultCode.invalid_license); |
|||
|
|||
@Getter |
|||
@Setter |
|||
private int code; |
|||
@Getter |
|||
@Setter |
|||
private String desc; |
|||
@Getter |
|||
@Setter |
|||
private T body; |
|||
|
|||
public Result(ResultCode resultCode) { |
|||
setCode(resultCode.getCode()); |
|||
setDesc(resultCode.getDesc()); |
|||
} |
|||
|
|||
public Result(int code, String msg) { |
|||
setCode(code); |
|||
setDesc(msg); |
|||
} |
|||
|
|||
/** |
|||
* 使用通用结果码生成对象 |
|||
*/ |
|||
public Result(ResultCode resultCode, T body) { |
|||
setCode(resultCode.getCode()); |
|||
setDesc(resultCode.getDesc()); |
|||
setBody(body); |
|||
} |
|||
|
|||
/** |
|||
* 判断执行结果是否成功 |
|||
*/ |
|||
public boolean isSuccess() { |
|||
return ResultCode.success.getCode() == code; |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
package com.mathvision.box.common.core.domain; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/21 |
|||
* @Description:统一结果码定义 |
|||
*/ |
|||
public enum ResultCode { |
|||
success(200, "成功"), |
|||
|
|||
invalidUserOrPassword(300, "用户名或者密码错误!"), |
|||
illegal_argument(400, "参数错误!"), |
|||
need_login(401, "未登录!"), |
|||
not_support_operate(404, "不支持的请求!"), |
|||
not_privileged(405, "无权限执行该操作!"), |
|||
invalid_license(406, "您的证书无效,请核查服务器是否取得授权或重新申请证书!"), |
|||
system_error(500, "系统异常!"), |
|||
operate_failure(503, "操作失败!"); |
|||
|
|||
@Getter |
|||
private final int code; |
|||
@Getter |
|||
private final String desc; |
|||
|
|||
ResultCode(int code, String desc) { |
|||
this.code = code; |
|||
this.desc = desc; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
package com.mathvision.box.common.core.domain.entity; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.util.Set; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class UserCache { |
|||
private Long userId; |
|||
|
|||
private String loginName; |
|||
|
|||
private String userName; |
|||
|
|||
private Set<String> permissions; |
|||
|
|||
private Long timeStamp; |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
package com.mathvision.box.common.core.page; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:分页数据 |
|||
*/ |
|||
public class PageDomain |
|||
{ |
|||
/** 当前记录起始索引 */ |
|||
private Integer pageNum; |
|||
|
|||
/** 每页显示记录数 */ |
|||
private Integer pageSize; |
|||
|
|||
/** 排序列 */ |
|||
private String orderByColumn; |
|||
|
|||
/** 排序的方向desc或者asc */ |
|||
private String isAsc = "asc"; |
|||
|
|||
/** 分页参数合理化 */ |
|||
private Boolean reasonable = true; |
|||
|
|||
public String getOrderBy() |
|||
{ |
|||
if (StringUtils.isEmpty(orderByColumn)) |
|||
{ |
|||
return ""; |
|||
} |
|||
return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; |
|||
} |
|||
|
|||
public Integer getPageNum() |
|||
{ |
|||
return pageNum; |
|||
} |
|||
|
|||
public void setPageNum(Integer pageNum) |
|||
{ |
|||
this.pageNum = pageNum; |
|||
} |
|||
|
|||
public Integer getPageSize() |
|||
{ |
|||
return pageSize; |
|||
} |
|||
|
|||
public void setPageSize(Integer pageSize) |
|||
{ |
|||
this.pageSize = pageSize; |
|||
} |
|||
|
|||
public String getOrderByColumn() |
|||
{ |
|||
return orderByColumn; |
|||
} |
|||
|
|||
public void setOrderByColumn(String orderByColumn) |
|||
{ |
|||
this.orderByColumn = orderByColumn; |
|||
} |
|||
|
|||
public String getIsAsc() |
|||
{ |
|||
return isAsc; |
|||
} |
|||
|
|||
public void setIsAsc(String isAsc) |
|||
{ |
|||
this.isAsc = isAsc; |
|||
} |
|||
|
|||
public Boolean getReasonable() |
|||
{ |
|||
if (StringUtils.isNull(reasonable)) |
|||
{ |
|||
return Boolean.TRUE; |
|||
} |
|||
return reasonable; |
|||
} |
|||
|
|||
public void setReasonable(Boolean reasonable) |
|||
{ |
|||
this.reasonable = reasonable; |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
package com.mathvision.box.common.core.page; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.List; |
|||
|
|||
@ApiModel("表格分页数据对象") |
|||
public class TableDataInfo implements Serializable |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty("总记录数") |
|||
private long total; |
|||
|
|||
@ApiModelProperty("列表数据") |
|||
private List<?> rows; |
|||
|
|||
@ApiModelProperty("消息状态码") |
|||
private int code; |
|||
|
|||
@ApiModelProperty("消息内容") |
|||
private String msg; |
|||
|
|||
/** |
|||
* 表格数据对象 |
|||
*/ |
|||
public TableDataInfo() |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* 分页 |
|||
* |
|||
* @param list 列表数据 |
|||
* @param total 总记录数 |
|||
*/ |
|||
public TableDataInfo(List<?> list, int total) |
|||
{ |
|||
this.rows = list; |
|||
this.total = total; |
|||
} |
|||
|
|||
public long getTotal() |
|||
{ |
|||
return total; |
|||
} |
|||
|
|||
public void setTotal(long total) |
|||
{ |
|||
this.total = total; |
|||
} |
|||
|
|||
public List<?> getRows() |
|||
{ |
|||
return rows; |
|||
} |
|||
|
|||
public void setRows(List<?> rows) |
|||
{ |
|||
this.rows = rows; |
|||
} |
|||
|
|||
public int getCode() |
|||
{ |
|||
return code; |
|||
} |
|||
|
|||
public void setCode(int code) |
|||
{ |
|||
this.code = code; |
|||
} |
|||
|
|||
public String getMsg() |
|||
{ |
|||
return msg; |
|||
} |
|||
|
|||
public void setMsg(String msg) |
|||
{ |
|||
this.msg = msg; |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
package com.mathvision.box.common.core.page; |
|||
|
|||
|
|||
import com.mathvision.box.common.core.text.Convert; |
|||
import com.mathvision.box.common.utils.common.ServletUtils; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:表格数据处理 |
|||
*/ |
|||
public class TableSupport |
|||
{ |
|||
/** |
|||
* 当前记录起始索引 |
|||
*/ |
|||
public static final String PAGE_NUM = "pageNum"; |
|||
|
|||
/** |
|||
* 每页显示记录数 |
|||
*/ |
|||
public static final String PAGE_SIZE = "pageSize"; |
|||
|
|||
/** |
|||
* 排序列 |
|||
*/ |
|||
public static final String ORDER_BY_COLUMN = "orderByColumn"; |
|||
|
|||
/** |
|||
* 排序的方向 "desc" 或者 "asc". |
|||
*/ |
|||
public static final String IS_ASC = "isAsc"; |
|||
|
|||
/** |
|||
* 分页参数合理化 |
|||
*/ |
|||
public static final String REASONABLE = "reasonable"; |
|||
|
|||
/** |
|||
* 封装分页对象 |
|||
*/ |
|||
public static PageDomain getPageDomain() |
|||
{ |
|||
PageDomain pageDomain = new PageDomain(); |
|||
pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); |
|||
pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); |
|||
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); |
|||
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); |
|||
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); |
|||
return pageDomain; |
|||
} |
|||
|
|||
public static PageDomain buildPageRequest() |
|||
{ |
|||
return getPageDomain(); |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
package com.mathvision.box.common.core.text; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
import java.nio.charset.Charset; |
|||
import java.nio.charset.StandardCharsets; |
|||
|
|||
/** |
|||
* 字符集工具类 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class CharsetKit |
|||
{ |
|||
/** ISO-8859-1 */ |
|||
public static final String ISO_8859_1 = "ISO-8859-1"; |
|||
/** UTF-8 */ |
|||
public static final String UTF_8 = "UTF-8"; |
|||
/** GBK */ |
|||
public static final String GBK = "GBK"; |
|||
|
|||
/** ISO-8859-1 */ |
|||
public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); |
|||
/** UTF-8 */ |
|||
public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); |
|||
/** GBK */ |
|||
public static final Charset CHARSET_GBK = Charset.forName(GBK); |
|||
|
|||
/** |
|||
* 转换为Charset对象 |
|||
* |
|||
* @param charset 字符集,为空则返回默认字符集 |
|||
* @return Charset |
|||
*/ |
|||
public static Charset charset(String charset) |
|||
{ |
|||
return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); |
|||
} |
|||
|
|||
/** |
|||
* 转换字符串的字符集编码 |
|||
* |
|||
* @param source 字符串 |
|||
* @param srcCharset 源字符集,默认ISO-8859-1 |
|||
* @param destCharset 目标字符集,默认UTF-8 |
|||
* @return 转换后的字符集 |
|||
*/ |
|||
public static String convert(String source, String srcCharset, String destCharset) |
|||
{ |
|||
return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); |
|||
} |
|||
|
|||
/** |
|||
* 转换字符串的字符集编码 |
|||
* |
|||
* @param source 字符串 |
|||
* @param srcCharset 源字符集,默认ISO-8859-1 |
|||
* @param destCharset 目标字符集,默认UTF-8 |
|||
* @return 转换后的字符集 |
|||
*/ |
|||
public static String convert(String source, Charset srcCharset, Charset destCharset) |
|||
{ |
|||
if (null == srcCharset) |
|||
{ |
|||
srcCharset = StandardCharsets.ISO_8859_1; |
|||
} |
|||
|
|||
if (null == destCharset) |
|||
{ |
|||
destCharset = StandardCharsets.UTF_8; |
|||
} |
|||
|
|||
if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) |
|||
{ |
|||
return source; |
|||
} |
|||
return new String(source.getBytes(srcCharset), destCharset); |
|||
} |
|||
|
|||
/** |
|||
* @return 系统字符集编码 |
|||
*/ |
|||
public static String systemCharset() |
|||
{ |
|||
return Charset.defaultCharset().name(); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,93 @@ |
|||
package com.mathvision.box.common.core.text; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
/** |
|||
* 字符串格式化 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class StrFormatter |
|||
{ |
|||
public static final String EMPTY_JSON = "{}"; |
|||
public static final char C_BACKSLASH = '\\'; |
|||
public static final char C_DELIM_START = '{'; |
|||
public static final char C_DELIM_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 strPattern 字符串模板 |
|||
* @param argArray 参数列表 |
|||
* @return 结果 |
|||
*/ |
|||
public static String format(final String strPattern, final Object... argArray) |
|||
{ |
|||
if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) |
|||
{ |
|||
return strPattern; |
|||
} |
|||
final int strPatternLength = strPattern.length(); |
|||
|
|||
// 初始化定义好的长度以获得更好的性能
|
|||
StringBuilder sbuf = new StringBuilder(strPatternLength + 50); |
|||
|
|||
int handledPosition = 0; |
|||
int delimIndex;// 占位符所在位置
|
|||
for (int argIndex = 0; argIndex < argArray.length; argIndex++) |
|||
{ |
|||
delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); |
|||
if (delimIndex == -1) |
|||
{ |
|||
if (handledPosition == 0) |
|||
{ |
|||
return strPattern; |
|||
} |
|||
else |
|||
{ // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
|
|||
sbuf.append(strPattern, handledPosition, strPatternLength); |
|||
return sbuf.toString(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) |
|||
{ |
|||
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) |
|||
{ |
|||
// 转义符之前还有一个转义符,占位符依旧有效
|
|||
sbuf.append(strPattern, handledPosition, delimIndex - 1); |
|||
sbuf.append(Convert.utf8Str(argArray[argIndex])); |
|||
handledPosition = delimIndex + 2; |
|||
} |
|||
else |
|||
{ |
|||
// 占位符被转义
|
|||
argIndex--; |
|||
sbuf.append(strPattern, handledPosition, delimIndex - 1); |
|||
sbuf.append(C_DELIM_START); |
|||
handledPosition = delimIndex + 1; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// 正常占位符
|
|||
sbuf.append(strPattern, handledPosition, delimIndex); |
|||
sbuf.append(Convert.utf8Str(argArray[argIndex])); |
|||
handledPosition = delimIndex + 2; |
|||
} |
|||
} |
|||
} |
|||
// 加入最后一个占位符后所有的字符
|
|||
sbuf.append(strPattern, handledPosition, strPattern.length()); |
|||
|
|||
return sbuf.toString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
package com.mathvision.box.common.enums; |
|||
|
|||
public enum ConfigEnum { |
|||
ADMIN_USER_ID("1", "管理员用户ID"), |
|||
DEFAULT_ROLE_ID("3", "默认角色ID"), |
|||
DEFAULT_ORGANIZATION_ID("1", "总公司"), |
|||
DEFAULT_ROLE_IDS("1,2,3", "默认角色ID"), |
|||
; |
|||
|
|||
|
|||
private final String data; |
|||
private final String desc; |
|||
|
|||
ConfigEnum(String data, String desc) |
|||
{ |
|||
this.data = data; |
|||
this.desc = desc; |
|||
} |
|||
|
|||
public String getData() |
|||
{ |
|||
return data; |
|||
} |
|||
|
|||
public String getDesc() |
|||
{ |
|||
return desc; |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package com.mathvision.box.common.enums; |
|||
|
|||
public enum DefectStatusEnum { |
|||
unprocessed("0", "已完成/未处理"), |
|||
processed("1", "已处理"), |
|||
unfinished("2", "未完成"); |
|||
|
|||
private final String code; |
|||
private final String info; |
|||
|
|||
DefectStatusEnum(String code, String info) { |
|||
this.code = code; |
|||
this.info = info; |
|||
} |
|||
|
|||
public String getCode() { |
|||
return code; |
|||
} |
|||
|
|||
public String getInfo() { |
|||
return info; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
package com.mathvision.box.common.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
@Getter |
|||
@AllArgsConstructor |
|||
public enum DetectStatusEnum { |
|||
info("0", "正常"), |
|||
alert("1", "异常"), |
|||
fix("2", "修正"), |
|||
NULL("", "") |
|||
; |
|||
|
|||
private final String code; |
|||
private final String description; |
|||
|
|||
public static DetectStatusEnum getStatus(String code){ |
|||
for (DetectStatusEnum status: DetectStatusEnum.values() ) { |
|||
if (status.code.equals(code) || status.name().equals(code)){ |
|||
return status; |
|||
} |
|||
} |
|||
return NULL; |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
package com.mathvision.box.common.enums; |
|||
|
|||
public enum DeviceStatus { |
|||
ready, online, offline, error;//, working
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
package com.mathvision.box.common.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
@Getter |
|||
@AllArgsConstructor |
|||
public enum RecordType { |
|||
alarm("告警", true), device("设备", true), message("消息", true), capture("图片",false), page("页面", false), config("配置", false); |
|||
|
|||
private final String desc; |
|||
private final boolean isQueryable; |
|||
|
|||
public static RecordType getByName(String type) { |
|||
return Arrays.stream(RecordType.values()).filter(x -> x.name().equals(type)).findFirst().orElse(null); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return desc; |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
package com.mathvision.box.common.enums; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/04/02 |
|||
* @Description: |
|||
*/ |
|||
public enum SexEnum { |
|||
MAN("0", "男"), WOMAN("1", "女"), UNKNOWN("2", "未知"); |
|||
|
|||
private final String code; |
|||
private final String info; |
|||
|
|||
SexEnum(String code, String info) |
|||
{ |
|||
this.code = code; |
|||
this.info = info; |
|||
} |
|||
|
|||
public String getCode() |
|||
{ |
|||
return code; |
|||
} |
|||
|
|||
public String getInfo() |
|||
{ |
|||
return info; |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
package com.mathvision.box.common.exception; |
|||
|
|||
/** |
|||
* 自定义异常 |
|||
*/ |
|||
public class CustomException extends RuntimeException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private Integer code; |
|||
|
|||
private String message; |
|||
|
|||
public CustomException(String message) |
|||
{ |
|||
this.message = message; |
|||
} |
|||
|
|||
public CustomException(String message, Integer code) |
|||
{ |
|||
this.message = message; |
|||
this.code = code; |
|||
} |
|||
|
|||
public CustomException(String message, Throwable e) |
|||
{ |
|||
super(message, e); |
|||
this.message = message; |
|||
} |
|||
|
|||
@Override |
|||
public String getMessage() |
|||
{ |
|||
return message; |
|||
} |
|||
|
|||
public Integer getCode() |
|||
{ |
|||
return code; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package com.mathvision.box.common.exception; |
|||
|
|||
/** |
|||
* 演示模式异常 |
|||
*/ |
|||
public class DemoModeException extends RuntimeException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public DemoModeException() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
package com.mathvision.box.common.exception; |
|||
|
|||
/** |
|||
* 全局异常 |
|||
*/ |
|||
public class GlobalException extends RuntimeException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 错误提示 |
|||
*/ |
|||
private String message; |
|||
|
|||
/** |
|||
* 错误明细,内部调试错误 |
|||
* |
|||
* 和 {@link CommonResult#getDetailMessage()} 一致的设计 |
|||
*/ |
|||
private String detailMessage; |
|||
|
|||
/** |
|||
* 空构造方法,避免反序列化问题 |
|||
*/ |
|||
public GlobalException() |
|||
{ |
|||
} |
|||
|
|||
public GlobalException(String message) |
|||
{ |
|||
this.message = message; |
|||
} |
|||
|
|||
public String getDetailMessage() |
|||
{ |
|||
return detailMessage; |
|||
} |
|||
|
|||
public GlobalException setDetailMessage(String detailMessage) |
|||
{ |
|||
this.detailMessage = detailMessage; |
|||
return this; |
|||
} |
|||
|
|||
@Override |
|||
public String getMessage() |
|||
{ |
|||
return message; |
|||
} |
|||
|
|||
public GlobalException setMessage(String message) |
|||
{ |
|||
this.message = message; |
|||
return this; |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
package com.mathvision.box.common.exception; |
|||
|
|||
/** |
|||
* 业务异常 |
|||
*/ |
|||
public final class ServiceException extends RuntimeException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
/** |
|||
* 错误提示 |
|||
*/ |
|||
private String message; |
|||
|
|||
/** |
|||
* 错误明细,内部调试错误 |
|||
* |
|||
* 和 {@link CommonResult#getDetailMessage()} 一致的设计 |
|||
*/ |
|||
private String detailMessage; |
|||
|
|||
/** |
|||
* 空构造方法,避免反序列化问题 |
|||
*/ |
|||
public ServiceException() |
|||
{ |
|||
} |
|||
|
|||
public ServiceException(String message) |
|||
{ |
|||
this.message = message; |
|||
} |
|||
|
|||
public String getDetailMessage() |
|||
{ |
|||
return detailMessage; |
|||
} |
|||
|
|||
public ServiceException setDetailMessage(String detailMessage) |
|||
{ |
|||
this.detailMessage = detailMessage; |
|||
return this; |
|||
} |
|||
|
|||
@Override |
|||
public String getMessage() |
|||
{ |
|||
return message; |
|||
} |
|||
|
|||
public ServiceException setMessage(String message) |
|||
{ |
|||
this.message = message; |
|||
return this; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
package com.mathvision.box.common.exception; |
|||
|
|||
/** |
|||
* 工具类异常 |
|||
*/ |
|||
public class UtilException extends RuntimeException |
|||
{ |
|||
private static final long serialVersionUID = 8247610319171014183L; |
|||
|
|||
public UtilException(Throwable e) |
|||
{ |
|||
super(e.getMessage(), e); |
|||
} |
|||
|
|||
public UtilException(String message) |
|||
{ |
|||
super(message); |
|||
} |
|||
|
|||
public UtilException(String message, Throwable throwable) |
|||
{ |
|||
super(message, throwable); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
package com.mathvision.box.common.exception.base; |
|||
|
|||
import com.mathvision.box.common.core.domain.Result; |
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/21 |
|||
* @Description:通用异常 |
|||
*/ |
|||
@Getter |
|||
public class BaseException extends RuntimeException { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final Result result; |
|||
|
|||
public BaseException(Result result) { |
|||
this.result = result; |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package com.mathvision.box.common.exception.box; |
|||
|
|||
|
|||
import com.mathvision.box.common.core.domain.Result; |
|||
import com.mathvision.box.common.core.domain.ResultCode; |
|||
import com.mathvision.box.common.exception.base.BaseException; |
|||
|
|||
public class BoxException extends BaseException { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public BoxException(String code, Object[] args) { |
|||
super(new Result(Integer.parseInt(code), "[BoxException]:" + args.toString())); |
|||
} |
|||
|
|||
public BoxException(String msg) { |
|||
super(new Result( ResultCode.operate_failure.getCode() , "[BoxException]:" + msg)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package com.mathvision.box.common.exception.file; |
|||
|
|||
|
|||
import com.mathvision.box.common.core.domain.Result; |
|||
import com.mathvision.box.common.exception.base.BaseException; |
|||
|
|||
/** |
|||
* 文件信息异常类 |
|||
*/ |
|||
public class FileException extends BaseException { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public FileException(String code, Object[] args) { |
|||
super(new Result(Integer.parseInt(code), "[FileException]:" + args.toString())); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package com.mathvision.box.common.exception.file; |
|||
|
|||
/** |
|||
* 文件名称超长限制异常类 |
|||
* |
|||
*/ |
|||
public class FileNameLengthLimitExceededException extends FileException { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public FileNameLengthLimitExceededException(int defaultFileNameLength) { |
|||
super("upload.filename.exceed.length", new Object[]{defaultFileNameLength}); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.file; |
|||
|
|||
/** |
|||
* 文件名大小限制异常类 |
|||
*/ |
|||
public class FileSizeLimitExceededException extends FileException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public FileSizeLimitExceededException(long defaultMaxSize) |
|||
{ |
|||
super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
package com.mathvision.box.common.exception.file; |
|||
|
|||
import java.io.PrintStream; |
|||
import java.io.PrintWriter; |
|||
|
|||
/** |
|||
* 文件上传异常类 |
|||
*/ |
|||
public class FileUploadException extends Exception |
|||
{ |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final Throwable cause; |
|||
|
|||
public FileUploadException() |
|||
{ |
|||
this(null, null); |
|||
} |
|||
|
|||
public FileUploadException(final String msg) |
|||
{ |
|||
this(msg, null); |
|||
} |
|||
|
|||
public FileUploadException(String msg, Throwable cause) |
|||
{ |
|||
super(msg); |
|||
this.cause = cause; |
|||
} |
|||
|
|||
@Override |
|||
public void printStackTrace(PrintStream stream) |
|||
{ |
|||
super.printStackTrace(stream); |
|||
if (cause != null) |
|||
{ |
|||
stream.println("Caused by:"); |
|||
cause.printStackTrace(stream); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void printStackTrace(PrintWriter writer) |
|||
{ |
|||
super.printStackTrace(writer); |
|||
if (cause != null) |
|||
{ |
|||
writer.println("Caused by:"); |
|||
cause.printStackTrace(writer); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public Throwable getCause() |
|||
{ |
|||
return cause; |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
package com.mathvision.box.common.exception.file; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
/** |
|||
* 文件上传 误异常类 |
|||
*/ |
|||
public class InvalidExtensionException extends FileUploadException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private String[] allowedExtension; |
|||
private String extension; |
|||
private String filename; |
|||
|
|||
public InvalidExtensionException(String[] allowedExtension, String extension, String filename) |
|||
{ |
|||
super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); |
|||
this.allowedExtension = allowedExtension; |
|||
this.extension = extension; |
|||
this.filename = filename; |
|||
} |
|||
|
|||
public String[] getAllowedExtension() |
|||
{ |
|||
return allowedExtension; |
|||
} |
|||
|
|||
public String getExtension() |
|||
{ |
|||
return extension; |
|||
} |
|||
|
|||
public String getFilename() |
|||
{ |
|||
return filename; |
|||
} |
|||
|
|||
public static class InvalidImageExtensionException extends InvalidExtensionException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) |
|||
{ |
|||
super(allowedExtension, extension, filename); |
|||
} |
|||
} |
|||
|
|||
public static class InvalidFlashExtensionException extends InvalidExtensionException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) |
|||
{ |
|||
super(allowedExtension, extension, filename); |
|||
} |
|||
} |
|||
|
|||
public static class InvalidMediaExtensionException extends InvalidExtensionException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) |
|||
{ |
|||
super(allowedExtension, extension, filename); |
|||
} |
|||
} |
|||
|
|||
public static class InvalidVideoExtensionException extends InvalidExtensionException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) |
|||
{ |
|||
super(allowedExtension, extension, filename); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 黑名单IP异常类 |
|||
*/ |
|||
public class BlackListException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public BlackListException() |
|||
{ |
|||
super("login.blocked", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 验证码错误异常类 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class CaptchaException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public CaptchaException() |
|||
{ |
|||
super("user.jcaptcha.error", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 角色锁定异常类 |
|||
*/ |
|||
public class RoleBlockedException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public RoleBlockedException() |
|||
{ |
|||
super("role.blocked", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 用户锁定异常类 |
|||
*/ |
|||
public class UserBlockedException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserBlockedException() |
|||
{ |
|||
super("user.blocked", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 用户账号已被删除 |
|||
*/ |
|||
public class UserDeleteException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserDeleteException() |
|||
{ |
|||
super("user.password.delete", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
|
|||
import com.mathvision.box.common.core.domain.Result; |
|||
import com.mathvision.box.common.exception.base.BaseException; |
|||
|
|||
/** |
|||
* 用户信息异常类 |
|||
*/ |
|||
public class UserException extends BaseException { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserException(String code, Object[] args) { |
|||
super(new Result(Integer.parseInt(code), "[UserException]:" + args.toString())); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 用户不存在异常类 |
|||
*/ |
|||
public class UserNotExistsException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserNotExistsException() |
|||
{ |
|||
super("user.not.exists", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 用户密码不正确或不符合规范异常类 |
|||
*/ |
|||
public class UserPasswordNotMatchException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserPasswordNotMatchException() |
|||
{ |
|||
super("user.password.not.match", null); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 用户错误记数异常类 |
|||
*/ |
|||
public class UserPasswordRetryLimitCountException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserPasswordRetryLimitCountException(int retryLimitCount) |
|||
{ |
|||
super("user.password.retry.limit.count", new Object[] { retryLimitCount }); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.mathvision.box.common.exception.user; |
|||
|
|||
/** |
|||
* 用户错误最大次数异常类 |
|||
*/ |
|||
public class UserPasswordRetryLimitExceedException extends UserException |
|||
{ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public UserPasswordRetryLimitExceedException(int retryLimitCount) |
|||
{ |
|||
super("user.password.retry.limit.exceed", new Object[] { retryLimitCount }); |
|||
} |
|||
} |
|||
@ -0,0 +1,250 @@ |
|||
package com.mathvision.box.common.utils.aes; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.codec.binary.Base64; |
|||
import org.apache.commons.codec.binary.Hex; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.KeyGenerator; |
|||
import javax.crypto.SecretKey; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.Key; |
|||
import java.security.NoSuchAlgorithmException; |
|||
/** |
|||
* @Author: fy |
|||
* @Description:AES 加/解密工具类 |
|||
* 使用密钥时请使用 initKey() 方法来生成随机密钥 |
|||
* initKey 方法内部使用 java.crypto.KeyGenerator 密钥生成器来生成特定于 AES 算法参数集的随机密钥 |
|||
*/ |
|||
|
|||
@Slf4j |
|||
public class AesEncryptUtil { |
|||
|
|||
private AesEncryptUtil() { |
|||
} |
|||
|
|||
/** |
|||
* 密钥算法类型 |
|||
*/ |
|||
public static final String KEY_ALGORITHM = "AES"; |
|||
|
|||
/** |
|||
* 密钥的默认位长度 |
|||
*/ |
|||
public static final int DEFAULT_KEY_SIZE = 128; |
|||
|
|||
/** |
|||
* 密钥类型 Utf8 Base64 Hex |
|||
*/ |
|||
public static final String keyType = "Utf8"; |
|||
|
|||
/** |
|||
* 加解密算法/工作模式/填充方式 |
|||
*/ |
|||
private static final String ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding"; |
|||
public static final String ECB_NO_PADDING = "AES/ECB/NoPadding"; |
|||
|
|||
public static String base64Encode(byte[] bytes) { |
|||
return Base64.encodeBase64String(bytes); |
|||
} |
|||
|
|||
public static byte[] base64Decode(String base64Code) { |
|||
return Base64.decodeBase64(base64Code); |
|||
} |
|||
|
|||
public static byte[] aesEncryptToBytes(String content, String hexAesKey) throws Exception { |
|||
return encrypt(content.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(hexAesKey.toCharArray())); |
|||
} |
|||
|
|||
/** |
|||
* 加密 |
|||
* |
|||
* @param content 待加密内容 |
|||
* @param hexAesKey 密钥 |
|||
* @return String 加密后的密文 |
|||
*/ |
|||
public static String aesEncrypt(String content, String hexAesKey) throws Exception { |
|||
return base64Encode(aesEncryptToBytes(content, hexAesKey)); |
|||
} |
|||
|
|||
public static String aesDecryptByBytes(byte[] encryptBytes, String hexAesKey) throws Exception { |
|||
byte[] decrypt = decrypt(encryptBytes, Hex.decodeHex(hexAesKey.toCharArray())); |
|||
return new String(decrypt, StandardCharsets.UTF_8); |
|||
} |
|||
|
|||
/** |
|||
* 解密 |
|||
* |
|||
* @param encryptStr 密文 |
|||
* @param hexAesKey 密钥 |
|||
* @return String 明文 |
|||
*/ |
|||
public static String aesDecrypt(String encryptStr, String hexAesKey) throws Exception { |
|||
return aesDecryptByBytes(base64Decode(encryptStr), hexAesKey); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 生成 Hex 格式默认长度的随机密钥 |
|||
* 字符串长度为 32,解二进制后为 16 个字节 |
|||
* |
|||
* @return String Hex 格式的随机密钥 |
|||
*/ |
|||
public static String initHexKey() { |
|||
return Hex.encodeHexString(initKey()); |
|||
} |
|||
|
|||
/** |
|||
* 生成默认长度的随机密钥 |
|||
* 默认长度为 128 |
|||
* |
|||
* @return byte[] 二进制密钥 |
|||
*/ |
|||
public static byte[] initKey() { |
|||
return initKey(DEFAULT_KEY_SIZE); |
|||
} |
|||
|
|||
/** |
|||
* 生成密钥 |
|||
* 128、192、256 可选 |
|||
* |
|||
* @param keySize 密钥长度 |
|||
* @return byte[] 二进制密钥 |
|||
*/ |
|||
public static byte[] initKey(int keySize) { |
|||
// AES 要求密钥长度为 128 位、192 位或 256 位
|
|||
if (keySize != 128 && keySize != 192 && keySize != 256) { |
|||
throw new RuntimeException("error keySize: " + keySize); |
|||
} |
|||
// 实例化
|
|||
KeyGenerator keyGenerator; |
|||
try { |
|||
keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); |
|||
} catch (NoSuchAlgorithmException e) { |
|||
throw new RuntimeException("no such algorithm exception: " + KEY_ALGORITHM, e); |
|||
} |
|||
keyGenerator.init(keySize); |
|||
// 生成秘密密钥
|
|||
SecretKey secretKey = keyGenerator.generateKey(); |
|||
// 获得密钥的二进制编码形式
|
|||
return secretKey.getEncoded(); |
|||
} |
|||
|
|||
/** |
|||
* 转换密钥 |
|||
* |
|||
* @param key 二进制密钥 |
|||
* @return Key 密钥 |
|||
*/ |
|||
private static Key toKey(byte[] key) { |
|||
// 实例化 DES 密钥材料
|
|||
return new SecretKeySpec(key, KEY_ALGORITHM); |
|||
} |
|||
|
|||
/** |
|||
* 加密 |
|||
* |
|||
* @param data 待加密数据 |
|||
* @param key 密钥 |
|||
* @return byte[] 加密的数据 |
|||
*/ |
|||
public static byte[] encrypt(byte[] data, byte[] key) { |
|||
return encrypt(data, key, ECB_PKCS_5_PADDING); |
|||
} |
|||
|
|||
/** |
|||
* 加密 |
|||
* |
|||
* @param data 待加密数据 |
|||
* @param key 密钥 |
|||
* @param cipherAlgorithm 算法/工作模式/填充模式 |
|||
* @return byte[] 加密的数据 |
|||
*/ |
|||
public static byte[] encrypt(byte[] data, byte[] key, final String cipherAlgorithm) { |
|||
// 还原密钥
|
|||
Key k = toKey(key); |
|||
try { |
|||
Cipher cipher = Cipher.getInstance(cipherAlgorithm); |
|||
// 初始化,设置为加密模式
|
|||
cipher.init(Cipher.ENCRYPT_MODE, k); |
|||
|
|||
// 发现使用 NoPadding 时,使用 ZeroPadding 填充
|
|||
if (ECB_NO_PADDING.equals(cipherAlgorithm)) { |
|||
return cipher.doFinal(formatWithZeroPadding(data, cipher.getBlockSize())); |
|||
} |
|||
|
|||
// 执行操作
|
|||
return cipher.doFinal(data); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("AES encrypt error", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 解密 |
|||
* |
|||
* @param data 待解密数据 |
|||
* @param key 密钥 |
|||
* @return byte[] 解密的数据 |
|||
*/ |
|||
public static byte[] decrypt(byte[] data, byte[] key) { |
|||
return decrypt(data, key, ECB_PKCS_5_PADDING); |
|||
} |
|||
|
|||
/** |
|||
* 解密 |
|||
* |
|||
* @param data 待解密数据 |
|||
* @param key 密钥 |
|||
* @param cipherAlgorithm 算法/工作模式/填充模式 |
|||
* @return byte[] 解密的数据 |
|||
*/ |
|||
public static byte[] decrypt(byte[] data, byte[] key, final String cipherAlgorithm) { |
|||
// 还原密钥
|
|||
Key k = toKey(key); |
|||
try { |
|||
Cipher cipher = Cipher.getInstance(cipherAlgorithm); |
|||
// 初始化,设置为解密模式
|
|||
cipher.init(Cipher.DECRYPT_MODE, k); |
|||
|
|||
// 发现使用 NoPadding 时,使用 ZeroPadding 填充
|
|||
if (ECB_NO_PADDING.equals(cipherAlgorithm)) { |
|||
return removeZeroPadding(cipher.doFinal(data), cipher.getBlockSize()); |
|||
} |
|||
|
|||
// 执行操作
|
|||
return cipher.doFinal(data); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("AES解码失败", e); |
|||
} |
|||
} |
|||
|
|||
private static byte[] formatWithZeroPadding(byte[] data, final int blockSize) { |
|||
final int length = data.length; |
|||
final int remainLength = length % blockSize; |
|||
|
|||
if (remainLength > 0) { |
|||
byte[] inputData = new byte[length + blockSize - remainLength]; |
|||
System.arraycopy(data, 0, inputData, 0, length); |
|||
return inputData; |
|||
} |
|||
return data; |
|||
} |
|||
|
|||
private static byte[] removeZeroPadding(byte[] data, final int blockSize) { |
|||
final int length = data.length; |
|||
final int remainLength = length % blockSize; |
|||
if (remainLength == 0) { |
|||
// 解码后的数据正好是块大小的整数倍,说明可能存在补 0 的情况,去掉末尾所有的 0
|
|||
int i = length - 1; |
|||
while (i >= 0 && 0 == data[i]) { |
|||
i--; |
|||
} |
|||
byte[] outputData = new byte[i + 1]; |
|||
System.arraycopy(data, 0, outputData, 0, outputData.length); |
|||
return outputData; |
|||
} |
|||
return data; |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
package com.mathvision.box.common.utils.bean; |
|||
|
|||
import java.lang.reflect.Method; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/25 |
|||
* @Description: Bean 工具类 |
|||
*/ |
|||
public class BeanUtils extends org.springframework.beans.BeanUtils { |
|||
/** |
|||
* Bean方法名中属性名开始的下标 |
|||
*/ |
|||
private static final int BEAN_METHOD_PROP_INDEX = 3; |
|||
|
|||
/** |
|||
* 匹配getter方法的正则表达式 |
|||
*/ |
|||
private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); |
|||
|
|||
/** |
|||
* 匹配setter方法的正则表达式 |
|||
*/ |
|||
private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); |
|||
|
|||
/** |
|||
* Bean属性复制工具方法。 |
|||
* |
|||
* @param dest 目标对象 |
|||
* @param src 源对象 |
|||
*/ |
|||
public static void copyBeanProp(Object dest, Object src) { |
|||
try { |
|||
copyProperties(src, dest); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取对象的setter方法。 |
|||
* |
|||
* @param obj 对象 |
|||
* @return 对象的setter方法列表 |
|||
*/ |
|||
public static List<Method> getSetterMethods(Object obj) { |
|||
// setter方法列表
|
|||
List<Method> setterMethods = new ArrayList<Method>(); |
|||
|
|||
// 获取所有方法
|
|||
Method[] methods = obj.getClass().getMethods(); |
|||
|
|||
// 查找setter方法
|
|||
|
|||
for (Method method : methods) { |
|||
Matcher m = SET_PATTERN.matcher(method.getName()); |
|||
if (m.matches() && (method.getParameterTypes().length == 1)) { |
|||
setterMethods.add(method); |
|||
} |
|||
} |
|||
// 返回setter方法列表
|
|||
return setterMethods; |
|||
} |
|||
|
|||
/** |
|||
* 获取对象的getter方法。 |
|||
* |
|||
* @param obj 对象 |
|||
* @return 对象的getter方法列表 |
|||
*/ |
|||
|
|||
public static List<Method> getGetterMethods(Object obj) { |
|||
// getter方法列表
|
|||
List<Method> getterMethods = new ArrayList<Method>(); |
|||
// 获取所有方法
|
|||
Method[] methods = obj.getClass().getMethods(); |
|||
// 查找getter方法
|
|||
for (Method method : methods) { |
|||
Matcher m = GET_PATTERN.matcher(method.getName()); |
|||
if (m.matches() && (method.getParameterTypes().length == 0)) { |
|||
getterMethods.add(method); |
|||
} |
|||
} |
|||
// 返回getter方法列表
|
|||
return getterMethods; |
|||
} |
|||
|
|||
/** |
|||
* 检查Bean方法名中的属性名是否相等。<br> |
|||
* 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 |
|||
* |
|||
* @param m1 方法名1 |
|||
* @param m2 方法名2 |
|||
* @return 属性名一样返回true,否则返回false |
|||
*/ |
|||
|
|||
public static boolean isMethodPropEquals(String m1, String m2) { |
|||
return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); |
|||
} |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import java.math.BigDecimal; |
|||
|
|||
/** |
|||
* @description 提供精确的浮点数运算(包括加 、 减 、 乘 、 除 、 四舍五入)工具类 |
|||
*/ |
|||
public class BigDecimalUtil { |
|||
|
|||
// 除法运算默认精度
|
|||
private static final int DEF_DIV_SCALE = 10; |
|||
|
|||
private BigDecimalUtil() { |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 精确加法 |
|||
*/ |
|||
public static double add(double value1, double value2) { |
|||
BigDecimal b1 = BigDecimal.valueOf(value1); |
|||
BigDecimal b2 = BigDecimal.valueOf(value2); |
|||
return b1.add(b2).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确加法 |
|||
*/ |
|||
public static double add(String value1, String value2) { |
|||
BigDecimal b1 = new BigDecimal(value1); |
|||
BigDecimal b2 = new BigDecimal(value2); |
|||
return b1.add(b2).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确减法 |
|||
*/ |
|||
public static double sub(double value1, double value2) { |
|||
BigDecimal b1 = BigDecimal.valueOf(value1); |
|||
BigDecimal b2 = BigDecimal.valueOf(value2); |
|||
return b1.subtract(b2).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确减法 |
|||
*/ |
|||
public static double sub(String value1, String value2) { |
|||
BigDecimal b1 = new BigDecimal(value1); |
|||
BigDecimal b2 = new BigDecimal(value2); |
|||
return b1.subtract(b2).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确乘法 |
|||
*/ |
|||
public static double mul(double value1, double value2) { |
|||
BigDecimal b1 = BigDecimal.valueOf(value1); |
|||
BigDecimal b2 = BigDecimal.valueOf(value2); |
|||
return b1.multiply(b2).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确乘法 |
|||
*/ |
|||
public static double mul(String value1, String value2) { |
|||
BigDecimal b1 = new BigDecimal(value1); |
|||
BigDecimal b2 = new BigDecimal(value2); |
|||
return b1.multiply(b2).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确除法 使用默认精度 |
|||
*/ |
|||
public static double div(double value1, double value2) throws IllegalAccessException { |
|||
return div(value1, value2, DEF_DIV_SCALE); |
|||
} |
|||
|
|||
/** |
|||
* 精确除法 使用默认精度 |
|||
*/ |
|||
public static double div(String value1, String value2) throws IllegalAccessException { |
|||
return div(value1, value2, DEF_DIV_SCALE); |
|||
} |
|||
|
|||
/** |
|||
* 精确除法 |
|||
* |
|||
* @param scale 精度 |
|||
*/ |
|||
public static double div(double value1, double value2, int scale) throws IllegalAccessException { |
|||
if (scale < 0) { |
|||
throw new IllegalAccessException("精确度不能小于0"); |
|||
} |
|||
BigDecimal b1 = BigDecimal.valueOf(value1); |
|||
BigDecimal b2 = BigDecimal.valueOf(value2); |
|||
// return b1.divide(b2, scale).doubleValue();
|
|||
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 精确除法 |
|||
* |
|||
* @param scale 精度 |
|||
*/ |
|||
public static double div(String value1, String value2, int scale) throws IllegalAccessException { |
|||
if (scale < 0) { |
|||
throw new IllegalAccessException("精确度不能小于0"); |
|||
} |
|||
BigDecimal b1 = new BigDecimal(value1); |
|||
BigDecimal b2 = new BigDecimal(value2); |
|||
// return b1.divide(b2, scale).doubleValue();
|
|||
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); |
|||
} |
|||
|
|||
/** |
|||
* 四舍五入 |
|||
* |
|||
* @param scale 小数点后保留几位 |
|||
*/ |
|||
public static double round(double v, int scale) throws IllegalAccessException { |
|||
return div(v, 1, scale); |
|||
} |
|||
|
|||
/** |
|||
* 四舍五入 |
|||
* |
|||
* @param scale 小数点后保留几位 |
|||
*/ |
|||
public static double round(String v, int scale) throws IllegalAccessException { |
|||
return div(v, "1", scale); |
|||
} |
|||
|
|||
/** |
|||
* 比较大小 |
|||
*/ |
|||
public static boolean equalTo(BigDecimal b1, BigDecimal b2) { |
|||
if (b1 == null || b2 == null) { |
|||
return false; |
|||
} |
|||
return 0 == b1.compareTo(b2); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
@Slf4j |
|||
public class CMDUtils { |
|||
/** |
|||
* 执行命令行 |
|||
* |
|||
* @param cmd 命令行 |
|||
* @return |
|||
*/ |
|||
public static boolean executeLinuxCmd(String cmd) { |
|||
try { |
|||
Process process = Runtime.getRuntime().exec(cmd); |
|||
process.waitFor(); |
|||
} catch (InterruptedException e) { |
|||
log.error("executeLinuxCmd 执行Linux命令异常:", e); |
|||
Thread.currentThread().interrupt(); |
|||
return false; |
|||
} catch (IOException e) { |
|||
log.error("获取系统命令执行环境异常", e); |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,365 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
|
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.util.Iterator; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
|
|||
/** |
|||
* Cache缓存工具类 |
|||
*/ |
|||
public class CacheUtils { |
|||
private static Logger logger = LoggerFactory.getLogger(CacheUtils.class); |
|||
|
|||
private static final String SYS_CACHE = "sys-cache"; |
|||
|
|||
|
|||
/** |
|||
* 全局缓存(本地),可切换成redis |
|||
*/ |
|||
private static final Map<String, Map<String, Object>> caches = new ConcurrentHashMap<>(); |
|||
|
|||
/** |
|||
* 获取SYS_CACHE缓存 |
|||
* |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
public static Object get(String key) { |
|||
return get(SYS_CACHE, key); |
|||
} |
|||
|
|||
/** |
|||
* 获取SYS_CACHE缓存 |
|||
* |
|||
* @param key |
|||
* @param defaultValue |
|||
* @return |
|||
*/ |
|||
public static Object get(String key, Object defaultValue) { |
|||
Object value = get(SYS_CACHE, key); |
|||
return value != null ? value : defaultValue; |
|||
} |
|||
|
|||
/** |
|||
* 写入SYS_CACHE缓存 |
|||
* |
|||
* @param key |
|||
* @param value |
|||
*/ |
|||
public static void put(String key, Object value) { |
|||
put(SYS_CACHE, key, value); |
|||
} |
|||
|
|||
/** |
|||
* 从SYS_CACHE缓存中移除 |
|||
* |
|||
* @param key |
|||
*/ |
|||
public static void remove(String key) { |
|||
remove(SYS_CACHE, key); |
|||
} |
|||
|
|||
/** |
|||
* 获取缓存 |
|||
* |
|||
* @param cacheName |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
public static Object get(String cacheName, String key) { |
|||
return getCache(cacheName).get(getKey(key)); |
|||
} |
|||
|
|||
/** |
|||
* 获取缓存 |
|||
* |
|||
* @param cacheName |
|||
* @param key |
|||
* @param defaultValue |
|||
* @return |
|||
*/ |
|||
public static Object get(String cacheName, String key, Object defaultValue) { |
|||
Object value = get(cacheName, key); |
|||
return value != null ? value : defaultValue; |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* |
|||
* @param cacheName |
|||
* @param key |
|||
* @param value |
|||
*/ |
|||
public static void put(String cacheName, String key, Object value) { |
|||
if (StringUtils.isNotBlank(key) && value != null) { |
|||
getCache(cacheName).put(getKey(key), value); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从缓存中移除 |
|||
* |
|||
* @param cacheName |
|||
* @param key |
|||
*/ |
|||
public static void remove(String cacheName, String key) { |
|||
getCache(cacheName).remove(getKey(key)); |
|||
} |
|||
|
|||
/** |
|||
* 从缓存中移除所有 |
|||
* |
|||
* @param cacheName |
|||
*/ |
|||
public static void removeAll(String cacheName) { |
|||
Map<String, Object> cache = getCache(cacheName); |
|||
Set<String> keys = cache.keySet(); |
|||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { |
|||
cache.remove(it.next()); |
|||
} |
|||
logger.info("清理缓存: {} => {}", cacheName, keys); |
|||
} |
|||
|
|||
/** |
|||
* 从缓存中移除指定key |
|||
* |
|||
* @param keys |
|||
*/ |
|||
public static void removeByKeys(Set<String> keys) { |
|||
removeByKeys(SYS_CACHE, keys); |
|||
} |
|||
|
|||
/** |
|||
* 从缓存中移除指定key |
|||
* |
|||
* @param cacheName |
|||
* @param keys |
|||
*/ |
|||
public static void removeByKeys(String cacheName, Set<String> keys) { |
|||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) { |
|||
remove(cacheName, it.next()); |
|||
} |
|||
logger.info("清理缓存: {} => {}", cacheName, keys); |
|||
} |
|||
|
|||
/** |
|||
* 获取缓存键名 |
|||
* |
|||
* @param key |
|||
* @return |
|||
*/ |
|||
private static String getKey(String key) { |
|||
return key; |
|||
} |
|||
|
|||
/** |
|||
* 获得一个Cache,没有则创建一个新的Cache。 |
|||
* |
|||
* @param cacheName |
|||
* @return |
|||
*/ |
|||
public static Map<String, Object> getCache(String cacheName) { |
|||
return caches.computeIfAbsent(cacheName, k -> new ConcurrentHashMap<>()); |
|||
} |
|||
|
|||
public static String getNameById(String cacheName, Long id) { |
|||
if (id != null && caches.containsKey(cacheName) && caches.get(cacheName).containsKey(id.toString())) { |
|||
return CacheUtils.get(cacheName, id.toString(), "").toString(); |
|||
} |
|||
return ""; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 获取所有缓存名称 |
|||
* |
|||
* @return 缓存组 |
|||
*/ |
|||
public static Set<String> getCacheNames() { |
|||
return caches.keySet(); |
|||
} |
|||
|
|||
// private static Logger logger = LoggerFactory.getLogger(CacheUtils.class);
|
|||
//
|
|||
// private static CacheManager cacheManager = SpringUtils.getBean(CacheManager.class);
|
|||
//
|
|||
// private static final String SYS_CACHE = "sys-cache";
|
|||
//
|
|||
// /**
|
|||
// * 获取SYS_CACHE缓存
|
|||
// *
|
|||
// * @param key
|
|||
// * @return
|
|||
// */
|
|||
// public static Object get(String key)
|
|||
// {
|
|||
// return get(SYS_CACHE, key);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 获取SYS_CACHE缓存
|
|||
// *
|
|||
// * @param key
|
|||
// * @param defaultValue
|
|||
// * @return
|
|||
// */
|
|||
// public static Object get(String key, Object defaultValue)
|
|||
// {
|
|||
// Object value = get(key);
|
|||
// return value != null ? value : defaultValue;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 写入SYS_CACHE缓存
|
|||
// *
|
|||
// * @param key
|
|||
// * @return
|
|||
// */
|
|||
// public static void put(String key, Object value)
|
|||
// {
|
|||
// put(SYS_CACHE, key, value);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 从SYS_CACHE缓存中移除
|
|||
// *
|
|||
// * @param key
|
|||
// * @return
|
|||
// */
|
|||
// public static void remove(String key)
|
|||
// {
|
|||
// remove(SYS_CACHE, key);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 获取缓存
|
|||
// *
|
|||
// * @param cacheName
|
|||
// * @param key
|
|||
// * @return
|
|||
// */
|
|||
// public static Object get(String cacheName, String key)
|
|||
// {
|
|||
// return getCache(cacheName).get(getKey(key));
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 获取缓存
|
|||
// *
|
|||
// * @param cacheName
|
|||
// * @param key
|
|||
// * @param defaultValue
|
|||
// * @return
|
|||
// */
|
|||
// public static Object get(String cacheName, String key, Object defaultValue)
|
|||
// {
|
|||
// Object value = get(cacheName, getKey(key));
|
|||
// return value != null ? value : defaultValue;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 写入缓存
|
|||
// *
|
|||
// * @param cacheName
|
|||
// * @param key
|
|||
// * @param value
|
|||
// */
|
|||
// public static void put(String cacheName, String key, Object value)
|
|||
// {
|
|||
// getCache(cacheName).put(getKey(key), value);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 从缓存中移除
|
|||
// *
|
|||
// * @param cacheName
|
|||
// * @param key
|
|||
// */
|
|||
// public static void remove(String cacheName, String key)
|
|||
// {
|
|||
// getCache(cacheName).remove(getKey(key));
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 从缓存中移除所有
|
|||
// *
|
|||
// * @param cacheName
|
|||
// */
|
|||
// public static void removeAll(String cacheName)
|
|||
// {
|
|||
// Cache<String, Object> cache = getCache(cacheName);
|
|||
// Set<String> keys = cache.keys();
|
|||
// for (Iterator<String> it = keys.iterator(); it.hasNext();)
|
|||
// {
|
|||
// cache.remove(it.next());
|
|||
// }
|
|||
// logger.info("清理缓存: {} => {}", cacheName, keys);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 从缓存中移除指定key
|
|||
// *
|
|||
// * @param keys
|
|||
// */
|
|||
// public static void removeByKeys(Set<String> keys)
|
|||
// {
|
|||
// removeByKeys(SYS_CACHE, keys);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 从缓存中移除指定key
|
|||
// *
|
|||
// * @param cacheName
|
|||
// * @param keys
|
|||
// */
|
|||
// public static void removeByKeys(String cacheName, Set<String> keys)
|
|||
// {
|
|||
// for (Iterator<String> it = keys.iterator(); it.hasNext();)
|
|||
// {
|
|||
// remove(it.next());
|
|||
// }
|
|||
// logger.info("清理缓存: {} => {}", cacheName, keys);
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 获取缓存键名
|
|||
// *
|
|||
// * @param key
|
|||
// * @return
|
|||
// */
|
|||
// private static String getKey(String key)
|
|||
// {
|
|||
// return key;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 获得一个Cache,没有则显示日志。
|
|||
// *
|
|||
// * @param cacheName
|
|||
// * @return
|
|||
// */
|
|||
// public static Cache<String, Object> getCache(String cacheName)
|
|||
// {
|
|||
// Cache<String, Object> cache = cacheManager.getCache(cacheName);
|
|||
// if (cache == null)
|
|||
// {
|
|||
// throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。");
|
|||
// }
|
|||
// return cache;
|
|||
// }
|
|||
//
|
|||
// /**
|
|||
// * 获取所有缓存
|
|||
// *
|
|||
// * @return 缓存组
|
|||
// */
|
|||
// public static String[] getCacheNames()
|
|||
// {
|
|||
// return ((EhCacheManager) cacheManager).getCacheManager().getCacheNames();
|
|||
// }
|
|||
} |
|||
@ -0,0 +1,264 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
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.Calendar; |
|||
import java.util.Date; |
|||
import java.util.TimeZone; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:时间工具类 |
|||
*/ |
|||
public class DateUtils extends org.apache.commons.lang3.time.DateUtils |
|||
{ |
|||
public static String YYYY = "yyyy"; |
|||
|
|||
public static String YYYY_MM = "yyyy-MM"; |
|||
|
|||
public static String YYYY_MM_DD = "yyyy-MM-dd"; |
|||
|
|||
public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; |
|||
|
|||
public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; |
|||
|
|||
private static String[] parsePatterns = { |
|||
"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", |
|||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" |
|||
}; |
|||
|
|||
/** |
|||
* 获取当前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 final String getTime() |
|||
{ |
|||
return dateTimeNow(YYYY_MM_DD_HH_MM_SS); |
|||
} |
|||
|
|||
public static final String dateTimeNow() |
|||
{ |
|||
return dateTimeNow(YYYYMMDDHHMMSS); |
|||
} |
|||
|
|||
public static final String dateTimeNow(final String format) |
|||
{ |
|||
return parseDateToStr(format, new Date()); |
|||
} |
|||
|
|||
public static final String dateTime(final Date date) |
|||
{ |
|||
return parseDateToStr(YYYY_MM_DD, date); |
|||
} |
|||
|
|||
public static final String parseDateToStr(final String format, final Date date) |
|||
{ |
|||
return new SimpleDateFormat(format).format(date); |
|||
} |
|||
|
|||
public static final 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 final String datePath() |
|||
{ |
|||
Date now = new Date(); |
|||
return DateFormatUtils.format(now, "yyyy/MM/dd"); |
|||
} |
|||
|
|||
/** |
|||
* 日期路径 即年/月/日 如20180808 |
|||
*/ |
|||
public static final 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(), parsePatterns); |
|||
} |
|||
catch (ParseException e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static Date parseDate(String dateString, String format) |
|||
{ |
|||
SimpleDateFormat dateFormat = new SimpleDateFormat(format); |
|||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); |
|||
|
|||
try { |
|||
return dateFormat.parse(dateString); |
|||
} catch (ParseException e) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取服务器启动时间 |
|||
*/ |
|||
public static Date getServerStartDate() |
|||
{ |
|||
long time = ManagementFactory.getRuntimeMXBean().getStartTime(); |
|||
return new Date(time); |
|||
} |
|||
|
|||
/** |
|||
* 计算相差月数 |
|||
*/ |
|||
public static int getMonthDiff(Date date1, Date date2) |
|||
{ |
|||
Calendar c1 = Calendar.getInstance(); |
|||
Calendar c2 = Calendar.getInstance(); |
|||
c1.setTime(date1); |
|||
c2.setTime(date2); |
|||
int year1 = c1.get(Calendar.YEAR); |
|||
int year2 = c2.get(Calendar.YEAR); |
|||
int month1 = c1.get(Calendar.MONTH); |
|||
int month2 = c2.get(Calendar.MONTH); |
|||
// 获取年的差值
|
|||
int yearInterval = year1 - year2; |
|||
// 获取月数差值
|
|||
int monthInterval = month1 - month2; |
|||
int monthsDiff = yearInterval * 12 + monthInterval; |
|||
return monthsDiff; |
|||
} |
|||
|
|||
/** |
|||
* 计算相差天数 |
|||
*/ |
|||
public static int differentDaysByMillisecond(Date date1, Date date2) |
|||
{ |
|||
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); |
|||
} |
|||
|
|||
/** |
|||
* 计算相差小时 |
|||
*/ |
|||
public static long getHourDiff(Date endDate, Date startDate) |
|||
{ |
|||
if (endDate == null || startDate == null) |
|||
{ |
|||
return 0; |
|||
} |
|||
long diffInMillis = endDate.getTime() - startDate.getTime(); |
|||
double diffInHours = (double) diffInMillis / (1000 * 60 * 60); |
|||
//四舍五入
|
|||
return Math.round(diffInHours); |
|||
} |
|||
|
|||
/** |
|||
* 计算时间差 |
|||
* |
|||
* @param endDate 最后时间 |
|||
* @param startTime 开始时间 |
|||
* @return 时间差(天/小时/分钟) |
|||
*/ |
|||
public static String timeDistance(Date endDate, Date startTime) |
|||
{ |
|||
long nd = 1000 * 24 * 60 * 60; |
|||
long nh = 1000 * 60 * 60; |
|||
long nm = 1000 * 60; |
|||
// long ns = 1000;
|
|||
// 获得两个时间的毫秒时间差异
|
|||
long diff = endDate.getTime() - startTime.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()); |
|||
} |
|||
|
|||
public static Date getDayStartTime(Date date, boolean isStart) |
|||
{ |
|||
if (date == null){ |
|||
date = new Date(); |
|||
} |
|||
if (isStart){ |
|||
LocalDateTime startDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); |
|||
LocalDateTime modifiedStartDateTime = startDateTime.toLocalDate().atStartOfDay(); |
|||
return Date.from(modifiedStartDateTime.atZone(ZoneId.systemDefault()).toInstant()); |
|||
}else { |
|||
LocalDateTime endDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); |
|||
LocalDateTime modifiedEndDateTime = endDateTime.toLocalDate().atTime(23, 59, 59); |
|||
return Date.from(modifiedEndDateTime.atZone(ZoneId.systemDefault()).toInstant()); |
|||
} |
|||
} |
|||
|
|||
public static boolean isWeekend(Date date) |
|||
{ |
|||
Calendar calendar = Calendar.getInstance(); |
|||
calendar.setTime(date); |
|||
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); |
|||
return dayOfWeek == Calendar.SUNDAY || dayOfWeek == Calendar.SATURDAY; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
|
|||
import org.apache.commons.lang3.exception.ExceptionUtils; |
|||
|
|||
import java.io.PrintWriter; |
|||
import java.io.StringWriter; |
|||
|
|||
/** |
|||
* 错误信息处理类。 |
|||
* |
|||
*/ |
|||
public class ExceptionUtil |
|||
{ |
|||
/** |
|||
* 获取exception的详细错误信息。 |
|||
*/ |
|||
public static String getExceptionMessage(Throwable e) |
|||
{ |
|||
StringWriter sw = new StringWriter(); |
|||
e.printStackTrace(new PrintWriter(sw, true)); |
|||
return sw.toString(); |
|||
} |
|||
|
|||
public static String getRootErrorMessage(Exception e) |
|||
{ |
|||
Throwable root = ExceptionUtils.getRootCause(e); |
|||
root = (root == null ? e : root); |
|||
if (root == null) |
|||
{ |
|||
return ""; |
|||
} |
|||
String msg = root.getMessage(); |
|||
if (msg == null) |
|||
{ |
|||
return "null"; |
|||
} |
|||
return StringUtils.defaultString(msg); |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import org.springframework.util.DigestUtils; |
|||
|
|||
import java.security.SecureRandom; |
|||
import java.util.Base64; |
|||
|
|||
/** |
|||
* Created with IntelliJ IDEA. |
|||
* |
|||
* @Author: fy |
|||
* @Date: 2023/04/19/10:09 |
|||
* @Description:MD5密码加密 |
|||
*/ |
|||
public class MD5Utils { |
|||
public static String md5(String src) { |
|||
return DigestUtils.md5DigestAsHex(src.getBytes()); |
|||
} |
|||
|
|||
//第一次加密
|
|||
public static String inputPassToFormPass(String inputPass, String salt) { |
|||
//md5加密密码前,先对密码进行处理,按以下salt的规则处理密码
|
|||
String str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4); |
|||
return md5(str); |
|||
} |
|||
|
|||
//第二次加密
|
|||
public static String formPassToDBPass(String formPass, String salt) { |
|||
String str = "" + salt.charAt(0) + salt.charAt(2) + formPass + salt.charAt(5) + salt.charAt(4); |
|||
return md5(str); |
|||
} |
|||
|
|||
//实际调用的方法,将第一次加密和第二次加密合并,结果应该一致
|
|||
public static String inputPassToDBPass(String inputPass, String salt) { |
|||
String formPass = inputPassToFormPass(inputPass, salt); |
|||
String dbPass = formPassToDBPass(formPass, salt); |
|||
return dbPass; |
|||
} |
|||
|
|||
public static String randomSalt() { |
|||
int length = 16; |
|||
SecureRandom random = new SecureRandom(); |
|||
byte[] salt = new byte[length]; |
|||
random.nextBytes(salt); |
|||
return Base64.getEncoder().encodeToString(salt); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
public class OSUtils { |
|||
public static String getOsName() { |
|||
return System.getProperty("os.name").toLowerCase(); |
|||
} |
|||
|
|||
public static boolean isWindows() { |
|||
return getOsName().contains("win"); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import com.github.pagehelper.PageHelper; |
|||
import com.mathvision.box.common.core.page.PageDomain; |
|||
import com.mathvision.box.common.core.page.TableSupport; |
|||
import com.mathvision.box.common.utils.sql.SqlUtil; |
|||
|
|||
public class PageUtils extends PageHelper { |
|||
/** |
|||
* 设置请求分页数据 |
|||
*/ |
|||
public static void startPage() |
|||
{ |
|||
PageDomain pageDomain = TableSupport.buildPageRequest(); |
|||
Integer pageNum = pageDomain.getPageNum(); |
|||
Integer pageSize = pageDomain.getPageSize(); |
|||
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); |
|||
Boolean reasonable = pageDomain.getReasonable(); |
|||
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); |
|||
} |
|||
|
|||
/** |
|||
* 清理分页的线程变量 |
|||
*/ |
|||
public static void clearPage() |
|||
{ |
|||
PageHelper.clearPage(); |
|||
} |
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import cn.hutool.core.convert.Convert; |
|||
import com.mathvision.box.common.constant.Constants; |
|||
import org.springframework.web.context.request.RequestAttributes; |
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import javax.servlet.http.HttpSession; |
|||
import java.io.IOException; |
|||
import java.io.UnsupportedEncodingException; |
|||
import java.net.URLDecoder; |
|||
import java.net.URLEncoder; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:客户端工具类 |
|||
*/ |
|||
public class ServletUtils |
|||
{ |
|||
/** |
|||
* 定义移动端请求的所有可能类型 |
|||
*/ |
|||
private final static String[] agent = { "Android", "iPhone", "iPod", "iPad", "Windows Phone", "MQQBrowser" }; |
|||
|
|||
/** |
|||
* 获取String参数 |
|||
*/ |
|||
public static String getParameter(String name) |
|||
{ |
|||
return getRequest().getParameter(name); |
|||
} |
|||
|
|||
/** |
|||
* 获取String参数 |
|||
*/ |
|||
public static String getParameter(String name, String defaultValue) |
|||
{ |
|||
return Convert.toStr(getRequest().getParameter(name), defaultValue); |
|||
} |
|||
|
|||
/** |
|||
* 获取Integer参数 |
|||
*/ |
|||
public static Integer getParameterToInt(String name) |
|||
{ |
|||
return Convert.toInt(getRequest().getParameter(name)); |
|||
} |
|||
|
|||
/** |
|||
* 获取Integer参数 |
|||
*/ |
|||
public static Integer getParameterToInt(String name, Integer defaultValue) |
|||
{ |
|||
return Convert.toInt(getRequest().getParameter(name), defaultValue); |
|||
} |
|||
|
|||
/** |
|||
* 获取Boolean参数 |
|||
*/ |
|||
public static Boolean getParameterToBool(String name) |
|||
{ |
|||
return Convert.toBool(getRequest().getParameter(name)); |
|||
} |
|||
|
|||
/** |
|||
* 获取Boolean参数 |
|||
*/ |
|||
public static Boolean getParameterToBool(String name, Boolean defaultValue) |
|||
{ |
|||
return Convert.toBool(getRequest().getParameter(name), defaultValue); |
|||
} |
|||
|
|||
/** |
|||
* 获取request |
|||
*/ |
|||
public static HttpServletRequest getRequest() |
|||
{ |
|||
return getRequestAttributes().getRequest(); |
|||
} |
|||
|
|||
/** |
|||
* 获取response |
|||
*/ |
|||
public static HttpServletResponse getResponse() |
|||
{ |
|||
return getRequestAttributes().getResponse(); |
|||
} |
|||
|
|||
/** |
|||
* 获取session |
|||
*/ |
|||
public static HttpSession getSession() |
|||
{ |
|||
return getRequest().getSession(); |
|||
} |
|||
|
|||
public static ServletRequestAttributes getRequestAttributes() |
|||
{ |
|||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); |
|||
return (ServletRequestAttributes) attributes; |
|||
} |
|||
|
|||
/** |
|||
* 将字符串渲染到客户端 |
|||
* |
|||
* @param response 渲染对象 |
|||
* @param string 待渲染的字符串 |
|||
* @return null |
|||
*/ |
|||
public static String renderString(HttpServletResponse response, String string) |
|||
{ |
|||
try |
|||
{ |
|||
response.setContentType("application/json"); |
|||
response.setCharacterEncoding("utf-8"); |
|||
response.getWriter().print(string); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
e.printStackTrace(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 是否是Ajax异步请求 |
|||
* |
|||
* @param request |
|||
*/ |
|||
public static boolean isAjaxRequest(HttpServletRequest request) |
|||
{ |
|||
String accept = request.getHeader("accept"); |
|||
if (accept != null && accept.contains("application/json")) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
String xRequestedWith = request.getHeader("X-Requested-With"); |
|||
if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
String uri = request.getRequestURI(); |
|||
if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
String ajax = request.getParameter("__ajax"); |
|||
return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); |
|||
} |
|||
|
|||
/** |
|||
* 判断User-Agent 是不是来自于手机 |
|||
*/ |
|||
public static boolean checkAgentIsMobile(String ua) |
|||
{ |
|||
boolean flag = false; |
|||
if (!ua.contains("Windows NT") || (ua.contains("Windows NT") && ua.contains("compatible; MSIE 9.0;"))) |
|||
{ |
|||
// 排除 苹果桌面系统
|
|||
if (!ua.contains("Windows NT") && !ua.contains("Macintosh")) |
|||
{ |
|||
for (String item : agent) |
|||
{ |
|||
if (ua.contains(item)) |
|||
{ |
|||
flag = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return flag; |
|||
} |
|||
|
|||
/** |
|||
* 内容编码 |
|||
* |
|||
* @param str 内容 |
|||
* @return 编码后的内容 |
|||
*/ |
|||
public static String urlEncode(String str) |
|||
{ |
|||
try |
|||
{ |
|||
return URLEncoder.encode(str, Constants.UTF8); |
|||
} |
|||
catch (UnsupportedEncodingException e) |
|||
{ |
|||
return StringUtils.EMPTY; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 内容解码 |
|||
* |
|||
* @param str 内容 |
|||
* @return 解码后的内容 |
|||
*/ |
|||
public static String urlDecode(String str) |
|||
{ |
|||
try |
|||
{ |
|||
return URLDecoder.decode(str, Constants.UTF8); |
|||
} |
|||
catch (UnsupportedEncodingException e) |
|||
{ |
|||
return StringUtils.EMPTY; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断当前是否通过 HTTP 请求调用 |
|||
* |
|||
* @return 如果是 HTTP 请求则返回 true,否则返回 false |
|||
*/ |
|||
public static boolean isHttpRequest() { |
|||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); |
|||
if (requestAttributes == null || ! (requestAttributes instanceof ServletRequestAttributes)){ |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,631 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import com.mathvision.box.common.constant.Constants; |
|||
import com.mathvision.box.common.core.text.StrFormatter; |
|||
import org.springframework.util.AntPathMatcher; |
|||
|
|||
import java.util.*; |
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description: |
|||
*/ |
|||
|
|||
/** |
|||
* 字符串工具类 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class StringUtils extends org.apache.commons.lang3.StringUtils |
|||
{ |
|||
/** 空字符串 */ |
|||
private static final String NULLSTR = ""; |
|||
|
|||
/** 下划线 */ |
|||
private static final char SEPARATOR = '_'; |
|||
|
|||
/** |
|||
* 获取参数不为空值 |
|||
* |
|||
* @param value defaultValue 要判断的value |
|||
* @return value 返回值 |
|||
*/ |
|||
public static <T> T nvl(T value, T defaultValue) |
|||
{ |
|||
return value != null ? value : defaultValue; |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个Collection是否为空, 包含List,Set,Queue |
|||
* |
|||
* @param coll 要判断的Collection |
|||
* @return true:为空 false:非空 |
|||
*/ |
|||
public static boolean isEmpty(Collection<?> coll) |
|||
{ |
|||
return isNull(coll) || coll.isEmpty(); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个Collection是否非空,包含List,Set,Queue |
|||
* |
|||
* @param coll 要判断的Collection |
|||
* @return true:非空 false:空 |
|||
*/ |
|||
public static boolean isNotEmpty(Collection<?> coll) |
|||
{ |
|||
return !isEmpty(coll); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个对象数组是否为空 |
|||
* |
|||
* @param objects 要判断的对象数组 |
|||
** @return true:为空 false:非空 |
|||
*/ |
|||
public static boolean isEmpty(Object[] objects) |
|||
{ |
|||
return isNull(objects) || (objects.length == 0); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个对象数组是否非空 |
|||
* |
|||
* @param objects 要判断的对象数组 |
|||
* @return true:非空 false:空 |
|||
*/ |
|||
public static boolean isNotEmpty(Object[] objects) |
|||
{ |
|||
return !isEmpty(objects); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个Map是否为空 |
|||
* |
|||
* @param map 要判断的Map |
|||
* @return true:为空 false:非空 |
|||
*/ |
|||
public static boolean isEmpty(Map<?, ?> map) |
|||
{ |
|||
return isNull(map) || map.isEmpty(); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个Map是否为空 |
|||
* |
|||
* @param map 要判断的Map |
|||
* @return true:非空 false:空 |
|||
*/ |
|||
public static boolean isNotEmpty(Map<?, ?> map) |
|||
{ |
|||
return !isEmpty(map); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个字符串是否为空串 |
|||
* |
|||
* @param str String |
|||
* @return true:为空 false:非空 |
|||
*/ |
|||
public static boolean isEmpty(String str) |
|||
{ |
|||
return isNull(str) || NULLSTR.equals(str.trim()); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个字符串是否为非空串 |
|||
* |
|||
* @param str String |
|||
* @return true:非空串 false:空串 |
|||
*/ |
|||
public static boolean isNotEmpty(String str) |
|||
{ |
|||
return !isEmpty(str); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个对象是否为空 |
|||
* |
|||
* @param object Object |
|||
* @return true:为空 false:非空 |
|||
*/ |
|||
public static boolean isNull(Object object) |
|||
{ |
|||
return object == null; |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个对象是否非空 |
|||
* |
|||
* @param object Object |
|||
* @return true:非空 false:空 |
|||
*/ |
|||
public static boolean isNotNull(Object object) |
|||
{ |
|||
return !isNull(object); |
|||
} |
|||
|
|||
/** |
|||
* * 判断一个对象是否是数组类型(Java基本型别的数组) |
|||
* |
|||
* @param object 对象 |
|||
* @return true:是数组 false:不是数组 |
|||
*/ |
|||
public static boolean isArray(Object object) |
|||
{ |
|||
return isNotNull(object) && object.getClass().isArray(); |
|||
} |
|||
|
|||
/** |
|||
* 去空格 |
|||
*/ |
|||
public static String trim(String str) |
|||
{ |
|||
return (str == null ? "" : str.trim()); |
|||
} |
|||
|
|||
/** |
|||
* 截取字符串 |
|||
* |
|||
* @param str 字符串 |
|||
* @param start 开始 |
|||
* @return 结果 |
|||
*/ |
|||
public static String substring(final String str, int start) |
|||
{ |
|||
if (str == null) |
|||
{ |
|||
return NULLSTR; |
|||
} |
|||
|
|||
if (start < 0) |
|||
{ |
|||
start = str.length() + start; |
|||
} |
|||
|
|||
if (start < 0) |
|||
{ |
|||
start = 0; |
|||
} |
|||
if (start > str.length()) |
|||
{ |
|||
return NULLSTR; |
|||
} |
|||
|
|||
return str.substring(start); |
|||
} |
|||
|
|||
/** |
|||
* 截取字符串 |
|||
* |
|||
* @param str 字符串 |
|||
* @param start 开始 |
|||
* @param end 结束 |
|||
* @return 结果 |
|||
*/ |
|||
public static String substring(final String str, int start, int end) |
|||
{ |
|||
if (str == null) |
|||
{ |
|||
return NULLSTR; |
|||
} |
|||
|
|||
if (end < 0) |
|||
{ |
|||
end = str.length() + end; |
|||
} |
|||
if (start < 0) |
|||
{ |
|||
start = str.length() + start; |
|||
} |
|||
|
|||
if (end > str.length()) |
|||
{ |
|||
end = str.length(); |
|||
} |
|||
|
|||
if (start > end) |
|||
{ |
|||
return NULLSTR; |
|||
} |
|||
|
|||
if (start < 0) |
|||
{ |
|||
start = 0; |
|||
} |
|||
if (end < 0) |
|||
{ |
|||
end = 0; |
|||
} |
|||
|
|||
return str.substring(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) |
|||
{ |
|||
if (isEmpty(params) || isEmpty(template)) |
|||
{ |
|||
return template; |
|||
} |
|||
return StrFormatter.format(template, params); |
|||
} |
|||
|
|||
/** |
|||
* 是否为http(s)://开头
|
|||
* |
|||
* @param link 链接 |
|||
* @return 结果 |
|||
*/ |
|||
public static boolean ishttp(String link) |
|||
{ |
|||
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); |
|||
} |
|||
|
|||
/** |
|||
* 字符串转set |
|||
* |
|||
* @param str 字符串 |
|||
* @param sep 分隔符 |
|||
* @return set集合 |
|||
*/ |
|||
public static final Set<String> str2Set(String str, String sep) |
|||
{ |
|||
return new HashSet<String>(str2List(str, sep, true, false)); |
|||
} |
|||
|
|||
/** |
|||
* 字符串转list |
|||
* |
|||
* @param str 字符串 |
|||
* @param sep 分隔符 |
|||
* @param filterBlank 过滤纯空白 |
|||
* @param trim 去掉首尾空白 |
|||
* @return list集合 |
|||
*/ |
|||
public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) |
|||
{ |
|||
List<String> list = new ArrayList<String>(); |
|||
if (StringUtils.isEmpty(str)) |
|||
{ |
|||
return list; |
|||
} |
|||
|
|||
// 过滤空白字符串
|
|||
if (filterBlank && StringUtils.isBlank(str)) |
|||
{ |
|||
return list; |
|||
} |
|||
String[] split = str.split(sep); |
|||
for (String string : split) |
|||
{ |
|||
if (filterBlank && StringUtils.isBlank(string)) |
|||
{ |
|||
continue; |
|||
} |
|||
if (trim) |
|||
{ |
|||
string = string.trim(); |
|||
} |
|||
list.add(string); |
|||
} |
|||
|
|||
return list; |
|||
} |
|||
|
|||
/** |
|||
* 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value |
|||
* |
|||
* @param collection 给定的集合 |
|||
* @param array 给定的数组 |
|||
* @return boolean 结果 |
|||
*/ |
|||
public static boolean containsAny(Collection<String> collection, String... array) |
|||
{ |
|||
if (isEmpty(collection) || isEmpty(array)) |
|||
{ |
|||
return false; |
|||
} |
|||
else |
|||
{ |
|||
for (String str : array) |
|||
{ |
|||
if (collection.contains(str)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 |
|||
* |
|||
* @param cs 指定字符串 |
|||
* @param searchCharSequences 需要检查的字符串数组 |
|||
* @return 是否包含任意一个字符串 |
|||
*/ |
|||
public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) |
|||
{ |
|||
if (isEmpty(cs) || isEmpty(searchCharSequences)) |
|||
{ |
|||
return false; |
|||
} |
|||
for (CharSequence testStr : searchCharSequences) |
|||
{ |
|||
if (containsIgnoreCase(cs, testStr)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 驼峰转下划线命名 |
|||
*/ |
|||
public static String toUnderScoreCase(String str) |
|||
{ |
|||
if (str == null) |
|||
{ |
|||
return null; |
|||
} |
|||
StringBuilder sb = new StringBuilder(); |
|||
// 前置字符是否大写
|
|||
boolean preCharIsUpperCase = true; |
|||
// 当前字符是否大写
|
|||
boolean curreCharIsUpperCase = true; |
|||
// 下一字符是否大写
|
|||
boolean nexteCharIsUpperCase = true; |
|||
for (int i = 0; i < str.length(); i++) |
|||
{ |
|||
char c = str.charAt(i); |
|||
if (i > 0) |
|||
{ |
|||
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); |
|||
} |
|||
else |
|||
{ |
|||
preCharIsUpperCase = false; |
|||
} |
|||
|
|||
curreCharIsUpperCase = Character.isUpperCase(c); |
|||
|
|||
if (i < (str.length() - 1)) |
|||
{ |
|||
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); |
|||
} |
|||
|
|||
if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) |
|||
{ |
|||
sb.append(SEPARATOR); |
|||
} |
|||
else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) |
|||
{ |
|||
sb.append(SEPARATOR); |
|||
} |
|||
sb.append(Character.toLowerCase(c)); |
|||
} |
|||
|
|||
return sb.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 是否包含字符串 |
|||
* |
|||
* @param str 验证字符串 |
|||
* @param strs 字符串组 |
|||
* @return 包含返回true |
|||
*/ |
|||
public static boolean inStringIgnoreCase(String str, String... strs) |
|||
{ |
|||
if (str != null && strs != null) |
|||
{ |
|||
for (String s : strs) |
|||
{ |
|||
if (str.equalsIgnoreCase(trim(s))) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 删除最后一个字符串 |
|||
* |
|||
* @param str 输入字符串 |
|||
* @param spit 以什么类型结尾的 |
|||
* @return 截取后的字符串 |
|||
*/ |
|||
public static String lastStringDel(String str, String spit) |
|||
{ |
|||
if (!StringUtils.isEmpty(str) && str.endsWith(spit)) |
|||
{ |
|||
return str.subSequence(0, str.length() - 1).toString(); |
|||
} |
|||
return str; |
|||
} |
|||
|
|||
/** |
|||
* 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld |
|||
* |
|||
* @param name 转换前的下划线大写方式命名的字符串 |
|||
* @return 转换后的驼峰式命名的字符串 |
|||
*/ |
|||
public static String convertToCamelCase(String name) |
|||
{ |
|||
StringBuilder result = new StringBuilder(); |
|||
// 快速检查
|
|||
if (name == null || name.isEmpty()) |
|||
{ |
|||
// 没必要转换
|
|||
return ""; |
|||
} |
|||
else if (!name.contains("_")) |
|||
{ |
|||
// 不含下划线,仅将首字母大写
|
|||
return name.substring(0, 1).toUpperCase() + name.substring(1); |
|||
} |
|||
// 用下划线将原始字符串分割
|
|||
String[] camels = name.split("_"); |
|||
for (String camel : camels) |
|||
{ |
|||
// 跳过原始字符串中开头、结尾的下换线或双重下划线
|
|||
if (camel.isEmpty()) |
|||
{ |
|||
continue; |
|||
} |
|||
// 首字母大写
|
|||
result.append(camel.substring(0, 1).toUpperCase()); |
|||
result.append(camel.substring(1).toLowerCase()); |
|||
} |
|||
return result.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 驼峰式命名法 |
|||
* 例如:user_name->userName |
|||
*/ |
|||
public static String toCamelCase(String s) |
|||
{ |
|||
if (s == null) |
|||
{ |
|||
return null; |
|||
} |
|||
if (s.indexOf(SEPARATOR) == -1) |
|||
{ |
|||
return s; |
|||
} |
|||
s = s.toLowerCase(); |
|||
StringBuilder sb = new StringBuilder(s.length()); |
|||
boolean upperCase = false; |
|||
for (int i = 0; i < s.length(); i++) |
|||
{ |
|||
char c = s.charAt(i); |
|||
|
|||
if (c == SEPARATOR) |
|||
{ |
|||
upperCase = true; |
|||
} |
|||
else if (upperCase) |
|||
{ |
|||
sb.append(Character.toUpperCase(c)); |
|||
upperCase = false; |
|||
} |
|||
else |
|||
{ |
|||
sb.append(c); |
|||
} |
|||
} |
|||
return sb.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 |
|||
* |
|||
* @param str 指定字符串 |
|||
* @param strs 需要检查的字符串数组 |
|||
* @return 是否匹配 |
|||
*/ |
|||
public static boolean matches(String str, List<String> strs) |
|||
{ |
|||
if (isEmpty(str) || isEmpty(strs)) |
|||
{ |
|||
return false; |
|||
} |
|||
for (String pattern : strs) |
|||
{ |
|||
if (isMatch(pattern, str)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 判断url是否与规则配置: |
|||
* ? 表示单个字符; |
|||
* * 表示一层路径内的任意字符串,不可跨层级; |
|||
* ** 表示任意层路径; |
|||
* |
|||
* @param pattern 匹配规则 |
|||
* @param url 需要匹配的url |
|||
* @return |
|||
*/ |
|||
public static boolean isMatch(String pattern, String url) |
|||
{ |
|||
AntPathMatcher matcher = new AntPathMatcher(); |
|||
return matcher.match(pattern, url); |
|||
} |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
public static <T> T cast(Object obj) |
|||
{ |
|||
return (T) obj; |
|||
} |
|||
|
|||
/** |
|||
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 |
|||
* |
|||
* @param num 数字对象 |
|||
* @param size 字符串指定长度 |
|||
* @return 返回数字的字符串格式,该字符串为指定长度。 |
|||
*/ |
|||
public static final 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 final 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) |
|||
{ |
|||
for (int i = size - len; i > 0; i--) |
|||
{ |
|||
sb.append(c); |
|||
} |
|||
sb.append(s); |
|||
} |
|||
else |
|||
{ |
|||
return s.substring(len - size, len); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = size; i > 0; i--) |
|||
{ |
|||
sb.append(c); |
|||
} |
|||
} |
|||
return sb.toString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
package com.mathvision.box.common.utils.common; |
|||
|
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.util.concurrent.*; |
|||
|
|||
/** |
|||
* 线程相关工具类. |
|||
*/ |
|||
public class Threads |
|||
{ |
|||
private static final Logger logger = LoggerFactory.getLogger(Threads.class); |
|||
|
|||
/** |
|||
* sleep等待,单位为毫秒 |
|||
*/ |
|||
public static void sleep(long milliseconds) |
|||
{ |
|||
try |
|||
{ |
|||
Thread.sleep(milliseconds); |
|||
} |
|||
catch (InterruptedException e) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 停止线程池 |
|||
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. |
|||
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. |
|||
* 如果仍人超時,則強制退出. |
|||
* 另对在shutdown时线程本身被调用中断做了处理. |
|||
*/ |
|||
public static void shutdownAndAwaitTermination(ExecutorService pool) |
|||
{ |
|||
if (pool != null && !pool.isShutdown()) |
|||
{ |
|||
pool.shutdown(); |
|||
try |
|||
{ |
|||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) |
|||
{ |
|||
pool.shutdownNow(); |
|||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) |
|||
{ |
|||
logger.info("Pool did not terminate"); |
|||
} |
|||
} |
|||
} |
|||
catch (InterruptedException ie) |
|||
{ |
|||
pool.shutdownNow(); |
|||
Thread.currentThread().interrupt(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 打印线程异常信息 |
|||
*/ |
|||
public static void printException(Runnable r, Throwable t) |
|||
{ |
|||
if (t == null && r instanceof Future<?>) |
|||
{ |
|||
try |
|||
{ |
|||
Future<?> future = (Future<?>) r; |
|||
if (future.isDone()) |
|||
{ |
|||
future.get(); |
|||
} |
|||
} |
|||
catch (CancellationException ce) |
|||
{ |
|||
t = ce; |
|||
} |
|||
catch (ExecutionException ee) |
|||
{ |
|||
t = ee.getCause(); |
|||
} |
|||
catch (InterruptedException ie) |
|||
{ |
|||
Thread.currentThread().interrupt(); |
|||
} |
|||
} |
|||
if (t != null) |
|||
{ |
|||
logger.error(t.getMessage(), t); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,131 @@ |
|||
package com.mathvision.box.common.utils.csv; |
|||
|
|||
import com.mathvision.box.common.annotation.CsvColumn; |
|||
import com.mathvision.box.common.utils.common.DateUtils; |
|||
import com.mathvision.box.common.utils.file.FileUtils; |
|||
import com.opencsv.CSVWriter; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.util.ReflectionUtils; |
|||
|
|||
import java.io.FileWriter; |
|||
import java.io.IOException; |
|||
import java.lang.reflect.Field; |
|||
import java.util.ArrayList; |
|||
import java.util.Comparator; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
@Slf4j |
|||
public class CsvUtils { |
|||
/** |
|||
* 导出数据到 CSV 文件 |
|||
* @param data 数据列表 |
|||
* @param headers CSV 表头 |
|||
* @param fieldNames 对应的字段名 |
|||
* @param filePath 文件保存路径 |
|||
* @param <T> 数据类型 |
|||
*/ |
|||
private static <T> void exportToCsv(List<T> data, String[] headers, String[] fieldNames, String filePath) { |
|||
try { |
|||
FileUtils.createFile(filePath); |
|||
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) { |
|||
// 写入表头
|
|||
writer.writeNext(headers); |
|||
|
|||
// 写入数据
|
|||
for (T item : data) { |
|||
String[] row = new String[fieldNames.length]; |
|||
for (int i = 0; i < fieldNames.length; i++) { |
|||
row[i] = getFieldValue(item, fieldNames[i]); |
|||
} |
|||
writer.writeNext(row); |
|||
} |
|||
|
|||
// 确保数据写入
|
|||
writer.flush(); |
|||
|
|||
log.info("[CsvUtils]:CSV file created successfully: {}", filePath); |
|||
} |
|||
} catch (IOException e) { |
|||
log.error("Error exporting CSV file: ", e); |
|||
throw new RuntimeException("Failed to export CSV file", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 使用反射获取对象字段值 |
|||
*/ |
|||
private static <T> String getFieldValue(T item, String fieldName) { |
|||
try { |
|||
Field field = ReflectionUtils.findField(item.getClass(), fieldName); |
|||
if (field != null) { |
|||
field.setAccessible(true); |
|||
Object value = field.get(item); |
|||
if (value != null && value instanceof Date){ |
|||
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, (Date) value); |
|||
} |
|||
return value != null ? value.toString() : ""; |
|||
} |
|||
return ""; |
|||
} catch (Exception e) { |
|||
log.error("[CsvUtils]:Error getting field value: ", e); |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据注解自动导出 CSV |
|||
* @param data 数据列表 |
|||
* @param filePath 文件保存路径 |
|||
* @param <T> 数据类型 |
|||
*/ |
|||
public static <T> void exportToCsvWithAnnotation(List<T> data, String filePath) { |
|||
if (data.isEmpty()) { |
|||
return; |
|||
} |
|||
|
|||
Class<?> clazz = data.get(0).getClass(); |
|||
List<CsvField> csvFields = getCsvFields(clazz); |
|||
|
|||
String[] headers = csvFields.stream() |
|||
.map(CsvField::getHeader) |
|||
.toArray(String[]::new); |
|||
|
|||
String[] fieldNames = csvFields.stream() |
|||
.map(CsvField::getField) |
|||
.toArray(String[]::new); |
|||
|
|||
exportToCsv(data, headers, fieldNames, filePath); |
|||
} |
|||
|
|||
/** |
|||
* 获取类中标注了 @CsvColumn 的字段信息 |
|||
*/ |
|||
private static List<CsvField> getCsvFields(Class<?> clazz) { |
|||
List<CsvField> fields = new ArrayList<>(); |
|||
ReflectionUtils.doWithFields(clazz, field -> { |
|||
CsvColumn annotation = field.getAnnotation(CsvColumn.class); |
|||
if (annotation != null) { |
|||
fields.add(new CsvField( |
|||
annotation.header(), |
|||
field.getName(), |
|||
annotation.order() |
|||
)); |
|||
} |
|||
}); |
|||
|
|||
// 按 order 排序
|
|||
fields.sort(Comparator.comparingInt(CsvField::getOrder)); |
|||
return fields; |
|||
} |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
private static class CsvField { |
|||
private String header; |
|||
private String field; |
|||
private int order; |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
package com.mathvision.box.common.utils.file; |
|||
|
|||
import com.mathvision.box.common.utils.common.OSUtils; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.nio.file.FileStore; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.nio.file.Paths; |
|||
|
|||
@Slf4j |
|||
public class DiskUtils { |
|||
/** |
|||
* 检查磁盘使用率 |
|||
* Windows传入盘符(如"C") |
|||
* Linux传入挂载路径(如"/data") |
|||
* |
|||
* @param path 磁盘路径 |
|||
* @throws IOException |
|||
*/ |
|||
public static boolean checkDiskUsage(String path, Integer threshold) { |
|||
try { |
|||
double usage; |
|||
|
|||
if (OSUtils.isWindows()) { |
|||
usage = getWindowsDiskUsage(path); |
|||
} else { |
|||
usage = getLinuxDiskUsage(path); |
|||
} |
|||
|
|||
if (usage > threshold) { |
|||
log.error("[警告] 磁盘" + path + "使用率已达" + usage); |
|||
return true; |
|||
} else { |
|||
//log.info("[正常] 磁盘" + path + "使用率正常" + usage);
|
|||
return false; |
|||
} |
|||
} catch (IOException e) { |
|||
log.error("[警告] 获取磁盘使用率失败", e); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
public static double getDiskUsage(String path) { |
|||
try { |
|||
double usage; |
|||
if (OSUtils.isWindows()) { |
|||
usage = getWindowsDiskUsage(path); |
|||
} else { |
|||
usage = getLinuxDiskUsage(path); |
|||
} |
|||
return usage; |
|||
} catch (IOException e) { |
|||
log.error("[警告] 获取磁盘使用率失败", e); |
|||
return 0d; |
|||
} |
|||
} |
|||
|
|||
|
|||
// Linux磁盘容量
|
|||
private static double getLinuxDiskUsage(String path) throws IOException { |
|||
Path targetPath = Paths.get(path); |
|||
FileStore store = Files.getFileStore(targetPath); |
|||
long total = store.getTotalSpace(); |
|||
long usable = store.getUsableSpace(); |
|||
return (total - usable) * 100.0 / total; |
|||
} |
|||
|
|||
// Windows磁盘容量
|
|||
private static double getWindowsDiskUsage(String path) throws IOException { |
|||
File file = new File(path); |
|||
for (File root : File.listRoots()) { |
|||
if (file.getAbsolutePath().startsWith(root.getAbsolutePath())) { |
|||
long total = root.getTotalSpace(); |
|||
long free = root.getFreeSpace(); |
|||
return (total - free) * 100.0 / total; |
|||
} |
|||
} |
|||
throw new IOException("路径未映射到有效磁盘: " + path); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
package com.mathvision.box.common.utils.file; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.io.IOException; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:文件类型工具类 |
|||
*/ |
|||
@Slf4j |
|||
public class FileTypeUtils |
|||
{ |
|||
/** |
|||
* 获取文件类型 |
|||
* <p> |
|||
* 例如: ruoyi.txt, 返回: txt |
|||
* |
|||
* @param file 文件名 |
|||
* @return 后缀(不含".") |
|||
*/ |
|||
public static String getFileType(File file) |
|||
{ |
|||
if (null == file) |
|||
{ |
|||
return StringUtils.EMPTY; |
|||
} |
|||
return getFileType(file.getName()); |
|||
} |
|||
|
|||
/** |
|||
* 获取文件类型 |
|||
* <p> |
|||
* 例如: ruoyi.txt, 返回: txt |
|||
* |
|||
* @param fileName 文件名 |
|||
* @return 后缀(不含".") |
|||
*/ |
|||
public static String getFileType(String fileName) |
|||
{ |
|||
int separatorIndex = fileName.lastIndexOf("."); |
|||
if (separatorIndex < 0) |
|||
{ |
|||
return ""; |
|||
} |
|||
return fileName.substring(separatorIndex + 1).toLowerCase(); |
|||
} |
|||
|
|||
/** |
|||
* 获取文件类型 |
|||
* |
|||
* @param photoByte 文件字节码 |
|||
* @return 后缀(不含".") |
|||
*/ |
|||
public static String getFileExtendName(byte[] photoByte) |
|||
{ |
|||
String strFileExtendName = "JPG"; |
|||
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) |
|||
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) |
|||
{ |
|||
strFileExtendName = "GIF"; |
|||
} |
|||
else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) |
|||
{ |
|||
strFileExtendName = "JPG"; |
|||
} |
|||
else if ((photoByte[0] == 66) && (photoByte[1] == 77)) |
|||
{ |
|||
strFileExtendName = "BMP"; |
|||
} |
|||
else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) |
|||
{ |
|||
strFileExtendName = "PNG"; |
|||
} |
|||
return strFileExtendName; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,258 @@ |
|||
package com.mathvision.box.common.utils.file; |
|||
|
|||
import com.mathvision.box.common.config.AppConfig; |
|||
import com.mathvision.box.common.constant.Constants; |
|||
import com.mathvision.box.common.exception.file.*; |
|||
import com.mathvision.box.common.utils.common.DateUtils; |
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
import com.mathvision.box.common.utils.uuid.Seq; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.FilenameUtils; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.io.*; |
|||
import java.nio.ByteBuffer; |
|||
import java.nio.file.Paths; |
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* 文件上传工具类 |
|||
*/ |
|||
@Slf4j |
|||
public class FileUploadUtils { |
|||
/** |
|||
* 默认大小 50M |
|||
*/ |
|||
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; |
|||
|
|||
/** |
|||
* 默认的文件名最大长度 100 |
|||
*/ |
|||
public static final int DEFAULT_FILE_NAME_LENGTH = 100; |
|||
|
|||
/** |
|||
* 默认上传的地址 |
|||
*/ |
|||
private static String defaultBaseDir = AppConfig.getProfile(); |
|||
|
|||
public static void setDefaultBaseDir(String defaultBaseDir) { |
|||
FileUploadUtils.defaultBaseDir = defaultBaseDir; |
|||
} |
|||
|
|||
public static String getDefaultBaseDir() { |
|||
return defaultBaseDir; |
|||
} |
|||
|
|||
public static final String upload(ByteBuffer buffer, int dataLen, String fileName, String profile) { |
|||
if (dataLen <= 0) { |
|||
log.error("buffer数据长度无效({}),保存失败", dataLen); |
|||
return ""; |
|||
} |
|||
if (buffer.capacity() < dataLen) { |
|||
log.error("缓冲区容量不足(需要{},实际{}),保存失败", dataLen, buffer.capacity()); |
|||
return ""; |
|||
} |
|||
byte[] bytes = new byte[dataLen]; |
|||
buffer.rewind(); |
|||
buffer.get(bytes); |
|||
return upload(bytes, dataLen, fileName, profile); |
|||
} |
|||
|
|||
public static final String upload(byte[] bytes, int dataLen, String fileName, String profile) { |
|||
String newFileName = StringUtils.isNotBlank(profile) ? AppConfig.getProfile() + "/" + profile + "/" + DateUtils.datePath() + "/" + fileName : AppConfig.getUploadPath() + DateUtils.datePath() + "/" + fileName; |
|||
File file = new File(newFileName); |
|||
|
|||
// 创建文件
|
|||
if (!file.exists()) { |
|||
if (!file.getParentFile().exists()) { |
|||
if (!file.getParentFile().mkdirs()) { |
|||
log.error("无法创建目录: {}", newFileName); |
|||
} |
|||
} |
|||
try { |
|||
file.createNewFile(); |
|||
} catch (IOException e) { |
|||
log.error("创建文件失败:{}", e); |
|||
} |
|||
} |
|||
|
|||
try (FileOutputStream fout = new FileOutputStream(newFileName)) { |
|||
fout.write(bytes, 0, dataLen); |
|||
fout.close(); |
|||
return newFileName; |
|||
} catch (Exception e) { |
|||
log.error("保存失败:{}", e); |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 以默认配置进行文件上传 |
|||
* |
|||
* @param file 上传的文件 |
|||
* @return 文件名称 |
|||
* @throws Exception |
|||
*/ |
|||
public static final String upload(MultipartFile file) throws IOException { |
|||
try { |
|||
return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); |
|||
} catch (Exception e) { |
|||
throw new IOException(e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据文件路径上传 |
|||
* |
|||
* @param baseDir 相对应用的基目录 |
|||
* @param file 上传的文件 |
|||
* @return 文件名称 |
|||
* @throws IOException |
|||
*/ |
|||
public static final String upload(String baseDir, MultipartFile file) throws IOException { |
|||
try { |
|||
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); |
|||
} catch (Exception e) { |
|||
throw new IOException(e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 文件上传 |
|||
* |
|||
* @param baseDir 相对应用的基目录 |
|||
* @param file 上传的文件 |
|||
* @param allowedExtension 上传文件类型 |
|||
* @return 返回上传成功的文件名 |
|||
* @throws FileSizeLimitExceededException 如果超出最大大小 |
|||
* @throws FileNameLengthLimitExceededException 文件名太长 |
|||
* @throws IOException 比如读写文件出错时 |
|||
* @throws InvalidExtensionException 文件校验异常 |
|||
*/ |
|||
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) |
|||
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, |
|||
InvalidExtensionException { |
|||
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); |
|||
if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { |
|||
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); |
|||
} |
|||
|
|||
assertAllowed(file, allowedExtension); |
|||
|
|||
String fileName = extractFilename(file); |
|||
|
|||
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); |
|||
file.transferTo(Paths.get(absPath)); |
|||
return getPathFileName(baseDir, fileName); |
|||
} |
|||
|
|||
/** |
|||
* 编码文件名 |
|||
*/ |
|||
public static final String extractFilename(MultipartFile file) { |
|||
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); |
|||
} |
|||
|
|||
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { |
|||
File desc = new File(uploadDir + File.separator + fileName); |
|||
|
|||
if (!desc.exists()) { |
|||
if (!desc.getParentFile().exists()) { |
|||
desc.getParentFile().mkdirs(); |
|||
} |
|||
} |
|||
return desc; |
|||
} |
|||
|
|||
public static final String getPathFileName(String uploadDir, String fileName) throws IOException { |
|||
int dirLastIndex = AppConfig.getProfile().length() + 1; |
|||
String currentDir = StringUtils.substring(uploadDir, dirLastIndex); |
|||
return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; |
|||
} |
|||
|
|||
/** |
|||
* 文件大小校验 |
|||
* |
|||
* @param file 上传的文件 |
|||
* @return |
|||
* @throws FileSizeLimitExceededException 如果超出最大大小 |
|||
* @throws InvalidExtensionException |
|||
*/ |
|||
public static final void assertAllowed(MultipartFile file, String[] allowedExtension) |
|||
throws FileSizeLimitExceededException, InvalidExtensionException { |
|||
long size = file.getSize(); |
|||
if (size > DEFAULT_MAX_SIZE) { |
|||
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); |
|||
} |
|||
|
|||
String fileName = file.getOriginalFilename(); |
|||
String extension = getExtension(file); |
|||
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { |
|||
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { |
|||
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, |
|||
fileName); |
|||
} else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { |
|||
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, |
|||
fileName); |
|||
} else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { |
|||
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, |
|||
fileName); |
|||
} else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { |
|||
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, |
|||
fileName); |
|||
} else { |
|||
throw new InvalidExtensionException(allowedExtension, extension, fileName); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断MIME类型是否是允许的MIME类型 |
|||
* |
|||
* @param extension |
|||
* @param allowedExtension |
|||
* @return |
|||
*/ |
|||
public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { |
|||
for (String str : allowedExtension) { |
|||
if (str.equalsIgnoreCase(extension)) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 获取文件名的后缀 |
|||
* |
|||
* @param file 表单文件 |
|||
* @return 后缀名 |
|||
*/ |
|||
public static final String getExtension(MultipartFile file) { |
|||
String extension = FilenameUtils.getExtension(file.getOriginalFilename()); |
|||
if (StringUtils.isEmpty(extension)) { |
|||
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); |
|||
} |
|||
return extension; |
|||
} |
|||
|
|||
/** |
|||
* 将 MultipartFile 转换为 File 并保存 |
|||
* |
|||
* @param multipartFile 上传的文件 |
|||
* @param destPath 保存的目标路径 |
|||
* @return 保存后的 File 对象 |
|||
* @throws IOException 如果在保存过程中发生错误 |
|||
*/ |
|||
public static File convertMultipartFileToFile(MultipartFile multipartFile, String destPath) throws IOException { |
|||
File destFile = new File(destPath); |
|||
// 如果目标路径不存在,创建目录
|
|||
if (!destFile.getParentFile().exists()) { |
|||
destFile.getParentFile().mkdirs(); |
|||
} |
|||
multipartFile.transferTo(destFile); |
|||
return destFile; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,447 @@ |
|||
package com.mathvision.box.common.utils.file; |
|||
|
|||
import com.mathvision.box.common.config.AppConfig; |
|||
import com.mathvision.box.common.utils.common.DateUtils; |
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
import com.mathvision.box.common.utils.uuid.IdUtils; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.io.FilenameUtils; |
|||
import org.apache.commons.io.IOUtils; |
|||
import org.apache.commons.lang3.ArrayUtils; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.io.*; |
|||
import java.net.URLEncoder; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.Files; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:文件处理工具类 |
|||
*/ |
|||
@Slf4j |
|||
public class FileUtils { |
|||
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; |
|||
|
|||
/** |
|||
* 检查级别枚举 |
|||
*/ |
|||
public enum CheckLevel { |
|||
BASIC, // 基础检查(存在性、类型)
|
|||
NORMAL, // 普通检查(基础 + 大小、权限)
|
|||
STRICT // 严格检查(普通 + 内容、时效性)
|
|||
} |
|||
|
|||
public static boolean checkParentDir(String filePath) throws IOException { |
|||
File file = new File(filePath); |
|||
// 确保父目录存在
|
|||
File parentDir = file.getParentFile(); |
|||
if (parentDir.exists()){ |
|||
return true; |
|||
}else { |
|||
if (parentDir.mkdirs()) { |
|||
return true; |
|||
}else { |
|||
throw new IOException("无法创建目录: " + parentDir.getAbsolutePath()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
public static boolean isExist(String filePath){ |
|||
File file = new File(filePath); |
|||
return file.exists(); |
|||
} |
|||
|
|||
/** |
|||
* 使用 File 类创建文件 |
|||
*/ |
|||
public static File createFile(String filePath) throws IOException { |
|||
File file = new File(filePath); |
|||
// 确保父目录存在
|
|||
File parentDir = file.getParentFile(); |
|||
if (!parentDir.exists()) { |
|||
if (!parentDir.mkdirs()) { |
|||
throw new IOException("无法创建目录: " + parentDir.getAbsolutePath()); |
|||
} |
|||
} |
|||
// 创建文件
|
|||
if (!file.exists()) { |
|||
if (!file.createNewFile()) { |
|||
throw new IOException("无法创建文件: " + filePath); |
|||
} |
|||
} |
|||
return file; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 输出指定文件的byte数组 |
|||
* |
|||
* @param filePath 文件路径 |
|||
* @param os 输出流 |
|||
* @return |
|||
*/ |
|||
public static void writeBytes(String filePath, OutputStream os) throws IOException { |
|||
FileInputStream fis = null; |
|||
try { |
|||
File file = new File(filePath); |
|||
if (!file.exists()) { |
|||
throw new FileNotFoundException(filePath); |
|||
} |
|||
fis = new FileInputStream(file); |
|||
byte[] b = new byte[1024]; |
|||
int length; |
|||
while ((length = fis.read(b)) > 0) { |
|||
os.write(b, 0, length); |
|||
} |
|||
} catch (IOException e) { |
|||
throw e; |
|||
} finally { |
|||
IOUtils.close(os); |
|||
IOUtils.close(fis); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 写数据到文件中 |
|||
* |
|||
* @param data 数据 |
|||
* @return 目标文件 |
|||
* @throws IOException IO异常 |
|||
*/ |
|||
public static String writeImportBytes(byte[] data) throws IOException { |
|||
return writeBytes(data, AppConfig.getImportPath()); |
|||
} |
|||
|
|||
/** |
|||
* 写数据到文件中 |
|||
* |
|||
* @param data 数据 |
|||
* @param uploadDir 目标文件 |
|||
* @return 目标文件 |
|||
* @throws IOException IO异常 |
|||
*/ |
|||
public static String writeBytes(byte[] data, String uploadDir) throws IOException { |
|||
FileOutputStream fos = null; |
|||
String pathName = ""; |
|||
try { |
|||
String extension = getFileExtendName(data); |
|||
pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; |
|||
File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); |
|||
fos = new FileOutputStream(file); |
|||
fos.write(data); |
|||
} finally { |
|||
IOUtils.close(fos); |
|||
} |
|||
return FileUploadUtils.getPathFileName(uploadDir, pathName); |
|||
} |
|||
|
|||
/** |
|||
* 删除文件 |
|||
* |
|||
* @param filePath 文件 |
|||
* @return |
|||
*/ |
|||
public static boolean deleteFile(String filePath) { |
|||
boolean flag = false; |
|||
File file = new File(filePath); |
|||
// 路径为文件且不为空则进行删除
|
|||
if (file.isFile() && file.exists()) { |
|||
flag = file.delete(); |
|||
} |
|||
return flag; |
|||
} |
|||
|
|||
/** |
|||
* 文件名称验证 |
|||
* |
|||
* @param filename 文件名称 |
|||
* @return true 正常 false 非法 |
|||
*/ |
|||
public static boolean isValidFilename(String filename) { |
|||
return filename.matches(FILENAME_PATTERN); |
|||
} |
|||
|
|||
/** |
|||
* 检查文件是否可下载 |
|||
* |
|||
* @param resource 需要下载的文件 |
|||
* @return true 正常 false 非法 |
|||
*/ |
|||
public static boolean checkAllowDownload(String resource) { |
|||
// 禁止目录上跳级别
|
|||
if (StringUtils.contains(resource, "..")) { |
|||
return false; |
|||
} |
|||
|
|||
// 检查允许下载的文件规则
|
|||
if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) { |
|||
return true; |
|||
} |
|||
|
|||
// 不在允许下载的文件规则
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 下载文件名重新编码 |
|||
* |
|||
* @param request 请求对象 |
|||
* @param fileName 文件名 |
|||
* @return 编码后的文件名 |
|||
*/ |
|||
public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { |
|||
final String agent = request.getHeader("USER-AGENT"); |
|||
String filename = fileName; |
|||
if (agent.contains("MSIE")) { |
|||
// IE浏览器
|
|||
filename = URLEncoder.encode(filename, "utf-8"); |
|||
filename = filename.replace("+", " "); |
|||
} else if (agent.contains("Firefox")) { |
|||
// 火狐浏览器
|
|||
filename = new String(fileName.getBytes(), "ISO8859-1"); |
|||
} else if (agent.contains("Chrome")) { |
|||
// google浏览器
|
|||
filename = URLEncoder.encode(filename, "utf-8"); |
|||
} else { |
|||
// 其它浏览器
|
|||
filename = URLEncoder.encode(filename, "utf-8"); |
|||
} |
|||
return filename; |
|||
} |
|||
|
|||
/** |
|||
* 下载文件名重新编码 |
|||
* |
|||
* @param response 响应对象 |
|||
* @param realFileName 真实文件名 |
|||
* @return |
|||
*/ |
|||
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { |
|||
String percentEncodedFileName = percentEncode(realFileName); |
|||
|
|||
StringBuilder contentDispositionValue = new StringBuilder(); |
|||
contentDispositionValue.append("attachment; filename=") |
|||
.append(percentEncodedFileName) |
|||
.append(";") |
|||
.append("filename*=") |
|||
.append("utf-8''") |
|||
.append(percentEncodedFileName); |
|||
|
|||
response.setHeader("Content-disposition", contentDispositionValue.toString()); |
|||
} |
|||
|
|||
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName, String type) throws UnsupportedEncodingException { |
|||
response.setCharacterEncoding("utf-8"); |
|||
if ("pdf".equals(type)) { |
|||
response.setContentType("application/pdf"); |
|||
response.setHeader("Content-Disposition", "inline; filename=\"" + URLEncoder.encode(realFileName + "_" + DateUtils.dateTimeNow() + "." + type, "UTF-8") + "\""); |
|||
} else if ("doc".equals(type) || "docx".equals(type)) { |
|||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); |
|||
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(realFileName + "_" + DateUtils.dateTimeNow() + "." + type, "UTF-8") + "\""); |
|||
} else if ("xls".equals(type) || "xlsx".equals(type)) { |
|||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
|||
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(realFileName + "_" + DateUtils.dateTimeNow() + "." + type, "UTF-8") + "\""); |
|||
} else if ("zip".equals(type)) { |
|||
response.setContentType("application/zip"); |
|||
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(realFileName + "_" + DateUtils.dateTimeNow() + "." + type, "UTF-8") + "\""); |
|||
} |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 百分号编码工具方法 |
|||
* |
|||
* @param s 需要百分号编码的字符串 |
|||
* @return 百分号编码后的字符串 |
|||
*/ |
|||
public static String percentEncode(String s) throws UnsupportedEncodingException { |
|||
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); |
|||
return encode.replaceAll("\\+", "%20"); |
|||
} |
|||
|
|||
/** |
|||
* 获取图像后缀 |
|||
* |
|||
* @param photoByte 图像数据 |
|||
* @return 后缀名 |
|||
*/ |
|||
public static String getFileExtendName(byte[] photoByte) { |
|||
String strFileExtendName = "jpg"; |
|||
if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) |
|||
&& ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { |
|||
strFileExtendName = "gif"; |
|||
} else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { |
|||
strFileExtendName = "jpg"; |
|||
} else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { |
|||
strFileExtendName = "bmp"; |
|||
} else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { |
|||
strFileExtendName = "png"; |
|||
} |
|||
return strFileExtendName; |
|||
} |
|||
|
|||
/** |
|||
* 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png |
|||
* |
|||
* @param fileName 路径名称 |
|||
* @return 没有文件路径的名称 |
|||
*/ |
|||
public static String getName(String fileName) { |
|||
if (fileName == null) { |
|||
return null; |
|||
} |
|||
int lastUnixPos = fileName.lastIndexOf('/'); |
|||
int lastWindowsPos = fileName.lastIndexOf('\\'); |
|||
int index = Math.max(lastUnixPos, lastWindowsPos); |
|||
return fileName.substring(index + 1); |
|||
} |
|||
|
|||
/** |
|||
* 获取文件父路径 |
|||
* |
|||
* @param fileName 文件路径 |
|||
* @return 获取文件父路径 |
|||
*/ |
|||
public static String getParent(String fileName) { |
|||
File file = new File(fileName); |
|||
File parentDir = file.getParentFile(); |
|||
if (parentDir != null) { |
|||
return parentDir.getAbsolutePath(); |
|||
} else { |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi |
|||
* |
|||
* @param fileName 路径名称 |
|||
* @return 没有文件路径和后缀的名称 |
|||
*/ |
|||
public static String getNameNotSuffix(String fileName) { |
|||
if (fileName == null) { |
|||
return null; |
|||
} |
|||
String baseName = FilenameUtils.getBaseName(fileName); |
|||
return baseName; |
|||
} |
|||
|
|||
/** |
|||
* 检查文件是否有效 |
|||
* |
|||
* @param filePath 文件路径 |
|||
* @param level 检查级别 |
|||
* @return 文件是否有效 |
|||
*/ |
|||
public static boolean isFileValid(String filePath, CheckLevel level) { |
|||
if (StringUtils.isEmpty(filePath)) { |
|||
log.warn("文件路径为空"); |
|||
return false; |
|||
} |
|||
|
|||
File file = new File(filePath); |
|||
|
|||
// 基础检查
|
|||
if (!basicCheck(file)) { |
|||
return false; |
|||
} |
|||
|
|||
// 根据检查级别执行不同的检查
|
|||
if (level.ordinal() >= CheckLevel.NORMAL.ordinal()) { |
|||
if (!normalCheck(file)) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (level.ordinal() >= CheckLevel.STRICT.ordinal()) { |
|||
if (!strictCheck(file)) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 基础检查:文件存在性和类型 |
|||
*/ |
|||
private static boolean basicCheck(File file) { |
|||
if (!file.exists()) { |
|||
log.warn("文件不存在: {}", file.getPath()); |
|||
return false; |
|||
} |
|||
|
|||
if (!file.isFile()) { |
|||
log.warn("不是普通文件: {}", file.getPath()); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 普通检查:文件大小和权限 |
|||
*/ |
|||
private static boolean normalCheck(File file) { |
|||
if (file.length() == 0) { |
|||
log.warn("文件为空: {}", file.getPath()); |
|||
return false; |
|||
} |
|||
|
|||
if (!file.canRead()) { |
|||
log.warn("文件不可读: {}", file.getPath()); |
|||
return false; |
|||
} |
|||
|
|||
try { |
|||
if (!Files.isReadable(file.toPath())) { |
|||
log.warn("文件不可访问: {}", file.getPath()); |
|||
return false; |
|||
} |
|||
} catch (SecurityException e) { |
|||
log.error("检查文件权限时出错: {}", file.getPath(), e); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 严格检查:文件内容和时效性 |
|||
*/ |
|||
private static boolean strictCheck(File file) { |
|||
// 检查文件内容
|
|||
try (FileInputStream fis = new FileInputStream(file)) { |
|||
byte[] buffer = new byte[1024]; |
|||
if (fis.read(buffer) == -1) { |
|||
//logger.warn("文件可能已损坏: {}", file.getPath());
|
|||
return false; |
|||
} |
|||
} catch (IOException e) { |
|||
//logger.error("读取文件时出错: {}", file.getPath(), e);
|
|||
return false; |
|||
} |
|||
|
|||
// 检查文件时效性(示例:30天)
|
|||
long lastModified = file.lastModified(); |
|||
if (lastModified == 0L || |
|||
System.currentTimeMillis() - lastModified > TimeUnit.DAYS.toMillis(30)) { |
|||
//logger.warn("文件可能已过期: {}", file.getPath());
|
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 使用默认检查级别(NORMAL)检查文件 |
|||
*/ |
|||
public static boolean isFileValid(String filePath) { |
|||
return isFileValid(filePath, CheckLevel.NORMAL); |
|||
} |
|||
} |
|||
@ -0,0 +1,265 @@ |
|||
package com.mathvision.box.common.utils.file; |
|||
|
|||
|
|||
import com.mathvision.box.common.config.AppConfig; |
|||
import com.mathvision.box.common.constant.Constants; |
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
import org.apache.commons.io.IOUtils; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import sun.misc.BASE64Decoder; |
|||
import sun.misc.BASE64Encoder; |
|||
|
|||
import javax.imageio.ImageIO; |
|||
import java.awt.*; |
|||
import java.awt.image.BufferedImage; |
|||
import java.io.*; |
|||
import java.net.URL; |
|||
import java.net.URLConnection; |
|||
import java.util.Arrays; |
|||
import java.util.Base64; |
|||
|
|||
/** |
|||
* 图片处理工具类 |
|||
*/ |
|||
public class ImageUtils |
|||
{ |
|||
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); |
|||
|
|||
public static byte[] getImage(String imagePath) |
|||
{ |
|||
InputStream is = getFile(imagePath); |
|||
try |
|||
{ |
|||
return IOUtils.toByteArray(is); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
log.error("图片加载异常 {}", e); |
|||
return null; |
|||
} |
|||
finally |
|||
{ |
|||
IOUtils.closeQuietly(is); |
|||
} |
|||
} |
|||
|
|||
public static InputStream getFile(String imagePath) |
|||
{ |
|||
try |
|||
{ |
|||
byte[] result = readFile(imagePath); |
|||
result = Arrays.copyOf(result, result.length); |
|||
return new ByteArrayInputStream(result); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
log.error("获取图片异常 {}", e); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 读取文件为字节数据 |
|||
* |
|||
* @param url 地址 |
|||
* @return 字节数据 |
|||
*/ |
|||
public static byte[] readFile(String url) |
|||
{ |
|||
InputStream in = null; |
|||
try |
|||
{ |
|||
if (url.startsWith("http")) |
|||
{ |
|||
// 网络地址
|
|||
URL urlObj = new URL(url); |
|||
URLConnection urlConnection = urlObj.openConnection(); |
|||
urlConnection.setConnectTimeout(30 * 1000); |
|||
urlConnection.setReadTimeout(60 * 1000); |
|||
urlConnection.setDoInput(true); |
|||
in = urlConnection.getInputStream(); |
|||
} |
|||
else |
|||
{ |
|||
// 本机地址
|
|||
String localPath = AppConfig.getProfile(); |
|||
String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); |
|||
in = new FileInputStream(downloadPath); |
|||
} |
|||
return IOUtils.toByteArray(in); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
log.error("获取文件路径异常 {}", e); |
|||
return null; |
|||
} |
|||
finally |
|||
{ |
|||
IOUtils.closeQuietly(in); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 通过BufferedImage图片流调整图片大小 |
|||
*/ |
|||
public static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException { |
|||
Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING); |
|||
BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); |
|||
outputImage.getGraphics().drawImage(resultingImage, 0, 0, null); |
|||
return outputImage; |
|||
} |
|||
|
|||
/** |
|||
* 返回base64图片 |
|||
* @param data |
|||
* @return |
|||
*/ |
|||
public static String imageToBase64(byte[] data) { |
|||
BASE64Encoder encoder = new BASE64Encoder(); |
|||
// 返回Base64编码过的字节数组字符串
|
|||
return encoder.encode(data); |
|||
} |
|||
|
|||
/** |
|||
* base64转换成byte数组 |
|||
* @param base64 |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public static byte[] base64ToByte(String base64) throws IOException { |
|||
BASE64Decoder decoder = new BASE64Decoder(); |
|||
// 返回Base64编码过的字节数组字符串
|
|||
return decoder.decodeBuffer(base64); |
|||
} |
|||
|
|||
/** |
|||
* BufferedImage图片流转byte[]数组 |
|||
*/ |
|||
public static byte[] imageToBytes(BufferedImage bImage) { |
|||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|||
try { |
|||
ImageIO.write(bImage, "png", out); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return out.toByteArray(); |
|||
} |
|||
|
|||
/** |
|||
* byte[]数组转BufferedImage图片流 |
|||
*/ |
|||
public static BufferedImage bytesToBufferedImage(byte[] ImageByte) { |
|||
ByteArrayInputStream in = new ByteArrayInputStream(ImageByte); |
|||
BufferedImage image = null; |
|||
try { |
|||
image = ImageIO.read(in); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return image; |
|||
} |
|||
|
|||
/** |
|||
* 在线图片资源转base |
|||
* @param imageUrl |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public static String convertToBase64(String imageUrl) throws IOException { |
|||
URL url = new URL(imageUrl); |
|||
String fileType = imageUrl.substring(imageUrl.length()-3); |
|||
String base64Str = "data:" + fileType + ";base64,"; |
|||
InputStream inputStream = url.openStream(); |
|||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
|||
byte[] buffer = new byte[4096]; |
|||
int bytesRead; |
|||
while ((bytesRead = inputStream.read(buffer)) != -1) { |
|||
outputStream.write(buffer, 0, bytesRead); |
|||
} |
|||
byte[] imageBytes = outputStream.toByteArray(); |
|||
String base64String = base64Str + Base64.getEncoder().encodeToString(imageBytes); |
|||
return base64String; |
|||
} |
|||
|
|||
//图片转化成base64字符串
|
|||
public static String getImageStr(String imgPath) throws IOException { |
|||
File file = new File(imgPath); |
|||
String fileContentBase64 = null; |
|||
if(file.exists()){ |
|||
String fileType = imgPath.substring(imgPath.length()-3); |
|||
String base64Str = "data:" + fileType + ";base64,"; |
|||
String content = null; |
|||
//将图片文件转化为字节数组字符串,并对其进行Base64编码处理
|
|||
InputStream in = null; |
|||
byte[] data = null; |
|||
//读取图片字节数组
|
|||
try { |
|||
in = new FileInputStream(file); |
|||
data = new byte[in.available()]; |
|||
in.read(data); |
|||
in.close(); |
|||
//对字节数组Base64编码
|
|||
if (data == null || data.length == 0) { |
|||
return null; |
|||
} |
|||
//content = Base64.encodeBytes(data);
|
|||
content = new BASE64Encoder().encode(data); |
|||
if (content == null || "".equals(content)) { |
|||
return null; |
|||
} |
|||
// 缩小图片
|
|||
if (StringUtils.isNotBlank(content)) { |
|||
BufferedImage bufferedImage = ImageUtils.bytesToBufferedImage(ImageUtils.base64ToByte(content)); |
|||
if (bufferedImage != null){ |
|||
int height = bufferedImage.getHeight(); |
|||
int width = bufferedImage.getWidth(); |
|||
// 如果图片宽度大于650,图片缩放
|
|||
if (width > 500) { |
|||
//高度等比缩放
|
|||
height = (int)(height*500.0/width); |
|||
BufferedImage imgZoom = ImageUtils.resizeImage(bufferedImage, 500, height); |
|||
content = ImageUtils.imageToBase64(ImageUtils.imageToBytes(imgZoom)); |
|||
} |
|||
} |
|||
} |
|||
fileContentBase64 = base64Str + content; |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} finally { |
|||
if (in != null) { |
|||
in.close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return fileContentBase64; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 验证给定的字符串是否为有效的 Base64 编码。 |
|||
* |
|||
* @param base64Str 要验证的字符串 |
|||
* @return 如果是有效的 Base64 编码,则返回 true;否则,返回 false |
|||
*/ |
|||
public static boolean isValidBase64(String base64Str) { |
|||
if (base64Str == null || base64Str.isEmpty()) { |
|||
return false; |
|||
} |
|||
try { |
|||
// 移除 Base64 数据的前缀(如果存在)
|
|||
String actualBase64Str = base64Str; |
|||
if (base64Str.contains(",")) { |
|||
actualBase64Str = base64Str.split(",")[1]; |
|||
} |
|||
|
|||
// 尝试解码
|
|||
Base64.getDecoder().decode(actualBase64Str); |
|||
return true; |
|||
} catch (IllegalArgumentException e) { |
|||
// 捕获解码异常,说明不是有效的 Base64 字符串
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
package com.mathvision.box.common.utils.file; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:媒体类型工具类 |
|||
*/ |
|||
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", |
|||
// 音频格式
|
|||
"mp3", "wav", "aac", |
|||
// pdf
|
|||
"pdf" }; |
|||
|
|||
public static String getExtension(String prefix) |
|||
{ |
|||
switch (prefix) |
|||
{ |
|||
case IMAGE_PNG: |
|||
return "png"; |
|||
case IMAGE_JPG: |
|||
return "jpg"; |
|||
case IMAGE_JPEG: |
|||
return "jpeg"; |
|||
case IMAGE_BMP: |
|||
return "bmp"; |
|||
case IMAGE_GIF: |
|||
return "gif"; |
|||
default: |
|||
return ""; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,166 @@ |
|||
package com.mathvision.box.common.utils.html; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
/** |
|||
* 转义和反转义工具类 |
|||
*/ |
|||
public class EscapeUtil |
|||
{ |
|||
public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; |
|||
|
|||
private static final char[][] TEXT = new char[64][]; |
|||
|
|||
static |
|||
{ |
|||
for (int i = 0; i < 64; i++) |
|||
{ |
|||
TEXT[i] = new char[] { (char) i }; |
|||
} |
|||
|
|||
// special HTML characters
|
|||
TEXT['\''] = "'".toCharArray(); // 单引号
|
|||
TEXT['"'] = """.toCharArray(); // 双引号
|
|||
TEXT['&'] = "&".toCharArray(); // &符
|
|||
TEXT['<'] = "<".toCharArray(); // 小于号
|
|||
TEXT['>'] = ">".toCharArray(); // 大于号
|
|||
} |
|||
|
|||
/** |
|||
* 转义文本中的HTML字符为安全的字符 |
|||
* |
|||
* @param text 被转义的文本 |
|||
* @return 转义后的文本 |
|||
*/ |
|||
public static String escape(String text) |
|||
{ |
|||
return encode(text); |
|||
} |
|||
|
|||
/** |
|||
* 还原被转义的HTML特殊字符 |
|||
* |
|||
* @param content 包含转义符的HTML内容 |
|||
* @return 转换后的字符串 |
|||
*/ |
|||
public static String unescape(String content) |
|||
{ |
|||
return decode(content); |
|||
} |
|||
|
|||
/** |
|||
* 清除所有HTML标签,但是不删除标签内的内容 |
|||
* |
|||
* @param content 文本 |
|||
* @return 清除标签后的文本 |
|||
*/ |
|||
public static String clean(String content) |
|||
{ |
|||
return new HTMLFilter().filter(content); |
|||
} |
|||
|
|||
/** |
|||
* Escape编码 |
|||
* |
|||
* @param text 被编码的文本 |
|||
* @return 编码后的字符 |
|||
*/ |
|||
private static String encode(String text) |
|||
{ |
|||
if (StringUtils.isEmpty(text)) |
|||
{ |
|||
return StringUtils.EMPTY; |
|||
} |
|||
|
|||
final StringBuilder tmp = new StringBuilder(text.length() * 6); |
|||
char c; |
|||
for (int i = 0; i < text.length(); i++) |
|||
{ |
|||
c = text.charAt(i); |
|||
if (c < 256) |
|||
{ |
|||
tmp.append("%"); |
|||
if (c < 16) |
|||
{ |
|||
tmp.append("0"); |
|||
} |
|||
tmp.append(Integer.toString(c, 16)); |
|||
} |
|||
else |
|||
{ |
|||
tmp.append("%u"); |
|||
if (c <= 0xfff) |
|||
{ |
|||
// issue#I49JU8@Gitee
|
|||
tmp.append("0"); |
|||
} |
|||
tmp.append(Integer.toString(c, 16)); |
|||
} |
|||
} |
|||
return tmp.toString(); |
|||
} |
|||
|
|||
/** |
|||
* Escape解码 |
|||
* |
|||
* @param content 被转义的内容 |
|||
* @return 解码后的字符串 |
|||
*/ |
|||
public static String decode(String content) |
|||
{ |
|||
if (StringUtils.isEmpty(content)) |
|||
{ |
|||
return content; |
|||
} |
|||
|
|||
StringBuilder tmp = new StringBuilder(content.length()); |
|||
int lastPos = 0, pos = 0; |
|||
char ch; |
|||
while (lastPos < content.length()) |
|||
{ |
|||
pos = content.indexOf("%", lastPos); |
|||
if (pos == lastPos) |
|||
{ |
|||
if (content.charAt(pos + 1) == 'u') |
|||
{ |
|||
ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); |
|||
tmp.append(ch); |
|||
lastPos = pos + 6; |
|||
} |
|||
else |
|||
{ |
|||
ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); |
|||
tmp.append(ch); |
|||
lastPos = pos + 3; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (pos == -1) |
|||
{ |
|||
tmp.append(content.substring(lastPos)); |
|||
lastPos = content.length(); |
|||
} |
|||
else |
|||
{ |
|||
tmp.append(content.substring(lastPos, pos)); |
|||
lastPos = pos; |
|||
} |
|||
} |
|||
} |
|||
return tmp.toString(); |
|||
} |
|||
|
|||
public static void main(String[] args) |
|||
{ |
|||
String html = "<script>alert(1);</script>"; |
|||
String escape = EscapeUtil.escape(html); |
|||
// String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
|
|||
// String html = "<123";
|
|||
// String html = "123>";
|
|||
System.out.println("clean: " + EscapeUtil.clean(html)); |
|||
System.out.println("escape: " + escape); |
|||
System.out.println("unescape: " + EscapeUtil.unescape(escape)); |
|||
} |
|||
} |
|||
@ -0,0 +1,564 @@ |
|||
package com.mathvision.box.common.utils.html; |
|||
|
|||
import java.util.*; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
/** |
|||
* HTML过滤器,用于去除XSS漏洞隐患。 |
|||
*/ |
|||
public final class HTMLFilter |
|||
{ |
|||
/** |
|||
* regex flag union representing /si modifiers in php |
|||
**/ |
|||
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; |
|||
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL); |
|||
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); |
|||
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); |
|||
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); |
|||
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); |
|||
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); |
|||
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); |
|||
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); |
|||
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); |
|||
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); |
|||
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); |
|||
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); |
|||
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); |
|||
private static final Pattern P_END_ARROW = Pattern.compile("^>"); |
|||
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); |
|||
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); |
|||
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); |
|||
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); |
|||
private static final Pattern P_AMP = Pattern.compile("&"); |
|||
private static final Pattern P_QUOTE = Pattern.compile("\""); |
|||
private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); |
|||
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); |
|||
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); |
|||
|
|||
// @xxx could grow large... maybe use sesat's ReferenceMap
|
|||
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); |
|||
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); |
|||
|
|||
/** |
|||
* set of allowed html elements, along with allowed attributes for each element |
|||
**/ |
|||
private final Map<String, List<String>> vAllowed; |
|||
/** |
|||
* counts of open tags for each (allowable) html element |
|||
**/ |
|||
private final Map<String, Integer> vTagCounts = new HashMap<>(); |
|||
|
|||
/** |
|||
* html elements which must always be self-closing (e.g. "<img />") |
|||
**/ |
|||
private final String[] vSelfClosingTags; |
|||
/** |
|||
* html elements which must always have separate opening and closing tags (e.g. "<b></b>") |
|||
**/ |
|||
private final String[] vNeedClosingTags; |
|||
/** |
|||
* set of disallowed html elements |
|||
**/ |
|||
private final String[] vDisallowed; |
|||
/** |
|||
* attributes which should be checked for valid protocols |
|||
**/ |
|||
private final String[] vProtocolAtts; |
|||
/** |
|||
* allowed protocols |
|||
**/ |
|||
private final String[] vAllowedProtocols; |
|||
/** |
|||
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />") |
|||
**/ |
|||
private final String[] vRemoveBlanks; |
|||
/** |
|||
* entities allowed within html markup |
|||
**/ |
|||
private final String[] vAllowedEntities; |
|||
/** |
|||
* flag determining whether comments are allowed in input String. |
|||
*/ |
|||
private final boolean stripComment; |
|||
private final boolean encodeQuotes; |
|||
/** |
|||
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>" |
|||
* becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped. |
|||
*/ |
|||
private final boolean alwaysMakeTags; |
|||
|
|||
/** |
|||
* Default constructor. |
|||
*/ |
|||
public HTMLFilter() |
|||
{ |
|||
vAllowed = new HashMap<>(); |
|||
|
|||
final ArrayList<String> a_atts = new ArrayList<>(); |
|||
a_atts.add("href"); |
|||
a_atts.add("target"); |
|||
vAllowed.put("a", a_atts); |
|||
|
|||
final ArrayList<String> img_atts = new ArrayList<>(); |
|||
img_atts.add("src"); |
|||
img_atts.add("width"); |
|||
img_atts.add("height"); |
|||
img_atts.add("alt"); |
|||
vAllowed.put("img", img_atts); |
|||
|
|||
final ArrayList<String> no_atts = new ArrayList<>(); |
|||
vAllowed.put("b", no_atts); |
|||
vAllowed.put("strong", no_atts); |
|||
vAllowed.put("i", no_atts); |
|||
vAllowed.put("em", no_atts); |
|||
|
|||
vSelfClosingTags = new String[] { "img" }; |
|||
vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; |
|||
vDisallowed = new String[] {}; |
|||
vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp.
|
|||
vProtocolAtts = new String[] { "src", "href" }; |
|||
vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; |
|||
vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; |
|||
stripComment = true; |
|||
encodeQuotes = true; |
|||
alwaysMakeTags = false; |
|||
} |
|||
|
|||
/** |
|||
* Map-parameter configurable constructor. |
|||
* |
|||
* @param conf map containing configuration. keys match field names. |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public HTMLFilter(final Map<String, Object> conf) |
|||
{ |
|||
|
|||
assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; |
|||
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; |
|||
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; |
|||
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; |
|||
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; |
|||
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; |
|||
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; |
|||
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; |
|||
|
|||
vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed")); |
|||
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); |
|||
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); |
|||
vDisallowed = (String[]) conf.get("vDisallowed"); |
|||
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); |
|||
vProtocolAtts = (String[]) conf.get("vProtocolAtts"); |
|||
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); |
|||
vAllowedEntities = (String[]) conf.get("vAllowedEntities"); |
|||
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; |
|||
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; |
|||
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; |
|||
} |
|||
|
|||
private void reset() |
|||
{ |
|||
vTagCounts.clear(); |
|||
} |
|||
|
|||
// ---------------------------------------------------------------
|
|||
// my versions of some PHP library functions
|
|||
public static String chr(final int decimal) |
|||
{ |
|||
return String.valueOf((char) decimal); |
|||
} |
|||
|
|||
public static String htmlSpecialChars(final String s) |
|||
{ |
|||
String result = s; |
|||
result = regexReplace(P_AMP, "&", result); |
|||
result = regexReplace(P_QUOTE, """, result); |
|||
result = regexReplace(P_LEFT_ARROW, "<", result); |
|||
result = regexReplace(P_RIGHT_ARROW, ">", result); |
|||
return result; |
|||
} |
|||
|
|||
// ---------------------------------------------------------------
|
|||
|
|||
/** |
|||
* given a user submitted input String, filter out any invalid or restricted html. |
|||
* |
|||
* @param input text (i.e. submitted by a user) than may contain html |
|||
* @return "clean" version of input, with only valid, whitelisted html elements allowed |
|||
*/ |
|||
public String filter(final String input) |
|||
{ |
|||
reset(); |
|||
String s = input; |
|||
|
|||
s = escapeComments(s); |
|||
|
|||
s = balanceHTML(s); |
|||
|
|||
s = checkTags(s); |
|||
|
|||
s = processRemoveBlanks(s); |
|||
|
|||
// s = validateEntities(s);
|
|||
|
|||
return s; |
|||
} |
|||
|
|||
public boolean isAlwaysMakeTags() |
|||
{ |
|||
return alwaysMakeTags; |
|||
} |
|||
|
|||
public boolean isStripComments() |
|||
{ |
|||
return stripComment; |
|||
} |
|||
|
|||
private String escapeComments(final String s) |
|||
{ |
|||
final Matcher m = P_COMMENTS.matcher(s); |
|||
final StringBuffer buf = new StringBuffer(); |
|||
if (m.find()) |
|||
{ |
|||
final String match = m.group(1); // (.*?)
|
|||
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->")); |
|||
} |
|||
m.appendTail(buf); |
|||
|
|||
return buf.toString(); |
|||
} |
|||
|
|||
private String balanceHTML(String s) |
|||
{ |
|||
if (alwaysMakeTags) |
|||
{ |
|||
//
|
|||
// try and form html
|
|||
//
|
|||
s = regexReplace(P_END_ARROW, "", s); |
|||
// 不追加结束标签
|
|||
s = regexReplace(P_BODY_TO_END, "<$1>", s); |
|||
s = regexReplace(P_XML_CONTENT, "$1<$2", s); |
|||
|
|||
} |
|||
else |
|||
{ |
|||
//
|
|||
// escape stray brackets
|
|||
//
|
|||
s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); |
|||
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); |
|||
|
|||
//
|
|||
// the last regexp causes '<>' entities to appear
|
|||
// (we need to do a lookahead assertion so that the last bracket can
|
|||
// be used in the next pass of the regexp)
|
|||
//
|
|||
s = regexReplace(P_BOTH_ARROWS, "", s); |
|||
} |
|||
|
|||
return s; |
|||
} |
|||
|
|||
private String checkTags(String s) |
|||
{ |
|||
Matcher m = P_TAGS.matcher(s); |
|||
|
|||
final StringBuffer buf = new StringBuffer(); |
|||
while (m.find()) |
|||
{ |
|||
String replaceStr = m.group(1); |
|||
replaceStr = processTag(replaceStr); |
|||
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); |
|||
} |
|||
m.appendTail(buf); |
|||
|
|||
// these get tallied in processTag
|
|||
// (remember to reset before subsequent calls to filter method)
|
|||
final StringBuilder sBuilder = new StringBuilder(buf.toString()); |
|||
for (String key : vTagCounts.keySet()) |
|||
{ |
|||
for (int ii = 0; ii < vTagCounts.get(key); ii++) |
|||
{ |
|||
sBuilder.append("</").append(key).append(">"); |
|||
} |
|||
} |
|||
s = sBuilder.toString(); |
|||
|
|||
return s; |
|||
} |
|||
|
|||
private String processRemoveBlanks(final String s) |
|||
{ |
|||
String result = s; |
|||
for (String tag : vRemoveBlanks) |
|||
{ |
|||
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) |
|||
{ |
|||
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">")); |
|||
} |
|||
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); |
|||
if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) |
|||
{ |
|||
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); |
|||
} |
|||
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) |
|||
{ |
|||
Matcher m = regex_pattern.matcher(s); |
|||
return m.replaceAll(replacement); |
|||
} |
|||
|
|||
private String processTag(final String s) |
|||
{ |
|||
// ending tags
|
|||
Matcher m = P_END_TAG.matcher(s); |
|||
if (m.find()) |
|||
{ |
|||
final String name = m.group(1).toLowerCase(); |
|||
if (allowed(name)) |
|||
{ |
|||
if (false == inArray(name, vSelfClosingTags)) |
|||
{ |
|||
if (vTagCounts.containsKey(name)) |
|||
{ |
|||
vTagCounts.put(name, vTagCounts.get(name) - 1); |
|||
return "</" + name + ">"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// starting tags
|
|||
m = P_START_TAG.matcher(s); |
|||
if (m.find()) |
|||
{ |
|||
final String name = m.group(1).toLowerCase(); |
|||
final String body = m.group(2); |
|||
String ending = m.group(3); |
|||
|
|||
// debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
|
|||
if (allowed(name)) |
|||
{ |
|||
final StringBuilder params = new StringBuilder(); |
|||
|
|||
final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); |
|||
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); |
|||
final List<String> paramNames = new ArrayList<>(); |
|||
final List<String> paramValues = new ArrayList<>(); |
|||
while (m2.find()) |
|||
{ |
|||
paramNames.add(m2.group(1)); // ([a-z0-9]+)
|
|||
paramValues.add(m2.group(3)); // (.*?)
|
|||
} |
|||
while (m3.find()) |
|||
{ |
|||
paramNames.add(m3.group(1)); // ([a-z0-9]+)
|
|||
paramValues.add(m3.group(3)); // ([^\"\\s']+)
|
|||
} |
|||
|
|||
String paramName, paramValue; |
|||
for (int ii = 0; ii < paramNames.size(); ii++) |
|||
{ |
|||
paramName = paramNames.get(ii).toLowerCase(); |
|||
paramValue = paramValues.get(ii); |
|||
|
|||
// debug( "paramName='" + paramName + "'" );
|
|||
// debug( "paramValue='" + paramValue + "'" );
|
|||
// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
|
|||
|
|||
if (allowedAttribute(name, paramName)) |
|||
{ |
|||
if (inArray(paramName, vProtocolAtts)) |
|||
{ |
|||
paramValue = processParamProtocol(paramValue); |
|||
} |
|||
params.append(' ').append(paramName).append("=\"").append(paramValue).append("\""); |
|||
} |
|||
} |
|||
|
|||
if (inArray(name, vSelfClosingTags)) |
|||
{ |
|||
ending = " /"; |
|||
} |
|||
|
|||
if (inArray(name, vNeedClosingTags)) |
|||
{ |
|||
ending = ""; |
|||
} |
|||
|
|||
if (ending == null || ending.length() < 1) |
|||
{ |
|||
if (vTagCounts.containsKey(name)) |
|||
{ |
|||
vTagCounts.put(name, vTagCounts.get(name) + 1); |
|||
} |
|||
else |
|||
{ |
|||
vTagCounts.put(name, 1); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
ending = " /"; |
|||
} |
|||
return "<" + name + params + ending + ">"; |
|||
} |
|||
else |
|||
{ |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
// comments
|
|||
m = P_COMMENT.matcher(s); |
|||
if (!stripComment && m.find()) |
|||
{ |
|||
return "<" + m.group() + ">"; |
|||
} |
|||
|
|||
return ""; |
|||
} |
|||
|
|||
private String processParamProtocol(String s) |
|||
{ |
|||
s = decodeEntities(s); |
|||
final Matcher m = P_PROTOCOL.matcher(s); |
|||
if (m.find()) |
|||
{ |
|||
final String protocol = m.group(1); |
|||
if (!inArray(protocol, vAllowedProtocols)) |
|||
{ |
|||
// bad protocol, turn into local anchor link instead
|
|||
s = "#" + s.substring(protocol.length() + 1); |
|||
if (s.startsWith("#//")) |
|||
{ |
|||
s = "#" + s.substring(3); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return s; |
|||
} |
|||
|
|||
private String decodeEntities(String s) |
|||
{ |
|||
StringBuffer buf = new StringBuffer(); |
|||
|
|||
Matcher m = P_ENTITY.matcher(s); |
|||
while (m.find()) |
|||
{ |
|||
final String match = m.group(1); |
|||
final int decimal = Integer.decode(match).intValue(); |
|||
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); |
|||
} |
|||
m.appendTail(buf); |
|||
s = buf.toString(); |
|||
|
|||
buf = new StringBuffer(); |
|||
m = P_ENTITY_UNICODE.matcher(s); |
|||
while (m.find()) |
|||
{ |
|||
final String match = m.group(1); |
|||
final int decimal = Integer.valueOf(match, 16).intValue(); |
|||
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); |
|||
} |
|||
m.appendTail(buf); |
|||
s = buf.toString(); |
|||
|
|||
buf = new StringBuffer(); |
|||
m = P_ENCODE.matcher(s); |
|||
while (m.find()) |
|||
{ |
|||
final String match = m.group(1); |
|||
final int decimal = Integer.valueOf(match, 16).intValue(); |
|||
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); |
|||
} |
|||
m.appendTail(buf); |
|||
s = buf.toString(); |
|||
|
|||
s = validateEntities(s); |
|||
return s; |
|||
} |
|||
|
|||
private String validateEntities(final String s) |
|||
{ |
|||
StringBuffer buf = new StringBuffer(); |
|||
|
|||
// validate entities throughout the string
|
|||
Matcher m = P_VALID_ENTITIES.matcher(s); |
|||
while (m.find()) |
|||
{ |
|||
final String one = m.group(1); // ([^&;]*)
|
|||
final String two = m.group(2); // (?=(;|&|$))
|
|||
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); |
|||
} |
|||
m.appendTail(buf); |
|||
|
|||
return encodeQuotes(buf.toString()); |
|||
} |
|||
|
|||
private String encodeQuotes(final String s) |
|||
{ |
|||
if (encodeQuotes) |
|||
{ |
|||
StringBuffer buf = new StringBuffer(); |
|||
Matcher m = P_VALID_QUOTES.matcher(s); |
|||
while (m.find()) |
|||
{ |
|||
final String one = m.group(1); // (>|^)
|
|||
final String two = m.group(2); // ([^<]+?)
|
|||
final String three = m.group(3); // (<|$)
|
|||
// 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two)
|
|||
m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); |
|||
} |
|||
m.appendTail(buf); |
|||
return buf.toString(); |
|||
} |
|||
else |
|||
{ |
|||
return s; |
|||
} |
|||
} |
|||
|
|||
private String checkEntity(final String preamble, final String term) |
|||
{ |
|||
|
|||
return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; |
|||
} |
|||
|
|||
private boolean isValidEntity(final String entity) |
|||
{ |
|||
return inArray(entity, vAllowedEntities); |
|||
} |
|||
|
|||
private static boolean inArray(final String s, final String[] array) |
|||
{ |
|||
for (String item : array) |
|||
{ |
|||
if (item != null && item.equals(s)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private boolean allowed(final String name) |
|||
{ |
|||
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); |
|||
} |
|||
|
|||
private boolean allowedAttribute(final String name, final String paramName) |
|||
{ |
|||
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); |
|||
} |
|||
} |
|||
@ -0,0 +1,340 @@ |
|||
package com.mathvision.box.common.utils.http; |
|||
|
|||
import org.springframework.http.HttpMethod; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.lang.Nullable; |
|||
import org.springframework.util.Assert; |
|||
import org.springframework.util.LinkedMultiValueMap; |
|||
import org.springframework.util.MultiValueMap; |
|||
|
|||
import java.util.Objects; |
|||
import java.util.regex.Pattern; |
|||
|
|||
/** |
|||
* HTTP请求模板封装(线程不安全) |
|||
* <p>提供类型安全的HTTP请求配置,支持参数校验、防御性拷贝和流畅API</p> |
|||
* |
|||
* <h3>使用示例:</h3> |
|||
* <pre>{@code |
|||
* HttpReqTemp reqTemp = new HttpReqTemp() |
|||
* .withMethod(HttpMethod.POST) |
|||
* .withUrl("http://192.168.1.3/api/version") |
|||
* .withQueryParam() |
|||
* .withHeader("Content-Type", "application/json") |
|||
* .withMediaType(MediaType.APPLICATION_JSON) |
|||
* .withBody("{\"name\":\"John\",\"age\":30}") |
|||
* .withTimeout(120) |
|||
* .withRetryCount(3) |
|||
* .withExceptionStrategy(HttpReqTemp.ExceptionHandlingStrategy.FALLBACK) |
|||
* .withExample("{\"name\":\"John\",\"age\":30}"); |
|||
* }</pre> |
|||
*/ |
|||
public class HttpReqTemp { |
|||
private static final Pattern URL_PATTERN = Pattern.compile("^(https?|ftp)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); |
|||
private static final int MAX_TIMEOUT = 10000; |
|||
private static final int MAX_RETRY = 10; |
|||
|
|||
// 核心参数
|
|||
private HttpMethod method; |
|||
private String url; |
|||
private MediaType mediaType; |
|||
private String body; |
|||
|
|||
// 集合参数(防御性拷贝)
|
|||
private MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); |
|||
private MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); |
|||
|
|||
// 执行策略
|
|||
private int timeout = 120; |
|||
private int retryCount = 1; |
|||
private ExceptionHandlingStrategy exceptionStrategy = ExceptionHandlingStrategy.DEFAULT; |
|||
|
|||
// MOCK数据
|
|||
private String example; |
|||
|
|||
/** |
|||
* 参数有效性校验 |
|||
* |
|||
* @throws IllegalStateException 当配置存在矛盾时抛出 |
|||
*/ |
|||
public void validateConfig() { |
|||
if (method == HttpMethod.GET && body != null) { |
|||
throw new IllegalStateException("GET请求不能包含请求体"); |
|||
} |
|||
if (mediaType == null && body != null) { |
|||
throw new IllegalStateException("包含请求体时必须指定MediaType"); |
|||
} |
|||
} |
|||
|
|||
/*----------------------- 核心参数配置 -----------------------*/ |
|||
|
|||
/** |
|||
* 设置HTTP方法 |
|||
* |
|||
* @param method HTTP方法枚举,不可为空 |
|||
*/ |
|||
public void setMethod(HttpMethod method) { |
|||
this.method = Objects.requireNonNull(method, "HTTP方法不能为空"); |
|||
} |
|||
|
|||
/** |
|||
* 设置请求URL |
|||
* |
|||
* @param url 符合格式的完整URL(支持http/https/ftp) |
|||
* @throws IllegalArgumentException 当URL格式无效时抛出 |
|||
*/ |
|||
public void setUrl(String url) { |
|||
Assert.hasText(url, "URL不能为空"); |
|||
if (!URL_PATTERN.matcher(url).matches()) { |
|||
throw new IllegalArgumentException("无效的URL格式: " + url); |
|||
} |
|||
this.url = url; |
|||
} |
|||
|
|||
/** |
|||
* 设置媒体类型 |
|||
* |
|||
* @param mediaType 标准MediaType枚举值,不可为空 |
|||
*/ |
|||
public void setMediaType(MediaType mediaType) { |
|||
this.mediaType = Objects.requireNonNull(mediaType, "MediaType不能为空"); |
|||
} |
|||
|
|||
public void setBody(String body) { |
|||
this.body = body; |
|||
} |
|||
|
|||
public void setExample(String example) { |
|||
this.example = example; |
|||
} |
|||
|
|||
/*----------------------- 集合参数操作 -----------------------*/ |
|||
|
|||
/** |
|||
* 完全替换查询参数 |
|||
* |
|||
* @param params 新的参数集合(自动防御性拷贝) |
|||
*/ |
|||
public void setQueryParams(@Nullable MultiValueMap<String, String> params) { |
|||
this.queryParams = params != null ? |
|||
new LinkedMultiValueMap<>(params) : |
|||
new LinkedMultiValueMap<>(); |
|||
} |
|||
|
|||
/** |
|||
* 添加单个查询参数 |
|||
* |
|||
* @param key 参数键(自动trim,不允许空值或包含非法字符) |
|||
* @param value 参数值(允许null) |
|||
*/ |
|||
public void addQueryParam(String key, @Nullable String value) { |
|||
validateParameterKey(key); |
|||
this.queryParams.add(key.trim(), value); |
|||
} |
|||
|
|||
/** |
|||
* 完全替换请求头 |
|||
* |
|||
* @param headers 新的头信息集合(自动防御性拷贝) |
|||
*/ |
|||
public void setHeaders(@Nullable MultiValueMap<String, String> headers) { |
|||
this.headers = headers != null ? |
|||
new LinkedMultiValueMap<>(headers) : |
|||
new LinkedMultiValueMap<>(); |
|||
} |
|||
|
|||
/** |
|||
* 添加请求头 |
|||
* |
|||
* @param key 头字段名(自动trim,需符合规范) |
|||
* @param value 头字段值(允许null) |
|||
*/ |
|||
public void addHeader(String key, @Nullable String value) { |
|||
validateHeaderKey(key); |
|||
this.headers.add(key.trim(), value); |
|||
} |
|||
|
|||
/*----------------------- 策略配置 -----------------------*/ |
|||
|
|||
/** |
|||
* 设置超时时间 |
|||
* |
|||
* @param seconds 超时时间(0-120秒) |
|||
*/ |
|||
public void setTimeout(int seconds) { |
|||
if (seconds < 0 || seconds > MAX_TIMEOUT) { |
|||
throw new IllegalArgumentException("超时时间需在0-" + MAX_TIMEOUT + "秒之间"); |
|||
} |
|||
this.timeout = seconds; |
|||
} |
|||
|
|||
/** |
|||
* 设置重试次数 |
|||
* |
|||
* @param count 重试次数(0-10次) |
|||
*/ |
|||
public void setRetryCount(int count) { |
|||
if (count < 0 || count > MAX_RETRY) { |
|||
throw new IllegalArgumentException("重试次数需在0-" + MAX_RETRY + "次之间"); |
|||
} |
|||
this.retryCount = count; |
|||
} |
|||
|
|||
/** |
|||
* 设置异常处理策略 |
|||
* |
|||
* @param strategy 异常处理策略枚举 |
|||
*/ |
|||
public void setExceptionStrategy(ExceptionHandlingStrategy strategy) { |
|||
this.exceptionStrategy = Objects.requireNonNull(strategy); |
|||
} |
|||
|
|||
/*----------------------- 校验方法 -----------------------*/ |
|||
|
|||
private void validateParameterKey(String key) { |
|||
Assert.hasText(key, "查询参数键不能为空"); |
|||
if (key.contains(" ")) { |
|||
throw new IllegalArgumentException("参数键包含非法空格: " + key); |
|||
} |
|||
} |
|||
|
|||
private void validateHeaderKey(String key) { |
|||
Assert.hasText(key, "请求头键不能为空"); |
|||
String normalized = key.trim().toLowerCase(); |
|||
if (normalized.equals("content-length")) { |
|||
throw new IllegalArgumentException("禁止手动设置Content-Length头"); |
|||
} |
|||
} |
|||
|
|||
/*----------------------- 流畅接口方法 -----------------------*/ |
|||
|
|||
public HttpReqTemp withMethod(HttpMethod method) { |
|||
setMethod(method); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withUrl(String url) { |
|||
setUrl(url); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withQueryParam(String key, String value) { |
|||
addQueryParam(key, value); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withHeader(String key, String value) { |
|||
addHeader(key, value); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withOauth(String token) { |
|||
addHeader("Authorization", "Bearer " + token); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withMediaType(MediaType mediaType) { |
|||
setMediaType(mediaType); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withTimeout(int seconds) { |
|||
setTimeout(seconds); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withRetryCount(int count) { |
|||
setRetryCount(count); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withExample(String example) { |
|||
setExample(example); |
|||
return this; |
|||
} |
|||
|
|||
public HttpReqTemp withBody(String body) { |
|||
setBody(body); |
|||
return this; |
|||
} |
|||
|
|||
/*----------------------- 枚举定义 -----------------------*/ |
|||
|
|||
/** |
|||
* 异常处理策略 |
|||
*/ |
|||
public enum ExceptionHandlingStrategy { |
|||
/** |
|||
* 抛出原始异常 |
|||
*/ |
|||
DEFAULT, |
|||
/** |
|||
* 返回预定义的示例数据 |
|||
*/ |
|||
FALLBACK |
|||
} |
|||
|
|||
/*----------------------- Getter方法 -----------------------*/ |
|||
|
|||
public HttpMethod getMethod() { |
|||
return method; |
|||
} |
|||
|
|||
public String getUrl() { |
|||
return url; |
|||
} |
|||
|
|||
public MediaType getMediaType() { |
|||
if (mediaType == null) { |
|||
return MediaType.APPLICATION_JSON; |
|||
} else { |
|||
return mediaType; |
|||
} |
|||
} |
|||
|
|||
public MultiValueMap<String, String> getQueryParams() { |
|||
return new LinkedMultiValueMap<>(queryParams); |
|||
} |
|||
|
|||
public MultiValueMap<String, String> getHeaders() { |
|||
return new LinkedMultiValueMap<>(headers); |
|||
} |
|||
|
|||
public int getTimeout() { |
|||
return timeout; |
|||
} |
|||
|
|||
public int getRetryCount() { |
|||
return retryCount; |
|||
} |
|||
|
|||
public ExceptionHandlingStrategy getExceptionStrategy() { |
|||
return exceptionStrategy; |
|||
} |
|||
|
|||
public String getBody() { |
|||
return body; |
|||
} |
|||
|
|||
public String getExample() { |
|||
return example; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "HttpReqTemp{" + |
|||
"method=" + method + |
|||
", url='" + url + '\'' + |
|||
", mediaType=" + mediaType + |
|||
", body='" + body + '\'' + |
|||
", queryParams=" + queryParams + |
|||
", headers=" + headers + |
|||
", timeout=" + timeout + |
|||
", retryCount=" + retryCount + |
|||
", exceptionStrategy=" + exceptionStrategy + |
|||
", example='" + example + '\'' + |
|||
'}'; |
|||
} |
|||
} |
|||
|
|||
|
|||
@ -0,0 +1,246 @@ |
|||
package com.mathvision.box.common.utils.http; |
|||
|
|||
import com.mathvision.box.common.utils.common.Threads; |
|||
import com.mathvision.box.common.utils.file.FileUtils; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.http.*; |
|||
import org.springframework.http.client.SimpleClientHttpRequestFactory; |
|||
import org.springframework.util.LinkedMultiValueMap; |
|||
import org.springframework.util.MultiValueMap; |
|||
import org.springframework.web.client.RestTemplate; |
|||
import org.springframework.web.util.UriComponentsBuilder; |
|||
|
|||
import java.nio.file.Files; |
|||
import java.nio.file.Paths; |
|||
import java.util.Arrays; |
|||
|
|||
/** |
|||
* http请求工具类 |
|||
* <p> |
|||
* 中文乱码问题:json = new String(json.getBytes("ISO-8859-1"), "UTF-8"); |
|||
*/ |
|||
@Slf4j |
|||
public class HttpUtil { |
|||
/** |
|||
* get请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @return |
|||
*/ |
|||
public static String get(String url, MultiValueMap<String, String> params) { |
|||
return get(url, params, null); |
|||
} |
|||
|
|||
/** |
|||
* get请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @param headers 请求头 |
|||
* @return |
|||
*/ |
|||
public static String get(String url, MultiValueMap<String, String> params, MultiValueMap<String, String> headers) { |
|||
return request(url, params, headers, HttpMethod.GET); |
|||
} |
|||
|
|||
/** |
|||
* post请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @return |
|||
*/ |
|||
public static String post(String url, MultiValueMap<String, String> params) { |
|||
return post(url, params, null); |
|||
} |
|||
|
|||
/** |
|||
* post请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @param headers 请求头 |
|||
* @return |
|||
*/ |
|||
public static String post(String url, MultiValueMap<String, String> params, MultiValueMap<String, String> headers) { |
|||
return request(url, params, headers, HttpMethod.POST); |
|||
} |
|||
|
|||
/** |
|||
* put请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @return |
|||
*/ |
|||
public static String put(String url, MultiValueMap<String, String> params) { |
|||
return put(url, params, null); |
|||
} |
|||
|
|||
/** |
|||
* put请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @param headers 请求头 |
|||
* @return |
|||
*/ |
|||
public static String put(String url, MultiValueMap<String, String> params, MultiValueMap<String, String> headers) { |
|||
return request(url, params, headers, HttpMethod.PUT); |
|||
} |
|||
|
|||
/** |
|||
* delete请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @return |
|||
*/ |
|||
public static String delete(String url, MultiValueMap<String, String> params) { |
|||
return delete(url, params, null); |
|||
} |
|||
|
|||
/** |
|||
* delete请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @param headers 请求头 |
|||
* @return |
|||
*/ |
|||
public static String delete(String url, MultiValueMap<String, String> params, MultiValueMap<String, String> headers) { |
|||
return request(url, params, headers, HttpMethod.DELETE); |
|||
} |
|||
|
|||
/** |
|||
* 表单请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @param headers 请求头 |
|||
* @param method 请求方式 |
|||
* @return |
|||
*/ |
|||
public static String request(String url, MultiValueMap<String, String> params, MultiValueMap<String, String> headers, HttpMethod method) { |
|||
if (params == null) { |
|||
params = new LinkedMultiValueMap<>(); |
|||
} |
|||
return request(url, params, headers, method, MediaType.APPLICATION_FORM_URLENCODED); |
|||
} |
|||
|
|||
/** |
|||
* http请求 |
|||
* |
|||
* @param url |
|||
* @param params 请求参数 |
|||
* @param headers 请求头 |
|||
* @param method 请求方式 |
|||
* @param mediaType 参数类型 |
|||
* @return |
|||
*/ |
|||
public static String request(String url, Object params, MultiValueMap<String, String> headers, HttpMethod method, MediaType mediaType) { |
|||
if (url == null || url.trim().isEmpty()) { |
|||
return null; |
|||
} |
|||
RestTemplate client = new RestTemplate(); |
|||
// header
|
|||
HttpHeaders httpHeaders = new HttpHeaders(); |
|||
if (headers != null) { |
|||
httpHeaders.addAll(headers); |
|||
} |
|||
// 提交方式:表单、json
|
|||
httpHeaders.setContentType(mediaType); |
|||
HttpEntity<Object> httpEntity = new HttpEntity(params, httpHeaders); |
|||
ResponseEntity<String> response = client.exchange(url, method, httpEntity, String.class); |
|||
return response.getBody(); |
|||
} |
|||
|
|||
public static String request(HttpReqTemp reqTemp) { |
|||
// 参数校验强化
|
|||
if (reqTemp == null) { |
|||
throw new IllegalArgumentException("HttpReqTemp cannot be null"); |
|||
} |
|||
reqTemp.validateConfig(); |
|||
|
|||
String url = reqTemp.getUrl(); |
|||
HttpMethod method = reqTemp.getMethod(); |
|||
|
|||
// 构建带有查询参数的URL
|
|||
url = UriComponentsBuilder.fromHttpUrl(url).queryParams(new LinkedMultiValueMap<>(reqTemp.getQueryParams())).build().toUriString(); |
|||
|
|||
// 配置请求头
|
|||
HttpHeaders headers = new HttpHeaders(); |
|||
reqTemp.getHeaders().forEach((key, values) -> headers.addAll(key, values)); |
|||
|
|||
// 设置Content-Type,如果未设置则使用默认值
|
|||
headers.setContentType(reqTemp.getMediaType()); |
|||
|
|||
// 构建请求实体
|
|||
HttpEntity<?> httpEntity = (reqTemp.getBody() != null) ? new HttpEntity<>(reqTemp.getBody(), headers) : new HttpEntity<>(headers); |
|||
|
|||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); |
|||
factory.setConnectTimeout(reqTemp.getTimeout()); |
|||
factory.setReadTimeout(reqTemp.getTimeout()); |
|||
RestTemplate restTemplate = new RestTemplate(factory); |
|||
|
|||
int retryCount = 0; |
|||
while (retryCount < reqTemp.getRetryCount()){ |
|||
try { |
|||
return restTemplate.exchange(url, method, httpEntity, String.class).getBody(); |
|||
}catch (Exception e){ |
|||
log.warn("请求失败(尝试{}次): {}", retryCount+1, e.getMessage()); |
|||
if (retryCount == reqTemp.getRetryCount()-1) { |
|||
if (reqTemp.getExceptionStrategy() == HttpReqTemp.ExceptionHandlingStrategy.DEFAULT) { |
|||
throw e; |
|||
} else if (reqTemp.getExceptionStrategy() == HttpReqTemp.ExceptionHandlingStrategy.FALLBACK) { |
|||
return reqTemp.getExample(); |
|||
} |
|||
} |
|||
Threads.sleep(reqTemp.getTimeout()); |
|||
retryCount++; |
|||
} |
|||
} |
|||
// // 发送请求并处理响应
|
|||
// ResponseEntity<String> response = null;
|
|||
// try {
|
|||
// response = restTemplate.exchange(url, method, httpEntity, String.class);
|
|||
// } catch (Exception e) {
|
|||
// if (reqTemp.getExceptionStrategy() == HttpReqTemp.ExceptionHandlingStrategy.DEFAULT) {
|
|||
// log.error("请求失败:{}", e.getMessage());
|
|||
// throw e;
|
|||
// } else if (reqTemp.getExceptionStrategy() == HttpReqTemp.ExceptionHandlingStrategy.FALLBACK) {
|
|||
// response = new ResponseEntity<>(reqTemp.getExample(), HttpStatus.OK);
|
|||
// }
|
|||
// }
|
|||
// return response.getBody();
|
|||
return null; |
|||
} |
|||
|
|||
public static boolean downloadImage(String imageUrl, String savePath){ |
|||
if (imageUrl == null || imageUrl.trim().isEmpty()) { |
|||
return false; |
|||
} |
|||
try { |
|||
FileUtils.checkParentDir(savePath); |
|||
|
|||
RestTemplate restTemplate = new RestTemplate(); |
|||
HttpHeaders headers = new HttpHeaders(); |
|||
headers.setAccept(Arrays.asList(MediaType.IMAGE_JPEG, MediaType.IMAGE_PNG)); |
|||
ResponseEntity<byte[]> response = restTemplate.exchange( |
|||
imageUrl, |
|||
HttpMethod.GET, |
|||
new HttpEntity<>(headers), |
|||
byte[].class |
|||
); |
|||
if (response.getStatusCode() == HttpStatus.OK) { |
|||
Files.write(Paths.get(savePath), response.getBody()); |
|||
return true; |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("下载图片失败:{}", e.getMessage()); |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,135 @@ |
|||
package com.mathvision.box.common.utils.http; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import okhttp3.*; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Map; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Slf4j |
|||
public class OkHttpUtil { |
|||
private OkHttpUtil() { |
|||
} |
|||
|
|||
/** |
|||
* 发送get请求 |
|||
* |
|||
* @param url 地址 |
|||
* @param params 参数 |
|||
* @return 请求结果 |
|||
*/ |
|||
public static String get(String url, Map<String, String> params) { |
|||
return get(url, params, null); |
|||
} |
|||
|
|||
public static String get(String url, Map<String, String> params, Map<String, String> headers) { |
|||
return request("get", url, params, headers); |
|||
} |
|||
|
|||
/** |
|||
* 发送post请求 |
|||
* |
|||
* @param url 地址 |
|||
* @param params 参数 |
|||
* @return 请求结果 |
|||
*/ |
|||
public static String post(String url, Map<String, String> params) { |
|||
return post(url, params, null); |
|||
} |
|||
|
|||
public static String post(String url, Map<String, String> params, Map<String, String> headers) { |
|||
return request("post", url, params, headers); |
|||
} |
|||
|
|||
/** |
|||
* 发送http请求 |
|||
* |
|||
* @param method 请求方法 |
|||
* @param url 地址 |
|||
* @param params 参数 |
|||
* @param headers 请求头 |
|||
* @return 请求结果 |
|||
*/ |
|||
public static String request(String method, String url, Map<String, String> params, Map<String, String> headers) { |
|||
if (method == null) { |
|||
throw new RuntimeException("请求方法不能为空"); |
|||
} |
|||
|
|||
if (url == null) { |
|||
throw new RuntimeException("url不能为空"); |
|||
} |
|||
|
|||
HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder(); |
|||
RequestBody requestBody = null; |
|||
|
|||
if ("get".equals(method) || "head".equals(method) || "delete".equals(method)) { |
|||
// 添加 QueryParam
|
|||
if (params != null) { |
|||
for (Map.Entry<String, String> param : params.entrySet()) { |
|||
httpBuilder.addQueryParameter(param.getKey(), param.getValue()); |
|||
} |
|||
} |
|||
} else { |
|||
FormBody.Builder formBodyBuilder = new FormBody.Builder(); |
|||
if (params != null) { |
|||
for (Map.Entry<String, String> param : params.entrySet()) { |
|||
formBodyBuilder.add(param.getKey(), param.getValue()); |
|||
} |
|||
} |
|||
requestBody = formBodyBuilder.build(); |
|||
} |
|||
Request.Builder requestBuilder = new Request.Builder().url(httpBuilder.build()); |
|||
|
|||
// 添加请求头
|
|||
if (headers != null) { |
|||
for (Map.Entry<String, String> header : headers.entrySet()) { |
|||
requestBuilder.addHeader(header.getKey(), header.getValue()); |
|||
} |
|||
} |
|||
|
|||
// 设置请求方法和请求体
|
|||
if (requestBody != null) { |
|||
requestBuilder.method(method, requestBody); |
|||
} else { |
|||
requestBuilder.method(method, new FormBody.Builder().build()); |
|||
} |
|||
|
|||
Request request = requestBuilder.build(); |
|||
|
|||
try { |
|||
OkHttpClient client = new OkHttpClient.Builder() |
|||
.readTimeout(20, TimeUnit.SECONDS) |
|||
.build(); |
|||
Response response = client.newCall(request).execute(); |
|||
if (!response.isSuccessful()) { |
|||
throw new IOException("请求失败,状态码: " + response.code()); |
|||
} |
|||
return response.body().string(); |
|||
} catch (IOException e) { |
|||
log.error("[OKHttpUtil]: 请求失败{}", e.getMessage()); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送post请求(json格式) |
|||
* |
|||
* @param url url |
|||
* @param json json字符串 |
|||
* @return 请求结果 |
|||
*/ |
|||
public static String postJson(String url, String json) { |
|||
Request request = new Request.Builder() |
|||
.url(url) |
|||
.post(RequestBody.Companion.create(json, MediaType.Companion.parse("application/json"))) |
|||
.build(); |
|||
try { |
|||
OkHttpClient client = new OkHttpClient(); |
|||
Response response = client.newCall(request).execute(); |
|||
return response.body().string(); |
|||
} catch (IOException e) { |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
package com.mathvision.box.common.utils.poi; |
|||
|
|||
|
|||
import org.apache.poi.ss.usermodel.Cell; |
|||
import org.apache.poi.ss.usermodel.Workbook; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/04/08 |
|||
* @Description:Excel数据格式处理适配器 |
|||
*/ |
|||
public interface ExcelHandlerAdapter |
|||
{ |
|||
/** |
|||
* 格式化 |
|||
* |
|||
* @param value 单元格数据值 |
|||
* @param args excel注解args参数组 |
|||
* @param cell 单元格对象 |
|||
* @param wb 工作簿对象 |
|||
* |
|||
* @return 处理后的值 |
|||
*/ |
|||
Object format(Object value, String[] args, Cell cell, Workbook wb); |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,405 @@ |
|||
package com.mathvision.box.common.utils.reflect; |
|||
|
|||
import com.mathvision.box.common.core.text.Convert; |
|||
import com.mathvision.box.common.utils.common.DateUtils; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.apache.commons.lang3.Validate; |
|||
import org.apache.poi.ss.usermodel.DateUtil; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import java.lang.reflect.*; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. |
|||
* |
|||
*/ |
|||
@SuppressWarnings("rawtypes") |
|||
public class ReflectUtils |
|||
{ |
|||
private static final String SETTER_PREFIX = "set"; |
|||
|
|||
private static final String GETTER_PREFIX = "get"; |
|||
|
|||
private static final String CGLIB_CLASS_SEPARATOR = "$$"; |
|||
|
|||
private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); |
|||
|
|||
/** |
|||
* 调用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 = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); |
|||
} |
|||
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 = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); |
|||
} |
|||
else |
|||
{ |
|||
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); |
|||
invokeMethodByName(object, setterMethodName, new Object[] { value }); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public static <E> E getFieldValue(final Object obj, final String fieldName) |
|||
{ |
|||
Field field = getAccessibleField(obj, fieldName); |
|||
if (field == null) |
|||
{ |
|||
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); |
|||
return null; |
|||
} |
|||
E result = null; |
|||
try |
|||
{ |
|||
result = (E) field.get(obj); |
|||
} |
|||
catch (IllegalAccessException e) |
|||
{ |
|||
logger.error("不可能抛出的异常{}", e.getMessage()); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. |
|||
*/ |
|||
public static <E> void setFieldValue(final Object obj, final String fieldName, final E value) |
|||
{ |
|||
Field field = getAccessibleField(obj, fieldName); |
|||
if (field == null) |
|||
{ |
|||
// throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
|
|||
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); |
|||
return; |
|||
} |
|||
try |
|||
{ |
|||
field.set(obj, value); |
|||
} |
|||
catch (IllegalAccessException e) |
|||
{ |
|||
logger.error("不可能抛出的异常: {}", e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 直接调用对象方法, 无视private/protected修饰符. |
|||
* 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. |
|||
* 同时匹配方法名+参数类型, |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes, |
|||
final Object[] args) |
|||
{ |
|||
if (obj == null || methodName == null) |
|||
{ |
|||
return null; |
|||
} |
|||
Method method = getAccessibleMethod(obj, methodName, parameterTypes); |
|||
if (method == null) |
|||
{ |
|||
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); |
|||
return null; |
|||
} |
|||
try |
|||
{ |
|||
return (E) method.invoke(obj, args); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; |
|||
throw convertReflectionExceptionToUnchecked(msg, e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 直接调用对象方法, 无视private/protected修饰符, |
|||
* 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. |
|||
* 只匹配函数名,如果有多个同名函数调用第一个。 |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args) |
|||
{ |
|||
Method method = getAccessibleMethodByName(obj, methodName, args.length); |
|||
if (method == null) |
|||
{ |
|||
// 如果为空不报错,直接返回空。
|
|||
logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); |
|||
return null; |
|||
} |
|||
try |
|||
{ |
|||
// 类型转换(将参数数据类型转换为目标方法参数类型)
|
|||
Class<?>[] cs = method.getParameterTypes(); |
|||
for (int i = 0; i < cs.length; i++) |
|||
{ |
|||
if (args[i] != null && !args[i].getClass().equals(cs[i])) |
|||
{ |
|||
if (cs[i] == String.class) |
|||
{ |
|||
args[i] = Convert.toStr(args[i]); |
|||
if (StringUtils.endsWith((String) args[i], ".0")) |
|||
{ |
|||
args[i] = StringUtils.substringBefore((String) args[i], ".0"); |
|||
} |
|||
} |
|||
else if (cs[i] == Integer.class) |
|||
{ |
|||
args[i] = Convert.toInt(args[i]); |
|||
} |
|||
else if (cs[i] == Long.class) |
|||
{ |
|||
args[i] = Convert.toLong(args[i]); |
|||
} |
|||
else if (cs[i] == Double.class) |
|||
{ |
|||
args[i] = Convert.toDouble(args[i]); |
|||
} |
|||
else if (cs[i] == Float.class) |
|||
{ |
|||
args[i] = Convert.toFloat(args[i]); |
|||
} |
|||
else if (cs[i] == Date.class) |
|||
{ |
|||
if (args[i] instanceof String) |
|||
{ |
|||
args[i] = DateUtils.parseDate(args[i]); |
|||
} |
|||
else |
|||
{ |
|||
args[i] = DateUtil.getJavaDate((Double) args[i]); |
|||
} |
|||
} |
|||
else if (cs[i] == boolean.class || cs[i] == Boolean.class) |
|||
{ |
|||
args[i] = Convert.toBool(args[i]); |
|||
} |
|||
} |
|||
} |
|||
return (E) method.invoke(obj, args); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; |
|||
throw convertReflectionExceptionToUnchecked(msg, e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. |
|||
* 如向上转型到Object仍无法找到, 返回null. |
|||
*/ |
|||
public static Field getAccessibleField(final Object obj, final String fieldName) |
|||
{ |
|||
// 为空不报错。直接返回 null
|
|||
if (obj == null) |
|||
{ |
|||
return null; |
|||
} |
|||
Validate.notBlank(fieldName, "fieldName can't be blank"); |
|||
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) |
|||
{ |
|||
try |
|||
{ |
|||
Field field = superClass.getDeclaredField(fieldName); |
|||
makeAccessible(field); |
|||
return field; |
|||
} |
|||
catch (NoSuchFieldException e) |
|||
{ |
|||
continue; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. |
|||
* 如向上转型到Object仍无法找到, 返回null. |
|||
* 匹配函数名+参数类型。 |
|||
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) |
|||
*/ |
|||
public static Method getAccessibleMethod(final Object obj, final String methodName, |
|||
final Class<?>... parameterTypes) |
|||
{ |
|||
// 为空不报错。直接返回 null
|
|||
if (obj == null) |
|||
{ |
|||
return null; |
|||
} |
|||
Validate.notBlank(methodName, "methodName can't be blank"); |
|||
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) |
|||
{ |
|||
try |
|||
{ |
|||
Method method = searchType.getDeclaredMethod(methodName, parameterTypes); |
|||
makeAccessible(method); |
|||
return method; |
|||
} |
|||
catch (NoSuchMethodException e) |
|||
{ |
|||
continue; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. |
|||
* 如向上转型到Object仍无法找到, 返回null. |
|||
* 只匹配函数名。 |
|||
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) |
|||
*/ |
|||
public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) |
|||
{ |
|||
// 为空不报错。直接返回 null
|
|||
if (obj == null) |
|||
{ |
|||
return null; |
|||
} |
|||
Validate.notBlank(methodName, "methodName can't be blank"); |
|||
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) |
|||
{ |
|||
Method[] methods = searchType.getDeclaredMethods(); |
|||
for (Method method : methods) |
|||
{ |
|||
if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) |
|||
{ |
|||
makeAccessible(method); |
|||
return method; |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 |
|||
*/ |
|||
public static void makeAccessible(Method method) |
|||
{ |
|||
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) |
|||
&& !method.isAccessible()) |
|||
{ |
|||
method.setAccessible(true); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 |
|||
*/ |
|||
public static void makeAccessible(Field field) |
|||
{ |
|||
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) |
|||
|| Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) |
|||
{ |
|||
field.setAccessible(true); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 |
|||
* 如无法找到, 返回Object.class. |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public static <T> Class<T> getClassGenricType(final Class clazz) |
|||
{ |
|||
return getClassGenricType(clazz, 0); |
|||
} |
|||
|
|||
/** |
|||
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型. |
|||
* 如无法找到, 返回Object.class. |
|||
*/ |
|||
public static Class getClassGenricType(final Class clazz, final int index) |
|||
{ |
|||
Type genType = clazz.getGenericSuperclass(); |
|||
|
|||
if (!(genType instanceof ParameterizedType)) |
|||
{ |
|||
logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); |
|||
return Object.class; |
|||
} |
|||
|
|||
Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); |
|||
|
|||
if (index >= params.length || index < 0) |
|||
{ |
|||
logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " |
|||
+ params.length); |
|||
return Object.class; |
|||
} |
|||
if (!(params[index] instanceof Class)) |
|||
{ |
|||
logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); |
|||
return Object.class; |
|||
} |
|||
|
|||
return (Class) params[index]; |
|||
} |
|||
|
|||
public static Class<?> getUserClass(Object instance) |
|||
{ |
|||
if (instance == null) |
|||
{ |
|||
throw new RuntimeException("Instance must not be null"); |
|||
} |
|||
Class clazz = instance.getClass(); |
|||
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) |
|||
{ |
|||
Class<?> superClass = clazz.getSuperclass(); |
|||
if (superClass != null && !Object.class.equals(superClass)) |
|||
{ |
|||
return superClass; |
|||
} |
|||
} |
|||
return clazz; |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 将反射时的checked exception转换为unchecked exception. |
|||
*/ |
|||
public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) |
|||
{ |
|||
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException |
|||
|| e instanceof NoSuchMethodException) |
|||
{ |
|||
return new IllegalArgumentException(msg, e); |
|||
} |
|||
else if (e instanceof InvocationTargetException) |
|||
{ |
|||
return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); |
|||
} |
|||
return new RuntimeException(msg, e); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
package com.mathvision.box.common.utils.security; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.auth0.jwt.JWT; |
|||
import com.auth0.jwt.algorithms.Algorithm; |
|||
import com.auth0.jwt.interfaces.Claim; |
|||
import com.auth0.jwt.interfaces.DecodedJWT; |
|||
import com.mathvision.box.common.core.domain.entity.UserCache; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:生成token |
|||
*/ |
|||
@Component |
|||
public class JwtUtils { |
|||
|
|||
@Value("${jwt.config.secretKey}") |
|||
private String secretKey; |
|||
|
|||
/** |
|||
* 加密token. |
|||
*/ |
|||
public String getToken(UserCache user) { |
|||
String token = JWT |
|||
.create() |
|||
.withClaim("user", JSON.toJSONString(user)) |
|||
// .withClaim("timeStamp", System.currentTimeMillis())
|
|||
.sign(Algorithm.HMAC256(secretKey)); |
|||
return token; |
|||
} |
|||
|
|||
/** |
|||
* 解析token. |
|||
*/ |
|||
public UserCache parseToken(String token) { |
|||
DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token); |
|||
Claim user = decodedjwt.getClaim("user"); |
|||
return JSON.parseObject(user.asString(), UserCache.class); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package com.mathvision.box.common.utils.security; |
|||
|
|||
import com.mathvision.box.common.core.domain.entity.UserCache; |
|||
|
|||
/** |
|||
* 线程安全用户会话 |
|||
*/ |
|||
public class SessionHolder { |
|||
private static final ThreadLocal<UserCache> USER_SESSION = new ThreadLocal<>(); |
|||
|
|||
public static void set(UserCache user) { |
|||
USER_SESSION.set(user); |
|||
} |
|||
|
|||
public static UserCache get() { |
|||
return USER_SESSION.get(); |
|||
} |
|||
|
|||
public static void clear() { |
|||
USER_SESSION.remove(); |
|||
} |
|||
} |
|||
@ -0,0 +1,157 @@ |
|||
package com.mathvision.box.common.utils.spring; |
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
import org.springframework.aop.framework.AopContext; |
|||
import org.springframework.beans.BeansException; |
|||
import org.springframework.beans.factory.NoSuchBeanDefinitionException; |
|||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; |
|||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
|||
import org.springframework.context.ApplicationContext; |
|||
import org.springframework.context.ApplicationContextAware; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* spring工具类 方便在非spring管理环境中获取bean |
|||
*/ |
|||
@Component |
|||
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware |
|||
{ |
|||
/** Spring应用上下文环境 */ |
|||
private static ConfigurableListableBeanFactory beanFactory; |
|||
|
|||
private static ApplicationContext applicationContext; |
|||
|
|||
@Override |
|||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException |
|||
{ |
|||
SpringUtils.beanFactory = beanFactory; |
|||
} |
|||
|
|||
@Override |
|||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException |
|||
{ |
|||
SpringUtils.applicationContext = applicationContext; |
|||
} |
|||
|
|||
/** |
|||
* 获取对象 |
|||
* |
|||
* @param name |
|||
* @return Object 一个以所给名字注册的bean的实例 |
|||
* @throws BeansException |
|||
* |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public static <T> T getBean(String name) throws BeansException |
|||
{ |
|||
return (T) beanFactory.getBean(name); |
|||
} |
|||
|
|||
/** |
|||
* 获取类型为requiredType的对象 |
|||
* |
|||
* @param clz |
|||
* @return |
|||
* @throws BeansException |
|||
* |
|||
*/ |
|||
public static <T> T getBean(Class<T> clz) throws BeansException |
|||
{ |
|||
T result = (T) beanFactory.getBean(clz); |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true |
|||
* |
|||
* @param name |
|||
* @return boolean |
|||
*/ |
|||
public static boolean containsBean(String name) |
|||
{ |
|||
return beanFactory.containsBean(name); |
|||
} |
|||
|
|||
/** |
|||
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) |
|||
* |
|||
* @param name |
|||
* @return boolean |
|||
* @throws NoSuchBeanDefinitionException |
|||
* |
|||
*/ |
|||
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException |
|||
{ |
|||
return beanFactory.isSingleton(name); |
|||
} |
|||
|
|||
/** |
|||
* @param name |
|||
* @return Class 注册对象的类型 |
|||
* @throws NoSuchBeanDefinitionException |
|||
* |
|||
*/ |
|||
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException |
|||
{ |
|||
return beanFactory.getType(name); |
|||
} |
|||
|
|||
/** |
|||
* 如果给定的bean名字在bean定义中有别名,则返回这些别名 |
|||
* |
|||
* @param name |
|||
* @return |
|||
* @throws NoSuchBeanDefinitionException |
|||
* |
|||
*/ |
|||
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException |
|||
{ |
|||
return beanFactory.getAliases(name); |
|||
} |
|||
|
|||
/** |
|||
* 获取aop代理对象 |
|||
* |
|||
* @param invoker |
|||
* @return |
|||
*/ |
|||
@SuppressWarnings("unchecked") |
|||
public static <T> T getAopProxy(T invoker) |
|||
{ |
|||
return (T) AopContext.currentProxy(); |
|||
} |
|||
|
|||
/** |
|||
* 获取当前的环境配置,无配置返回null |
|||
* |
|||
* @return 当前的环境配置 |
|||
*/ |
|||
public static String[] getActiveProfiles() |
|||
{ |
|||
return applicationContext.getEnvironment().getActiveProfiles(); |
|||
} |
|||
|
|||
/** |
|||
* 获取当前的环境配置,当有多个环境配置时,只获取第一个 |
|||
* |
|||
* @return 当前的环境配置 |
|||
*/ |
|||
public static String getActiveProfile() |
|||
{ |
|||
final String[] activeProfiles = getActiveProfiles(); |
|||
return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; |
|||
} |
|||
|
|||
/** |
|||
* 获取配置文件中的值 |
|||
* |
|||
* @param key 配置文件的key |
|||
* @return 当前的配置文件的值 |
|||
* |
|||
*/ |
|||
public static String getRequiredProperty(String key) |
|||
{ |
|||
return applicationContext.getEnvironment().getRequiredProperty(key); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
package com.mathvision.box.common.utils.sql; |
|||
|
|||
|
|||
import com.mathvision.box.common.core.domain.Result; |
|||
import com.mathvision.box.common.exception.base.BaseException; |
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
/** |
|||
* @Author: fy |
|||
* @Date: 2024/03/22 |
|||
* @Description:sql操作工具类 |
|||
*/ |
|||
public class SqlUtil |
|||
{ |
|||
/** |
|||
* 定义常用的 sql关键字 |
|||
*/ |
|||
public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()"; |
|||
|
|||
/** |
|||
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) |
|||
*/ |
|||
public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; |
|||
|
|||
/** |
|||
* 限制orderBy最大长度 |
|||
*/ |
|||
private static final int ORDER_BY_MAX_LENGTH = 500; |
|||
|
|||
/** |
|||
* 检查字符,防止注入绕过 |
|||
*/ |
|||
public static String escapeOrderBySql(String value) |
|||
{ |
|||
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) |
|||
{ |
|||
|
|||
throw new BaseException(Result.ILLEGAL_ARGUMENT); |
|||
} |
|||
if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH) |
|||
{ |
|||
Result result = Result.FAILURE; |
|||
result.setDesc("参数已超过最大限制,不能进行查询"); |
|||
throw new BaseException(result); |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
/** |
|||
* 验证 order by 语法是否符合规范 |
|||
*/ |
|||
public static boolean isValidOrderBySql(String value) |
|||
{ |
|||
return value.matches(SQL_PATTERN); |
|||
} |
|||
|
|||
/** |
|||
* SQL关键字检查 |
|||
*/ |
|||
public static void filterKeyword(String value) |
|||
{ |
|||
if (StringUtils.isEmpty(value)) |
|||
{ |
|||
return; |
|||
} |
|||
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); |
|||
for (String sqlKeyword : sqlKeywords) |
|||
{ |
|||
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) |
|||
{ |
|||
Result result = Result.FAILURE; |
|||
result.setDesc("参数存在SQL注入风险"); |
|||
throw new BaseException(result); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
package com.mathvision.box.common.utils.tree; |
|||
|
|||
import com.mathvision.box.common.annotation.tree.TreeNodeId; |
|||
import com.mathvision.box.common.annotation.tree.TreeNodeParentId; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.util.CollectionUtils; |
|||
|
|||
import java.lang.reflect.Field; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* @Description:树型结构数据构建工具 |
|||
* @Version: 1.0 |
|||
* @Author: yyy |
|||
*/ |
|||
@Slf4j |
|||
public class TreeBuilderUtil { |
|||
|
|||
public static <T> List<TreeNode<T>> buildTree(List<T> nodes) { |
|||
if (CollectionUtils.isEmpty(nodes)) { |
|||
return new ArrayList<>(); |
|||
} |
|||
Map<Object, TreeNode<T>> nodeMap = new HashMap<>(); |
|||
List<TreeNode<T>> rootNodes = new ArrayList<>(); |
|||
|
|||
Field idField = null; |
|||
Field parentIdField = null; |
|||
|
|||
// 找到标注了注解的字段
|
|||
for (Field field : nodes.get(0).getClass().getDeclaredFields()) { |
|||
if (field.isAnnotationPresent(TreeNodeId.class)) { |
|||
idField = field; |
|||
idField.setAccessible(true); |
|||
} |
|||
if (field.isAnnotationPresent(TreeNodeParentId.class)) { |
|||
parentIdField = field; |
|||
parentIdField.setAccessible(true); |
|||
} |
|||
} |
|||
if (idField == null || parentIdField == null) { |
|||
throw new IllegalArgumentException("Missing TreeNodeId or TreeNodeParentId annotation"); |
|||
} |
|||
try { |
|||
// 将节点存储到Map中
|
|||
for (T node : nodes) { |
|||
Object id = idField.get(node); |
|||
TreeNode<T> treeNode = new TreeNode<>(node); |
|||
nodeMap.put(id, treeNode); |
|||
} |
|||
|
|||
// 构建树形结构
|
|||
for (T node : nodes) { |
|||
Object parentId = parentIdField.get(node); |
|||
TreeNode<T> treeNode = nodeMap.get(idField.get(node)); |
|||
if (parentId == null) { |
|||
rootNodes.add(treeNode); |
|||
treeNode.setPath("/" + treeNode.getId() + "-" + treeNode.getName()); |
|||
} else { |
|||
TreeNode<T> parentNode = nodeMap.get(parentId); |
|||
if (parentNode != null) { |
|||
parentNode.getChildren().add(treeNode); |
|||
treeNode.setParent(parentNode); |
|||
treeNode.setPath(parentNode.getPath() + "/" + treeNode.getId() + "-" + treeNode.getName()); |
|||
} else { |
|||
rootNodes.add(treeNode); |
|||
treeNode.setPath("/" + treeNode.getId() + "-" + treeNode.getName()); |
|||
} |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Error in buildTree:{}", e); |
|||
} |
|||
return rootNodes; |
|||
} |
|||
|
|||
/** |
|||
* 根据ID在树结构中查找节点 |
|||
* |
|||
* @param treeNodes 树节点列表 |
|||
* @param id 要查找的ID |
|||
* @return 找到的节点, 未找到返回null |
|||
*/ |
|||
public static <T> TreeNode<T> getById(List<TreeNode<T>> treeNodes, Long id) { |
|||
// 参数校验
|
|||
if (CollectionUtils.isEmpty(treeNodes)) { |
|||
log.debug("Tree nodes list is empty"); |
|||
return null; |
|||
} |
|||
|
|||
if (id == null) { |
|||
log.warn("Search id is null"); |
|||
return null; |
|||
} |
|||
|
|||
try { |
|||
// 遍历顶层节点
|
|||
for (TreeNode<T> node : treeNodes) { |
|||
TreeNode<T> result = findNodeById(node, id); |
|||
if (result != null) { |
|||
return result; |
|||
} |
|||
} |
|||
log.debug("No node found with id: {}", id); |
|||
return null; |
|||
} catch (Exception e) { |
|||
log.error("Error while searching node with id: {}", id, e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 递归查找节点 |
|||
* |
|||
* @param node 当前节点 |
|||
* @param id 要查找的ID |
|||
* @return 找到的节点, 未找到返回null |
|||
*/ |
|||
private static <T> TreeNode<T> findNodeById(TreeNode<T> node, Long id) { |
|||
// 参数校验
|
|||
if (node == null || id == null) { |
|||
return null; |
|||
} |
|||
|
|||
try { |
|||
// 检查当前节点
|
|||
if (id.equals(node.getId())) { |
|||
return node; |
|||
} |
|||
|
|||
// 递归检查子节点
|
|||
List<TreeNode<T>> children = node.getChildren(); |
|||
if (!CollectionUtils.isEmpty(children)) { |
|||
for (TreeNode<T> child : children) { |
|||
TreeNode<T> result = findNodeById(child, id); |
|||
if (result != null) { |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} catch (Exception e) { |
|||
log.error("Error in findNodeById for id: {}, node: {}", id, node, e); |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
package com.mathvision.box.common.utils.tree; |
|||
|
|||
|
|||
import com.mathvision.box.common.annotation.tree.TreeNodeId; |
|||
import com.mathvision.box.common.annotation.tree.TreeNodeName; |
|||
|
|||
import java.lang.reflect.Field; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Stack; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* @Description:树节点 |
|||
* @Version: 1.0 |
|||
* @Author: yyy |
|||
*/ |
|||
public class TreeNode<T> { |
|||
private T data; |
|||
private List<TreeNode<T>> children = new ArrayList<>(); |
|||
private TreeNode<T> parent; |
|||
private String path; |
|||
|
|||
public TreeNode(T data) { |
|||
this.data = data; |
|||
} |
|||
|
|||
public T getData() { |
|||
return data; |
|||
} |
|||
|
|||
public void setData(T data) { |
|||
this.data = data; |
|||
} |
|||
|
|||
public List<TreeNode<T>> getChildren() { |
|||
return children; |
|||
} |
|||
|
|||
public void setChildren(List<TreeNode<T>> children) { |
|||
this.children = children; |
|||
} |
|||
|
|||
public TreeNode<T> getParent() { |
|||
return parent; |
|||
} |
|||
|
|||
public void setParent(TreeNode<T> parent) { |
|||
this.parent = parent; |
|||
} |
|||
|
|||
public String getPath() { |
|||
return path; |
|||
} |
|||
|
|||
//path = /1-资产/2-办公用品/3-办公用品/4-办公用品/5-办公用品
|
|||
public String getRootId() { |
|||
return path.split("/")[1].split("-")[0]; |
|||
} |
|||
|
|||
public String getRootName() { |
|||
return path.split("/")[1].split("-")[1]; |
|||
} |
|||
|
|||
public String getIdPath() { |
|||
String[] segments = path.split("/"); |
|||
StringBuilder idPath = new StringBuilder(); |
|||
for (int i = 1; i < segments.length; i++) { |
|||
String[] parts = segments[i].split("-"); |
|||
if (parts.length > 1) { |
|||
idPath.append(parts[0]); |
|||
if (i < segments.length - 1) { |
|||
idPath.append("/"); |
|||
} |
|||
} |
|||
} |
|||
return idPath.toString(); |
|||
} |
|||
|
|||
public String getNamePath() { |
|||
String[] segments = path.split("/"); |
|||
StringBuilder namePath = new StringBuilder(); |
|||
for (int i = 1; i < segments.length; i++) { |
|||
String[] parts = segments[i].split("-"); |
|||
if (parts.length > 1) { |
|||
namePath.append(parts[1]); |
|||
if (i < segments.length - 1) { |
|||
namePath.append("/"); |
|||
} |
|||
} |
|||
} |
|||
return namePath.toString(); |
|||
} |
|||
|
|||
public void setPath(String path) { |
|||
this.path = path; |
|||
} |
|||
|
|||
public List<Object> getChildIds() { |
|||
List<Object> idList = new ArrayList<>(); |
|||
// 检查 children 是否为 null 或空
|
|||
if (children == null || children.isEmpty()) { |
|||
return idList; |
|||
} |
|||
// 使用栈实现迭代遍历
|
|||
Stack<TreeNode<T>> stack = new Stack<>(); |
|||
for (TreeNode<T> node : children) { |
|||
stack.push(node); |
|||
} |
|||
while (!stack.isEmpty()) { |
|||
TreeNode<T> currentNode = stack.pop(); |
|||
idList.add(currentNode.getId()); |
|||
// 将当前节点的子节点压入栈中
|
|||
if (currentNode.getChildren() != null && !currentNode.getChildren().isEmpty()) { |
|||
for (TreeNode<T> child : currentNode.getChildren()) { |
|||
stack.push(child); |
|||
} |
|||
} |
|||
} |
|||
return idList; |
|||
} |
|||
|
|||
|
|||
public List<String> getChildNames() { |
|||
return children.stream() |
|||
.map(TreeNode::getName) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
public Object getParentId() { |
|||
return parent != null ? getId(parent.getData()) : null; |
|||
} |
|||
|
|||
public String getParentName() { |
|||
return parent != null ? getName(parent.getData()) : null; |
|||
} |
|||
|
|||
public Object getId() { |
|||
return getId(this.data); |
|||
} |
|||
|
|||
public String getName() { |
|||
return getName(this.data); |
|||
} |
|||
|
|||
private Object getId(T node) { |
|||
try { |
|||
for (Field field : node.getClass().getDeclaredFields()) { |
|||
if (field.isAnnotationPresent(TreeNodeId.class)) { |
|||
field.setAccessible(true); |
|||
return field.get(node); |
|||
} |
|||
} |
|||
} catch (IllegalAccessException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private String getName(T node) { |
|||
try { |
|||
for (Field field : node.getClass().getDeclaredFields()) { |
|||
if (field.isAnnotationPresent(TreeNodeName.class)) { |
|||
field.setAccessible(true); |
|||
return (String) field.get(node); |
|||
} |
|||
} |
|||
} catch (IllegalAccessException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
package com.mathvision.box.common.utils.uuid; |
|||
|
|||
/** |
|||
* ID生成器工具类 |
|||
*/ |
|||
public class IdUtils |
|||
{ |
|||
/** |
|||
* 获取随机UUID |
|||
* |
|||
* @return 随机UUID |
|||
*/ |
|||
public static String randomUUID() |
|||
{ |
|||
return UUID.randomUUID().toString(); |
|||
} |
|||
|
|||
/** |
|||
* 简化的UUID,去掉了横线 |
|||
* |
|||
* @return 简化的UUID,去掉了横线 |
|||
*/ |
|||
public static String simpleUUID() |
|||
{ |
|||
return UUID.randomUUID().toString(true); |
|||
} |
|||
|
|||
/** |
|||
* 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID |
|||
* |
|||
* @return 随机UUID |
|||
*/ |
|||
public static String fastUUID() |
|||
{ |
|||
return UUID.fastUUID().toString(); |
|||
} |
|||
|
|||
/** |
|||
* 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID |
|||
* |
|||
* @return 简化的UUID,去掉了横线 |
|||
*/ |
|||
public static String fastSimpleUUID() |
|||
{ |
|||
return UUID.fastUUID().toString(true); |
|||
} |
|||
|
|||
|
|||
public static String shortUUID() |
|||
{ |
|||
return UUID.randomUUID().toString().substring(0, 8); |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
package com.mathvision.box.common.utils.uuid; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.DateUtils; |
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
/** |
|||
* 序列生成类 |
|||
*/ |
|||
public class Seq |
|||
{ |
|||
// 通用序列类型
|
|||
public static final String commSeqType = "COMMON"; |
|||
|
|||
// 上传序列类型
|
|||
public static final String uploadSeqType = "UPLOAD"; |
|||
|
|||
// 通用接口序列数
|
|||
private static AtomicInteger commSeq = new AtomicInteger(1); |
|||
|
|||
// 上传接口序列数
|
|||
private static AtomicInteger uploadSeq = new AtomicInteger(1); |
|||
|
|||
// 机器标识
|
|||
private static final String machineCode = "A"; |
|||
|
|||
/** |
|||
* 获取通用序列号 |
|||
* |
|||
* @return 序列值 |
|||
*/ |
|||
public static String getId() |
|||
{ |
|||
return getId(commSeqType); |
|||
} |
|||
|
|||
/** |
|||
* 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 |
|||
* |
|||
* @return 序列值 |
|||
*/ |
|||
public static String getId(String type) |
|||
{ |
|||
AtomicInteger atomicInt = commSeq; |
|||
if (uploadSeqType.equals(type)) |
|||
{ |
|||
atomicInt = uploadSeq; |
|||
} |
|||
return getId(atomicInt, 3); |
|||
} |
|||
|
|||
/** |
|||
* 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 |
|||
* |
|||
* @param atomicInt 序列数 |
|||
* @param length 数值长度 |
|||
* @return 序列值 |
|||
*/ |
|||
public static String getId(AtomicInteger atomicInt, int length) |
|||
{ |
|||
String result = DateUtils.dateTimeNow(); |
|||
result += machineCode; |
|||
result += getSeq(atomicInt, length); |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 |
|||
* |
|||
* @return 序列值 |
|||
*/ |
|||
private synchronized static String getSeq(AtomicInteger atomicInt, int length) |
|||
{ |
|||
// 先取值再+1
|
|||
int value = atomicInt.getAndIncrement(); |
|||
|
|||
// 如果更新后值>=10 的 (length)幂次方则重置为1
|
|||
int maxSeq = (int) Math.pow(10, length); |
|||
if (atomicInt.get() >= maxSeq) |
|||
{ |
|||
atomicInt.set(1); |
|||
} |
|||
// 转字符串,用0左补齐
|
|||
return StringUtils.padl(value, length); |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
package com.mathvision.box.common.utils.uuid; |
|||
import cn.hutool.core.lang.Singleton; |
|||
|
|||
/** |
|||
* Created with IntelliJ IDEA. |
|||
* |
|||
* @Author: fy |
|||
* @Date: 2023/05/09/15:41 |
|||
* @Description:雪花算法工具类 |
|||
*/ |
|||
public class SnowFlakeUtil { |
|||
private static final long START_STMP = 1420041600000L; |
|||
private static final long SEQUENCE_BIT = 9L; |
|||
private static final long MACHINE_BIT = 2L; |
|||
private static final long DATACENTER_BIT = 2L; |
|||
private static final long MAX_SEQUENCE = 511L; |
|||
private static final long MAX_MACHINE_NUM = 3L; |
|||
private static final long MAX_DATACENTER_NUM = 3L; |
|||
private static final long MACHINE_LEFT = 9L; |
|||
private static final long DATACENTER_LEFT = 11L; |
|||
private static final long TIMESTMP_LEFT = 13L; |
|||
private long datacenterId; |
|||
private long machineId; |
|||
private long sequence = 0L; |
|||
private long lastStmp = -1L; |
|||
|
|||
public SnowFlakeUtil(long datacenterId, long machineId) { |
|||
if (datacenterId <= 3L && datacenterId >= 0L) { |
|||
if (machineId <= 3L && machineId >= 0L) { |
|||
this.datacenterId = datacenterId; |
|||
this.machineId = machineId; |
|||
} else { |
|||
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); |
|||
} |
|||
} else { |
|||
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); |
|||
} |
|||
} |
|||
|
|||
public synchronized long nextId() { |
|||
long currStmp = this.getNewstmp(); |
|||
if (currStmp < this.lastStmp) { |
|||
throw new RuntimeException("Clock moved backwards. Refusing to generate id"); |
|||
} else { |
|||
if (currStmp == this.lastStmp) { |
|||
this.sequence = this.sequence + 1L & 511L; |
|||
if (this.sequence == 0L) { |
|||
currStmp = this.getNextMill(); |
|||
} |
|||
} else { |
|||
this.sequence = 0L; |
|||
} |
|||
|
|||
this.lastStmp = currStmp; |
|||
return currStmp - 1420041600000L << 13 | this.datacenterId << 11 | this.machineId << 9 | this.sequence; |
|||
} |
|||
} |
|||
|
|||
private long getNextMill() { |
|||
long mill; |
|||
for(mill = this.getNewstmp(); mill <= this.lastStmp; mill = this.getNewstmp()) { |
|||
} |
|||
|
|||
return mill; |
|||
} |
|||
|
|||
private long getNewstmp() { |
|||
return System.currentTimeMillis(); |
|||
} |
|||
|
|||
public static Long getDefaultSnowFlakeId() { |
|||
return ((SnowFlakeUtil)Singleton.get(SnowFlakeUtil.class, new Object[]{1L, 1L})).nextId(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,485 @@ |
|||
package com.mathvision.box.common.utils.uuid; |
|||
|
|||
|
|||
import com.mathvision.box.common.exception.UtilException; |
|||
|
|||
import java.security.MessageDigest; |
|||
import java.security.NoSuchAlgorithmException; |
|||
import java.security.SecureRandom; |
|||
import java.util.Random; |
|||
import java.util.concurrent.ThreadLocalRandom; |
|||
|
|||
/** |
|||
* 提供通用唯一识别码(universally unique identifier)(UUID)实现 |
|||
*/ |
|||
public final class UUID implements java.io.Serializable, Comparable<UUID> |
|||
{ |
|||
private static final long serialVersionUID = -1185015143654744140L; |
|||
|
|||
/** |
|||
* SecureRandom 的单例 |
|||
* |
|||
*/ |
|||
private static class Holder |
|||
{ |
|||
static final SecureRandom numberGenerator = getSecureRandom(); |
|||
} |
|||
|
|||
/** 此UUID的最高64有效位 */ |
|||
private final long mostSigBits; |
|||
|
|||
/** 此UUID的最低64有效位 */ |
|||
private final long leastSigBits; |
|||
|
|||
/** |
|||
* 私有构造 |
|||
* |
|||
* @param data 数据 |
|||
*/ |
|||
private UUID(byte[] data) |
|||
{ |
|||
long msb = 0; |
|||
long lsb = 0; |
|||
assert data.length == 16 : "data must be 16 bytes in length"; |
|||
for (int i = 0; i < 8; i++) |
|||
{ |
|||
msb = (msb << 8) | (data[i] & 0xff); |
|||
} |
|||
for (int i = 8; i < 16; i++) |
|||
{ |
|||
lsb = (lsb << 8) | (data[i] & 0xff); |
|||
} |
|||
this.mostSigBits = msb; |
|||
this.leastSigBits = lsb; |
|||
} |
|||
|
|||
/** |
|||
* 使用指定的数据构造新的 UUID。 |
|||
* |
|||
* @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 |
|||
* @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 |
|||
*/ |
|||
public UUID(long mostSigBits, long leastSigBits) |
|||
{ |
|||
this.mostSigBits = mostSigBits; |
|||
this.leastSigBits = leastSigBits; |
|||
} |
|||
|
|||
/** |
|||
* 获取类型 4(伪随机生成的)UUID 的静态工厂。 |
|||
* |
|||
* @return 随机生成的 {@code UUID} |
|||
*/ |
|||
public static UUID fastUUID() |
|||
{ |
|||
return randomUUID(false); |
|||
} |
|||
|
|||
/** |
|||
* 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 |
|||
* |
|||
* @return 随机生成的 {@code UUID} |
|||
*/ |
|||
public static UUID randomUUID() |
|||
{ |
|||
return randomUUID(true); |
|||
} |
|||
|
|||
/** |
|||
* 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 |
|||
* |
|||
* @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 |
|||
* @return 随机生成的 {@code UUID} |
|||
*/ |
|||
public static UUID randomUUID(boolean isSecure) |
|||
{ |
|||
final Random ng = isSecure ? Holder.numberGenerator : getRandom(); |
|||
|
|||
byte[] randomBytes = new byte[16]; |
|||
ng.nextBytes(randomBytes); |
|||
randomBytes[6] &= 0x0f; /* clear version */ |
|||
randomBytes[6] |= 0x40; /* set to version 4 */ |
|||
randomBytes[8] &= 0x3f; /* clear variant */ |
|||
randomBytes[8] |= 0x80; /* set to IETF variant */ |
|||
return new UUID(randomBytes); |
|||
} |
|||
|
|||
/** |
|||
* 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 |
|||
* |
|||
* @param name 用于构造 UUID 的字节数组。 |
|||
* |
|||
* @return 根据指定数组生成的 {@code UUID} |
|||
*/ |
|||
public static UUID nameUUIDFromBytes(byte[] name) |
|||
{ |
|||
MessageDigest md; |
|||
try |
|||
{ |
|||
md = MessageDigest.getInstance("MD5"); |
|||
} |
|||
catch (NoSuchAlgorithmException nsae) |
|||
{ |
|||
throw new InternalError("MD5 not supported"); |
|||
} |
|||
byte[] md5Bytes = md.digest(name); |
|||
md5Bytes[6] &= 0x0f; /* clear version */ |
|||
md5Bytes[6] |= 0x30; /* set to version 3 */ |
|||
md5Bytes[8] &= 0x3f; /* clear variant */ |
|||
md5Bytes[8] |= 0x80; /* set to IETF variant */ |
|||
return new UUID(md5Bytes); |
|||
} |
|||
|
|||
/** |
|||
* 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 |
|||
* |
|||
* @param name 指定 {@code UUID} 字符串 |
|||
* @return 具有指定值的 {@code UUID} |
|||
* @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 |
|||
* |
|||
*/ |
|||
public static UUID fromString(String name) |
|||
{ |
|||
String[] components = name.split("-"); |
|||
if (components.length != 5) |
|||
{ |
|||
throw new IllegalArgumentException("Invalid UUID string: " + name); |
|||
} |
|||
for (int i = 0; i < 5; i++) |
|||
{ |
|||
components[i] = "0x" + components[i]; |
|||
} |
|||
|
|||
long mostSigBits = Long.decode(components[0]).longValue(); |
|||
mostSigBits <<= 16; |
|||
mostSigBits |= Long.decode(components[1]).longValue(); |
|||
mostSigBits <<= 16; |
|||
mostSigBits |= Long.decode(components[2]).longValue(); |
|||
|
|||
long leastSigBits = Long.decode(components[3]).longValue(); |
|||
leastSigBits <<= 48; |
|||
leastSigBits |= Long.decode(components[4]).longValue(); |
|||
|
|||
return new UUID(mostSigBits, leastSigBits); |
|||
} |
|||
|
|||
/** |
|||
* 返回此 UUID 的 128 位值中的最低有效 64 位。 |
|||
* |
|||
* @return 此 UUID 的 128 位值中的最低有效 64 位。 |
|||
*/ |
|||
public long getLeastSignificantBits() |
|||
{ |
|||
return leastSigBits; |
|||
} |
|||
|
|||
/** |
|||
* 返回此 UUID 的 128 位值中的最高有效 64 位。 |
|||
* |
|||
* @return 此 UUID 的 128 位值中最高有效 64 位。 |
|||
*/ |
|||
public long getMostSignificantBits() |
|||
{ |
|||
return mostSigBits; |
|||
} |
|||
|
|||
/** |
|||
* 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 |
|||
* <p> |
|||
* 版本号具有以下含意: |
|||
* <ul> |
|||
* <li>1 基于时间的 UUID |
|||
* <li>2 DCE 安全 UUID |
|||
* <li>3 基于名称的 UUID |
|||
* <li>4 随机生成的 UUID |
|||
* </ul> |
|||
* |
|||
* @return 此 {@code UUID} 的版本号 |
|||
*/ |
|||
public int version() |
|||
{ |
|||
// Version is bits masked by 0x000000000000F000 in MS long
|
|||
return (int) ((mostSigBits >> 12) & 0x0f); |
|||
} |
|||
|
|||
/** |
|||
* 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 |
|||
* <p> |
|||
* 变体号具有以下含意: |
|||
* <ul> |
|||
* <li>0 为 NCS 向后兼容保留 |
|||
* <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF RFC 4122</a>(Leach-Salz), 用于此类 |
|||
* <li>6 保留,微软向后兼容 |
|||
* <li>7 保留供以后定义使用 |
|||
* </ul> |
|||
* |
|||
* @return 此 {@code UUID} 相关联的变体号 |
|||
*/ |
|||
public int variant() |
|||
{ |
|||
// This field is composed of a varying number of bits.
|
|||
// 0 - - Reserved for NCS backward compatibility
|
|||
// 1 0 - The IETF aka Leach-Salz variant (used by this class)
|
|||
// 1 1 0 Reserved, Microsoft backward compatibility
|
|||
// 1 1 1 Reserved for future definition.
|
|||
return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); |
|||
} |
|||
|
|||
/** |
|||
* 与此 UUID 相关联的时间戳值。 |
|||
* |
|||
* <p> |
|||
* 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br> |
|||
* 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 |
|||
* |
|||
* <p> |
|||
* 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br> |
|||
* 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 |
|||
* |
|||
* @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 |
|||
*/ |
|||
public long timestamp() throws UnsupportedOperationException |
|||
{ |
|||
checkTimeBase(); |
|||
return (mostSigBits & 0x0FFFL) << 48//
|
|||
| ((mostSigBits >> 16) & 0x0FFFFL) << 32//
|
|||
| mostSigBits >>> 32; |
|||
} |
|||
|
|||
/** |
|||
* 与此 UUID 相关联的时钟序列值。 |
|||
* |
|||
* <p> |
|||
* 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 |
|||
* <p> |
|||
* {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 |
|||
* UnsupportedOperationException。 |
|||
* |
|||
* @return 此 {@code UUID} 的时钟序列 |
|||
* |
|||
* @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 |
|||
*/ |
|||
public int clockSequence() throws UnsupportedOperationException |
|||
{ |
|||
checkTimeBase(); |
|||
return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); |
|||
} |
|||
|
|||
/** |
|||
* 与此 UUID 相关的节点值。 |
|||
* |
|||
* <p> |
|||
* 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 |
|||
* <p> |
|||
* 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br> |
|||
* 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 |
|||
* |
|||
* @return 此 {@code UUID} 的节点值 |
|||
* |
|||
* @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 |
|||
*/ |
|||
public long node() throws UnsupportedOperationException |
|||
{ |
|||
checkTimeBase(); |
|||
return leastSigBits & 0x0000FFFFFFFFFFFFL; |
|||
} |
|||
|
|||
/** |
|||
* 返回此{@code UUID} 的字符串表现形式。 |
|||
* |
|||
* <p> |
|||
* UUID 的字符串表示形式由此 BNF 描述: |
|||
* |
|||
* <pre> |
|||
* {@code |
|||
* UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node> |
|||
* time_low = 4*<hexOctet> |
|||
* time_mid = 2*<hexOctet> |
|||
* time_high_and_version = 2*<hexOctet> |
|||
* variant_and_sequence = 2*<hexOctet> |
|||
* node = 6*<hexOctet> |
|||
* hexOctet = <hexDigit><hexDigit> |
|||
* hexDigit = [0-9a-fA-F] |
|||
* } |
|||
* </pre> |
|||
* |
|||
* </blockquote> |
|||
* |
|||
* @return 此{@code UUID} 的字符串表现形式 |
|||
* @see #toString(boolean) |
|||
*/ |
|||
@Override |
|||
public String toString() |
|||
{ |
|||
return toString(false); |
|||
} |
|||
|
|||
/** |
|||
* 返回此{@code UUID} 的字符串表现形式。 |
|||
* |
|||
* <p> |
|||
* UUID 的字符串表示形式由此 BNF 描述: |
|||
* |
|||
* <pre> |
|||
* {@code |
|||
* UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node> |
|||
* time_low = 4*<hexOctet> |
|||
* time_mid = 2*<hexOctet> |
|||
* time_high_and_version = 2*<hexOctet> |
|||
* variant_and_sequence = 2*<hexOctet> |
|||
* node = 6*<hexOctet> |
|||
* hexOctet = <hexDigit><hexDigit> |
|||
* hexDigit = [0-9a-fA-F] |
|||
* } |
|||
* </pre> |
|||
* |
|||
* </blockquote> |
|||
* |
|||
* @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 |
|||
* @return 此{@code UUID} 的字符串表现形式 |
|||
*/ |
|||
public String toString(boolean isSimple) |
|||
{ |
|||
final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); |
|||
// time_low
|
|||
builder.append(digits(mostSigBits >> 32, 8)); |
|||
if (false == isSimple) |
|||
{ |
|||
builder.append('-'); |
|||
} |
|||
// time_mid
|
|||
builder.append(digits(mostSigBits >> 16, 4)); |
|||
if (false == isSimple) |
|||
{ |
|||
builder.append('-'); |
|||
} |
|||
// time_high_and_version
|
|||
builder.append(digits(mostSigBits, 4)); |
|||
if (false == isSimple) |
|||
{ |
|||
builder.append('-'); |
|||
} |
|||
// variant_and_sequence
|
|||
builder.append(digits(leastSigBits >> 48, 4)); |
|||
if (false == isSimple) |
|||
{ |
|||
builder.append('-'); |
|||
} |
|||
// node
|
|||
builder.append(digits(leastSigBits, 12)); |
|||
|
|||
return builder.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 返回此 UUID 的哈希码。 |
|||
* |
|||
* @return UUID 的哈希码值。 |
|||
*/ |
|||
@Override |
|||
public int hashCode() |
|||
{ |
|||
long hilo = mostSigBits ^ leastSigBits; |
|||
return ((int) (hilo >> 32)) ^ (int) hilo; |
|||
} |
|||
|
|||
/** |
|||
* 将此对象与指定对象比较。 |
|||
* <p> |
|||
* 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 |
|||
* |
|||
* @param obj 要与之比较的对象 |
|||
* |
|||
* @return 如果对象相同,则返回 {@code true};否则返回 {@code false} |
|||
*/ |
|||
@Override |
|||
public boolean equals(Object obj) |
|||
{ |
|||
if ((null == obj) || (obj.getClass() != UUID.class)) |
|||
{ |
|||
return false; |
|||
} |
|||
UUID id = (UUID) obj; |
|||
return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); |
|||
} |
|||
|
|||
// Comparison Operations
|
|||
|
|||
/** |
|||
* 将此 UUID 与指定的 UUID 比较。 |
|||
* |
|||
* <p> |
|||
* 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 |
|||
* |
|||
* @param val 与此 UUID 比较的 UUID |
|||
* |
|||
* @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 |
|||
* |
|||
*/ |
|||
@Override |
|||
public int compareTo(UUID val) |
|||
{ |
|||
// The ordering is intentionally set up so that the UUIDs
|
|||
// can simply be numerically compared as two numbers
|
|||
return (this.mostSigBits < val.mostSigBits ? -1 : //
|
|||
(this.mostSigBits > val.mostSigBits ? 1 : //
|
|||
(this.leastSigBits < val.leastSigBits ? -1 : //
|
|||
(this.leastSigBits > val.leastSigBits ? 1 : //
|
|||
0)))); |
|||
} |
|||
|
|||
// -------------------------------------------------------------------------------------------------------------------
|
|||
// Private method start
|
|||
/** |
|||
* 返回指定数字对应的hex值 |
|||
* |
|||
* @param val 值 |
|||
* @param digits 位 |
|||
* @return 值 |
|||
*/ |
|||
private static String digits(long val, int digits) |
|||
{ |
|||
long hi = 1L << (digits * 4); |
|||
return Long.toHexString(hi | (val & (hi - 1))).substring(1); |
|||
} |
|||
|
|||
/** |
|||
* 检查是否为time-based版本UUID |
|||
*/ |
|||
private void checkTimeBase() |
|||
{ |
|||
if (version() != 1) |
|||
{ |
|||
throw new UnsupportedOperationException("Not a time-based UUID"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) |
|||
* |
|||
* @return {@link SecureRandom} |
|||
*/ |
|||
public static SecureRandom getSecureRandom() |
|||
{ |
|||
try |
|||
{ |
|||
return SecureRandom.getInstance("SHA1PRNG"); |
|||
} |
|||
catch (NoSuchAlgorithmException e) |
|||
{ |
|||
|
|||
throw new UtilException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取随机数生成器对象<br> |
|||
* ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 |
|||
* |
|||
* @return {@link ThreadLocalRandom} |
|||
*/ |
|||
public static ThreadLocalRandom getRandom() |
|||
{ |
|||
return ThreadLocalRandom.current(); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
package com.mathvision.box.common.xss; |
|||
|
|||
import javax.validation.Constraint; |
|||
import javax.validation.Payload; |
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* 自定义xss校验注解 |
|||
*/ |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) |
|||
@Constraint(validatedBy = { XssValidator.class }) |
|||
public @interface Xss |
|||
{ |
|||
String message() |
|||
|
|||
default "不允许任何脚本运行"; |
|||
|
|||
Class<?>[] groups() default {}; |
|||
|
|||
Class<? extends Payload>[] payload() default {}; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
package com.mathvision.box.common.xss; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
import javax.servlet.*; |
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 防止XSS攻击的过滤器 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class XssFilter implements Filter |
|||
{ |
|||
/** |
|||
* 排除链接 |
|||
*/ |
|||
public List<String> excludes = new ArrayList<>(); |
|||
|
|||
@Override |
|||
public void init(FilterConfig filterConfig) throws ServletException |
|||
{ |
|||
String tempExcludes = filterConfig.getInitParameter("excludes"); |
|||
if (StringUtils.isNotEmpty(tempExcludes)) |
|||
{ |
|||
String[] urls = tempExcludes.split(","); |
|||
for (String url : urls) |
|||
{ |
|||
excludes.add(url); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
|||
throws IOException, ServletException |
|||
{ |
|||
HttpServletRequest req = (HttpServletRequest) request; |
|||
HttpServletResponse resp = (HttpServletResponse) response; |
|||
if (handleExcludeURL(req, resp)) |
|||
{ |
|||
chain.doFilter(request, response); |
|||
return; |
|||
} |
|||
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); |
|||
chain.doFilter(xssRequest, response); |
|||
} |
|||
|
|||
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) |
|||
{ |
|||
String url = request.getServletPath(); |
|||
String method = request.getMethod(); |
|||
// GET DELETE 不过滤
|
|||
if (method == null || method.matches("GET") || method.matches("DELETE")) |
|||
{ |
|||
return true; |
|||
} |
|||
return StringUtils.matches(url, excludes); |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
package com.mathvision.box.common.xss; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.html.EscapeUtil; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletRequestWrapper; |
|||
|
|||
/** |
|||
* XSS过滤处理 |
|||
* |
|||
* @author ruoyi |
|||
*/ |
|||
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper |
|||
{ |
|||
/** |
|||
* @param request |
|||
*/ |
|||
public XssHttpServletRequestWrapper(HttpServletRequest request) |
|||
{ |
|||
super(request); |
|||
} |
|||
|
|||
@Override |
|||
public String[] getParameterValues(String name) |
|||
{ |
|||
String[] values = super.getParameterValues(name); |
|||
if (values != null) |
|||
{ |
|||
int length = values.length; |
|||
String[] escapseValues = new String[length]; |
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
// 防xss攻击和过滤前后空格
|
|||
escapseValues[i] = EscapeUtil.clean(values[i]).trim(); |
|||
} |
|||
return escapseValues; |
|||
} |
|||
return super.getParameterValues(name); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
package com.mathvision.box.common.xss; |
|||
|
|||
|
|||
import com.mathvision.box.common.utils.common.StringUtils; |
|||
|
|||
import javax.validation.ConstraintValidator; |
|||
import javax.validation.ConstraintValidatorContext; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
/** |
|||
* 自定义xss校验注解实现 |
|||
*/ |
|||
public class XssValidator implements ConstraintValidator<Xss, String> |
|||
{ |
|||
private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; |
|||
|
|||
@Override |
|||
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) |
|||
{ |
|||
if (StringUtils.isBlank(value)) |
|||
{ |
|||
return true; |
|||
} |
|||
return !containsHtml(value); |
|||
} |
|||
|
|||
public static boolean containsHtml(String value) |
|||
{ |
|||
StringBuilder sHtml = new StringBuilder(); |
|||
Pattern pattern = Pattern.compile(HTML_PATTERN); |
|||
Matcher matcher = pattern.matcher(value); |
|||
while (matcher.find()) |
|||
{ |
|||
sHtml.append(matcher.group()); |
|||
} |
|||
return pattern.matcher(sHtml).matches(); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<parent> |
|||
<artifactId>box</artifactId> |
|||
<groupId>com.mathvision</groupId> |
|||
<version>1.0-SNAPSHOT</version> |
|||
</parent> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<artifactId>box-dao</artifactId> |
|||
<description>数据库操作</description> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>com.mathvision</groupId> |
|||
<artifactId>box-common</artifactId> |
|||
<version>1.0-SNAPSHOT</version> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>org.xerial</groupId> |
|||
<artifactId>sqlite-jdbc</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.alibaba</groupId> |
|||
<artifactId>druid-spring-boot-starter</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.baomidou</groupId> |
|||
<artifactId>mybatis-plus-boot-starter</artifactId> |
|||
</dependency> |
|||
</dependencies> |
|||
</project> |
|||
@ -0,0 +1,45 @@ |
|||
package com.mathvision.box.dao.domain.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import javax.validation.constraints.Max; |
|||
import javax.validation.constraints.Min; |
|||
import javax.validation.constraints.NotNull; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
@ApiModel("数据存储管理修改参数") |
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class DataStorageConfigDto implements Serializable { |
|||
private static final long serialVersionUID = 534034771306361219L; |
|||
|
|||
@ApiModelProperty(value = "告警阈值", example = "告警阈值(整数0-100)") |
|||
@NotNull(message = "告警阈值不能为空") |
|||
@Min(value = 0, message = "阈值不能小于0") |
|||
@Max(value = 100, message = "阈值不能超过100") |
|||
private Integer threshold; |
|||
|
|||
@ApiModelProperty(value = "扩展字段1", example = "扩展字段1") |
|||
private String ext1; |
|||
|
|||
@ApiModelProperty(value = "扩展字段2", example = "扩展字段2") |
|||
private String ext2; |
|||
|
|||
@ApiModelProperty(value = "扩展字段3", example = "扩展字段3") |
|||
private String ext3; |
|||
|
|||
@ApiModelProperty(value = "扩展字段4", example = "扩展字段4") |
|||
private String ext4; |
|||
|
|||
@ApiModelProperty(value = "扩展字段5", example = "扩展字段5") |
|||
private String ext5; |
|||
|
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
package com.mathvision.box.dao.domain.dto; |
|||
|
|||
import java.util.Date; |
|||
import java.io.Serializable; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.NoArgsConstructor; |
|||
import lombok.Builder; |
|||
|
|||
@ApiModel("检测结果查询") |
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class DefectQueryDto implements Serializable { |
|||
private static final long serialVersionUID = 459409543849580076L; |
|||
@ApiModelProperty(notes = "开始时间") |
|||
private Date startTime; |
|||
|
|||
@ApiModelProperty(notes = "结束时间") |
|||
private Date endTime; |
|||
|
|||
@ApiModelProperty(notes = "过程id") |
|||
private String processId; |
|||
|
|||
@ApiModelProperty(notes = "告警类型") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(notes = "设备ip") |
|||
private String ip; |
|||
|
|||
@ApiModelProperty(notes = "设备名称") |
|||
private String deviceName; |
|||
|
|||
@ApiModelProperty(notes = "通道名称") |
|||
private String channelName; |
|||
|
|||
@ApiModelProperty("状态(0-未处理/1-已处理)") |
|||
private String status; |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package com.mathvision.box.dao.domain.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
@ApiModel("检测结果修改") |
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class DefectUpdateDto implements Serializable { |
|||
private static final long serialVersionUID = 459409543849580076L; |
|||
private Long id; |
|||
|
|||
@ApiModelProperty(notes = "处置信息") |
|||
private String ext; |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
package com.mathvision.box.dao.domain.dto; |
|||
|
|||
import java.util.Date; |
|||
import java.io.Serializable; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.NoArgsConstructor; |
|||
import lombok.Builder; |
|||
|
|||
@ApiModel("检测过程表(DetectDto)传输数据类") |
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class DetectDto implements Serializable { |
|||
private static final long serialVersionUID = 346776873048750268L; |
|||
|
|||
private Long id; |
|||
|
|||
@ApiModelProperty(notes = "告警时间") |
|||
private Date time; |
|||
|
|||
@ApiModelProperty(notes = "告警类型") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(notes = "设备IP") |
|||
private String ip; |
|||
|
|||
@ApiModelProperty(notes = "通道") |
|||
private Integer channel; |
|||
|
|||
@ApiModelProperty(notes = "图片") |
|||
private String img; |
|||
|
|||
@ApiModelProperty(notes = "热成像图片") |
|||
private String thermalImg; |
|||
|
|||
@ApiModelProperty(notes = "扩展数据") |
|||
private String data; |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue