初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
package tech.easyflow.common;
public class Consts {
public static final String VERSION = "v2.0.9";
public static final String REQ_ATTR_ACCOUNT_ID = "loginAccountId";
public static final String JWT_ATTR_SESSION_ID = "sessionId";
public static Boolean ENABLE_DATA_SCOPE = false;
}

View File

@@ -0,0 +1,13 @@
package tech.easyflow.common.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DictDef {
String name();
String code();
String keyField();
String labelField();
}

View File

@@ -0,0 +1,10 @@
package tech.easyflow.common.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedApiKeyAccess {
String[] value() default {};
}

View File

@@ -0,0 +1,11 @@
package tech.easyflow.common.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface UsePermission {
// 使用哪个模块的权限
String moduleName();
}

View File

@@ -0,0 +1,14 @@
package tech.easyflow.common.constant;
public interface CacheKey {
String DOC_NODE_CONTENT_KEY = "docNode:content:";
String CHAIN_SUSPEND_KEY = "chain:suspend:";
String CHAIN_STATUS_CACHE_KEY = "chain:status:";
String CHAIN_CACHE_KEY = "chainState:";
String NODE_CACHE_KEY = "nodeState:";
String OAUTH_STATE_KEY = "oauth:state:";
}

View File

@@ -0,0 +1,33 @@
package tech.easyflow.common.constant;
import java.math.BigInteger;
public interface Constants {
// 超级管理员ID
BigInteger SUPER_ADMIN_ID = BigInteger.valueOf(1L);
// 超级管理员角色ID
BigInteger SUPER_ADMIN_ROLE_ID = BigInteger.valueOf(1L);
// 默认租户ID
BigInteger DEFAULT_TENANT_ID = BigInteger.valueOf(1000000L);
// 默认部门ID
BigInteger DEFAULT_DEPT_ID = BigInteger.valueOf(1L);
// 登录账户KEY
String LOGIN_USER_KEY = "loginUser";
// 超级管理员角色标识
String SUPER_ADMIN_ROLE_CODE = "super_admin";
// 租户管理员角色名称
String TENANT_ADMIN_ROLE_NAME = "租户管理员";
// 租户管理员角色标识
String TENANT_ADMIN_ROLE_CODE = "tenant_admin";
// 创建者字段
String CREATED_BY = "created_by";
// 部门ID字段
String DEPT_ID = "dept_id";
// 租户ID字段
String TENANT_ID = "tenant_id";
// 根部门标识
String ROOT_DEPT = "root_dept";
// 第三方登录账号角色标识
String OAUTH_ROLE_KEY = "oauth_role";
}

View File

@@ -0,0 +1,50 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
/**
* 用户类型
*/
@DictDef(name = "用户类型", code = "accountType", keyField = "code", labelField = "text")
public enum EnumAccountType {
NORMAL(0, "普通账号"),
TENANT_ADMIN(1, "租户管理员"),
SUPER_ADMIN(99, "超级管理员"),
;
private Integer code;
private String text;
EnumAccountType(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumAccountType getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumAccountType type : EnumAccountType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,52 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
/**
* 数据权限类型
*/
@DictDef(name = "数据权限类型", code = "dataScope", keyField = "code", labelField = "text")
public enum EnumDataScope {
ALL(1, "全部权限"),
SELF(2, "仅查看本人"),
DEPT(3, "当前所在部门"),
DEPT_AND_SUB(4, "当前所在部门及子部门"),
CUSTOM(5, "自定义权限"),
;
private Integer code;
private String text;
EnumDataScope(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumDataScope getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumDataScope type : EnumDataScope.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,49 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
/**
* 通用数据状态
*/
@DictDef(name = "通用数据状态", code = "dataStatus", keyField = "code", labelField = "text")
public enum EnumDataStatus {
UNAVAILABLE(0, "未启用"),
AVAILABLE(1, "已启用"),
;
private Integer code;
private String text;
EnumDataStatus(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumDataStatus getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumDataStatus type : EnumDataStatus.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,27 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "反馈类型", code = "feedbackType", keyField = "code", labelField = "text")
public enum EnumFeedbackType {
UNREAD(0,"未查看"),
VIEWED(1,"已查看"),
PROCESSED(2, "已处理");
private final int code;
private final String text;
EnumFeedbackType(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,53 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
/**
* 数据中枢 - 字段类型枚举
*/
@DictDef(name = "字段类型", code = "fieldType", keyField = "code", labelField = "text")
public enum EnumFieldType {
STRING(1, "String"),
INTEGER(2, "Integer"),
TIME(3, "Time"),
NUMBER(4, "Number"),
BOOLEAN(5, "Boolean"),
;
private Integer code;
private String text;
EnumFieldType(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumFieldType getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumFieldType type : EnumFieldType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,27 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "定时任务状态", code = "jobStatus", keyField = "code", labelField = "text")
public enum EnumJobExecStatus {
SUCCESS(1,"成功"),
FAIL(0,"失败"),
;
private final int code;
private final String text;
EnumJobExecStatus(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,28 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "任务执行结果", code = "jobResult", keyField = "code", labelField = "text")
public enum EnumJobResult {
SUCCESS(1,"成功"),
FAIL(0,"失败"),
;
private final int code;
private final String text;
EnumJobResult(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,27 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "定时任务状态", code = "jobStatus", keyField = "code", labelField = "text")
public enum EnumJobStatus {
STOP(0,"停止"),
RUNNING(1,"运行中"),
;
private final int code;
private final String text;
EnumJobStatus(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,28 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "定时任务类型", code = "jobType", keyField = "code", labelField = "text")
public enum EnumJobType {
TINY_FLOW(1,"工作流"),
SPRING_BEAN(2,"SpringBean"),
JAVA_CLASS(3,"Java类");
;
private final int code;
private final String text;
EnumJobType(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,29 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
/**
* 菜单类型
*/
@DictDef(name = "菜单类型", code = "menuType", keyField = "code", labelField = "text")
public enum EnumMenuType {
MENU(0,"菜单"),
BTN(1,"按钮");
private final int code;
private final String text;
EnumMenuType(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,29 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "错过策略", code = "misfirePolicy", keyField = "code", labelField = "text")
public enum EnumMisfirePolicy {
DEFAULT(0,"默认"),
MISFIRE_IGNORE_MISFIRES(1,"立即触发"),
MISFIRE_FIRE_AND_PROCEED(2,"立即触发一次"),
MISFIRE_DO_NOTHING(3,"忽略");
;
private final int code;
private final String text;
EnumMisfirePolicy(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,31 @@
package tech.easyflow.common.constant.enums;
/**
* errorCode 枚举
*/
public enum EnumRes {
SUCCESS(0, "成功"),
FAIL(1, "失败"),
NO_AUTHENTICATION(401, "请重新登陆"),
NO_AUTHORIZATION(4010, "无权操作"),
DUPLICATE_KEY(900, "记录已存在"),
PARAM_ERROR(400, "参数错误"),
;
private final int code;
private final String msg;
EnumRes(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}

View File

@@ -0,0 +1,47 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "素材来源", code = "resourceOriginType", keyField = "code", labelField = "text")
public enum EnumResourceOriginType {
SYSTEM(0, "系统上传"),
GENERATE(1, "工作流生成"),
;
private Integer code;
private String text;
EnumResourceOriginType(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumResourceOriginType getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumResourceOriginType type : EnumResourceOriginType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,49 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "素材类型", code = "resourceType", keyField = "code", labelField = "text")
public enum EnumResourceType {
IMG(0, "图片"),
VIDEO(1, "视频"),
AUDIO(2, "音频"),
DOC(3, "文档"),
OTHER(99, "其他"),
;
private Integer code;
private String text;
EnumResourceType(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumResourceType getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumResourceType type : EnumResourceType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,26 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "显示或者不显示", code = "showOrNot", keyField = "code", labelField = "text")
public enum EnumShowOrNot {
YES(1,"显示"),
NO(0,"不显示");
private final int code;
private final String text;
EnumShowOrNot(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,49 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
/**
* 租户启用状态
*/
@DictDef(name = "租户启用状态", code = "tenantStatus", keyField = "code", labelField = "text")
public enum EnumTenantStatus {
UNAVAILABLE(0, "未启用"),
AVAILABLE(1, "已启用"),
;
private Integer code;
private String text;
EnumTenantStatus(Integer code, String text) {
this.code = code;
this.text = text;
}
public static EnumTenantStatus getByCode(Integer code) {
if (null == code) {
return null;
}
for (EnumTenantStatus type : EnumTenantStatus.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new RuntimeException("内容类型非法");
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}}

View File

@@ -0,0 +1,26 @@
package tech.easyflow.common.constant.enums;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "是否", code = "yesOrNo", keyField = "code", labelField = "text")
public enum EnumYesOrNo {
YES(1,""),
NO(0,"");
private final int code;
private final String text;
EnumYesOrNo(int code, String text) {
this.code = code;
this.text = text;
}
public int getCode() {
return code;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,53 @@
package tech.easyflow.common.dict;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Dict implements Serializable {
private String name;
private String code;
private String description;
private List<DictItem> items;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<DictItem> getItems() {
return items;
}
public void setItems(List<DictItem> items) {
this.items = items;
}
public void addItem(DictItem item){
if (this.items == null){
this.items = new ArrayList<>();
}
items.add(item);
}
}

View File

@@ -0,0 +1,42 @@
package tech.easyflow.common.dict;
import tech.easyflow.common.util.SpringContextUtil;
import tech.easyflow.common.annotation.DictDef;
import tech.easyflow.common.dict.loader.EnumDictLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.event.EventListener;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;
@Component
public class DictDefAutoConfig {
private static final Logger LOG = LoggerFactory.getLogger(DictDefAutoConfig.class);
@EventListener(ApplicationReadyEvent.class)
public <E extends Enum<E>> void onApplicationStartup() {
DictManager dictManager = SpringContextUtil.getBean(DictManager.class);
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(DictDef.class));
for (BeanDefinition bd : scanner.findCandidateComponents("tech.easyflow")) {
try {
@SuppressWarnings("unchecked")
Class<E> enumClass = (Class<E>) Class.forName(bd.getBeanClassName());
DictDef dictDef = enumClass.getAnnotation(DictDef.class);
dictManager.putLoader(new EnumDictLoader<>(dictDef.code(), enumClass, dictDef.keyField(), dictDef.labelField()));
} catch (ClassNotFoundException e) {
LOG.warn("Could not resolve class object for bean definition", e);
}
}
}
}

View File

@@ -0,0 +1,108 @@
package tech.easyflow.common.dict;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 在 DictItem 的属性中key value 是同一个值,而 label 和 title 是同一个值
* 进行这么设计的原因,是为了适配不同的前段组件,不需要对数据进行字段转换
*/
public class DictItem implements Serializable {
/**
* 值
*/
private Object value;
/**
* key
*/
private Object key;
/**
* 标签
*/
private String label;
/**
* 标题
*/
private String title;
/**
* 禁用
*/
private Boolean disabled;
private Integer layerNo;
private List<DictItem> children;
public DictItem() {
}
public DictItem(Object value, String label) {
this.setValue(value);
this.setLabel(label);
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
this.key = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
this.title = label;
}
public List<DictItem> getChildren() {
return children;
}
public void setChildren(List<DictItem> children) {
this.children = children;
}
public Object getKey() {
return key;
}
public void setKey(Object key) {
this.key = key;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Boolean getDisabled() {
return disabled;
}
public void setDisabled(Boolean disabled) {
this.disabled = disabled;
}
public Integer getLayerNo() {
return layerNo;
}
public void setLayerNo(Integer layerNo) {
this.layerNo = layerNo;
}
public void addChild(DictItem childDictItem) {
if (children == null) {
children = new ArrayList<>();
}
children.add(childDictItem);
}
}

View File

@@ -0,0 +1,8 @@
package tech.easyflow.common.dict;
import java.util.Map;
public interface DictLoader {
String code();
Dict load(String keyword, Map<String, String[]> parameters);
}

View File

@@ -0,0 +1,48 @@
package tech.easyflow.common.dict;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class DictManager implements BeanPostProcessor {
private Map<String, DictLoader> loaders = new HashMap<>();
public DictManager(ObjectProvider<List<DictLoader>> listObjectProvider) {
List<DictLoader> dictLoaders = listObjectProvider.getIfAvailable();
if (dictLoaders != null) {
dictLoaders.forEach(dictLoader -> loaders.put(dictLoader.code(), dictLoader));
}
}
public Map<String, DictLoader> getLoaders() {
return loaders;
}
public void setLoaders(Map<String, DictLoader> loaders) {
this.loaders = loaders;
}
public void putLoader(DictLoader loader) {
if (loader == null){
return;
}
loaders.put(loader.code(), loader);
}
public void removeLoader(String code) {
loaders.remove(code);
}
public DictLoader getLoader(String code) {
if (loaders == null || loaders.isEmpty()) {
return null;
}
return loaders.get(code);
}
}

View File

@@ -0,0 +1,29 @@
package tech.easyflow.common.dict;
import tech.easyflow.common.annotation.DictDef;
@DictDef(name = "字典类型", code = "dictType", keyField = "value", labelField = "text")
public enum DictType {
CUSTOM(1, "自定义字典"),
TABLE(2, "数据表字典"),
ENUM(3, "枚举类字典"),
SYSTEM(4, "系统字典"),
;
private final int value;
private final String text;
DictType(int value, String text) {
this.value = value;
this.text = text;
}
public int getValue() {
return value;
}
public String getText() {
return text;
}
}

View File

@@ -0,0 +1,208 @@
package tech.easyflow.common.dict.loader;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.util.RequestUtil;
import tech.easyflow.common.dict.Dict;
import tech.easyflow.common.dict.DictItem;
import tech.easyflow.common.dict.DictLoader;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class DatabaseDictLoader implements DictLoader {
private static final Object[] EMPTY_PARAMS = new Object[0];
private String code;
private String tableName;
private String keyColumn;
private String labelColumn;
private String parentColumn;
private String orderBy;
public DatabaseDictLoader(String code, String tableName, String keyColumn, String labelColumn) {
this.code = code;
this.tableName = tableName;
this.keyColumn = keyColumn;
this.labelColumn = labelColumn;
}
public DatabaseDictLoader(String code, String tableName, String keyColumn, String labelColumn, String parentColumn) {
this.code = code;
this.tableName = tableName;
this.keyColumn = keyColumn;
this.labelColumn = labelColumn;
this.parentColumn = parentColumn;
}
public DatabaseDictLoader(String code, String tableName, String keyColumn, String labelColumn, String parentColumn, String orderBy) {
this.code = code;
this.tableName = tableName;
this.keyColumn = keyColumn;
this.labelColumn = labelColumn;
this.parentColumn = parentColumn;
this.orderBy = orderBy;
}
@Override
public String code() {
return code;
}
@Override
public Dict load(String keyword, Map<String, String[]> parameters) {
String sql = "SELECT " + keyColumn + ", " + labelColumn;
if (StringUtils.hasText(parentColumn)) {
sql += ", " + parentColumn;
}
sql += " FROM " + tableName;
if (StringUtils.hasText(keyword)) {
sql += "WHERE " + labelColumn + " = ?";
}
if (StringUtils.hasText(orderBy)) {
sql += " ORDER BY " + orderBy;
}
List<Row> rows = Db.selectListBySql(sql, StringUtils.hasText(keyword) ? new Object[]{keyword.trim()} : EMPTY_PARAMS);
if (rows == null || rows.isEmpty()) {
return null;
}
List<DictItem> items = new ArrayList<>(rows.size());
Boolean asTree = RequestUtil.getParamAsBoolean(parameters,"asTree");
//有树形结构
if (StringUtils.hasText(parentColumn)) {
List<Row> topLayerRows = findTopLayerRows(rows);
//以树形结构输出
if (asTree != null && asTree) {
makeTree(topLayerRows, items, rows);
}
//以平级结构输出
else {
makeLayer(0, topLayerRows, items, rows);
}
}
//无树形结构数据
else {
for (Row row : rows) {
DictItem dictItem = new DictItem();
dictItem.setValue(row.get(keyColumn));
dictItem.setLabel(String.valueOf(row.get(labelColumn)));
items.add(dictItem);
}
}
Dict dict = new Dict();
dict.setCode(code);
dict.setItems(items);
return dict;
}
private void makeTree(List<Row> parentRows, List<DictItem> parentItems, List<Row> allRows) {
for (Row parentRow : parentRows) {
DictItem parentItem = row2DictItem(0, parentRow);
parentItems.add(parentItem);
List<Row> children = new ArrayList<>();
for (Row maybeChild : allRows) {
if (Objects.equals(maybeChild.get(parentColumn), parentRow.get(keyColumn))) {
children.add(maybeChild);
}
}
if (!children.isEmpty()) {
List<DictItem> childrenItems = new ArrayList<>(children.size());
parentItem.setChildren(childrenItems);
makeTree(children, childrenItems, allRows);
}
}
}
private void makeLayer(int layerNo, List<Row> parentRows, List<DictItem> parentItems, List<Row> allRows) {
for (Row parentRow : parentRows) {
parentItems.add(row2DictItem(layerNo, parentRow));
List<Row> children = new ArrayList<>();
for (Row maybeChild : allRows) {
if (Objects.equals(maybeChild.get(parentColumn), parentRow.get(keyColumn))) {
children.add(maybeChild);
}
}
if (!children.isEmpty()) {
makeLayer(layerNo + 1, children, parentItems, allRows);
}
}
}
private DictItem row2DictItem(int layerNo, Row row) {
DictItem dictItem = new DictItem();
dictItem.setValue(row.get(keyColumn));
dictItem.setLabel(Tree.getPrefix(layerNo) + row.get(labelColumn));
dictItem.setLayerNo(layerNo);
return dictItem;
}
private List<Row> findTopLayerRows(List<Row> rows) {
List<Row> topLayerRows = new ArrayList<>();
for (Row row : rows) {
boolean foundParent = false;
for (Row row1 : rows) {
if (Objects.equals(row1.get(keyColumn), row.get(parentColumn))) {
foundParent = true;
break;
}
}
if (!foundParent) {
topLayerRows.add(row);
}
}
return topLayerRows;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getKeyColumn() {
return keyColumn;
}
public void setKeyColumn(String keyColumn) {
this.keyColumn = keyColumn;
}
public String getLabelColumn() {
return labelColumn;
}
public void setLabelColumn(String labelColumn) {
this.labelColumn = labelColumn;
}
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
}

View File

@@ -0,0 +1,214 @@
package tech.easyflow.common.dict.loader;
import cn.hutool.core.util.StrUtil;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.util.RequestUtil;
import tech.easyflow.common.dict.Dict;
import tech.easyflow.common.dict.DictItem;
import tech.easyflow.common.dict.DictLoader;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class DbDataLoader<T> implements DictLoader {
private final String code;
private final BaseMapper<T> mapper;
// 下划线命名
private final String keyColumn;
private final String labelColumn;
private final String parentColumn;
// 驼峰命名
private final String keyColumnCamelCase;
private final String labelColumnCamelCase;
private final String parentColumnCamelCase;
private final String orderBy;
private boolean queryStatus = false;
public DbDataLoader(String code,
BaseMapper<T> mapper,
String keyColumn,
String labelColumn,
String parentColumn,
String orderBy,
boolean queryStatus) {
this.code = code;
this.mapper = mapper;
this.keyColumn = keyColumn;
this.labelColumn = labelColumn;
this.parentColumn = parentColumn;
this.keyColumnCamelCase = StrUtil.toCamelCase(this.keyColumn);
this.labelColumnCamelCase = StrUtil.toCamelCase(labelColumn);
this.parentColumnCamelCase = StrUtil.toCamelCase(parentColumn);
this.orderBy = orderBy;
this.queryStatus = queryStatus;
}
@Override
public String code() {
return code;
}
@Override
public Dict load(String keyword, Map<String, String[]> parameters) {
QueryWrapper where = QueryWrapper.create();
if (StrUtil.isNotEmpty(keyword)) {
where.eq(labelColumn, keyword);
}
if (queryStatus) {
where.eq("status", 1);
}
if (StrUtil.isNotEmpty(orderBy)) {
where.orderBy(orderBy);
}
List<T> records = mapper.selectListByQuery(where);
List<JSONObject> rows = new ArrayList<>();
for (T record : records) {
rows.add(JSON.parseObject(JSON.toJSONString(record)));
}
Boolean asTree = RequestUtil.getParamAsBoolean(parameters, "asTree");
List<DictItem> items = new ArrayList<>(rows.size());
//有树形结构
if (StringUtils.hasText(parentColumn)) {
List<JSONObject> topLayerRows = findTopLayerRows(rows);
//以树形结构输出
if (asTree != null && asTree) {
makeTree(topLayerRows, items, rows);
}
//以平级结构输出
else {
makeLayer(0, topLayerRows, items, rows);
}
}
//无树形结构数据
else {
for (JSONObject row : rows) {
DictItem dictItem = new DictItem();
dictItem.setValue(row.get(keyColumnCamelCase));
dictItem.setLabel(String.valueOf(row.get(labelColumnCamelCase)));
items.add(dictItem);
}
}
Dict dict = new Dict();
dict.setCode(code);
dict.setItems(items);
return dict;
}
private void makeTree(List<JSONObject> parentRows, List<DictItem> parentItems, List<JSONObject> allRows) {
for (JSONObject parentRow : parentRows) {
DictItem parentItem = row2DictItem(0, parentRow);
parentItems.add(parentItem);
List<JSONObject> children = new ArrayList<>();
for (JSONObject maybeChild : allRows) {
if (Objects.equals(maybeChild.get(parentColumnCamelCase), parentRow.get(keyColumnCamelCase))) {
children.add(maybeChild);
}
}
if (!children.isEmpty()) {
List<DictItem> childrenItems = new ArrayList<>(children.size());
parentItem.setChildren(childrenItems);
makeTree(children, childrenItems, allRows);
}
}
}
private void makeLayer(int layerNo, List<JSONObject> parentRows, List<DictItem> parentItems, List<JSONObject> allRows) {
for (JSONObject parentRow : parentRows) {
parentItems.add(row2DictItem(layerNo, parentRow));
List<JSONObject> children = new ArrayList<>();
for (JSONObject maybeChild : allRows) {
if (Objects.equals(maybeChild.get(parentColumnCamelCase), parentRow.get(keyColumnCamelCase))) {
children.add(maybeChild);
}
}
if (!children.isEmpty()) {
makeLayer(layerNo + 1, children, parentItems, allRows);
}
}
}
private DictItem row2DictItem(int layerNo, JSONObject row) {
DictItem dictItem = new DictItem();
dictItem.setValue(row.get(keyColumnCamelCase));
dictItem.setLabel(Tree.getPrefix(layerNo) + row.get(labelColumnCamelCase));
dictItem.setLayerNo(layerNo);
return dictItem;
}
private List<JSONObject> findTopLayerRows(List<JSONObject> rows) {
List<JSONObject> topLayerRows = new ArrayList<>();
for (JSONObject row : rows) {
boolean foundParent = false;
for (JSONObject row1 : rows) {
if (Objects.equals(row1.get(keyColumnCamelCase), row.get(parentColumnCamelCase))) {
foundParent = true;
break;
}
}
if (!foundParent) {
topLayerRows.add(row);
}
}
return topLayerRows;
}
public String getCode() {
return code;
}
public BaseMapper<T> getMapper() {
return mapper;
}
public String getKeyColumn() {
return keyColumn;
}
public String getLabelColumn() {
return labelColumn;
}
public String getParentColumn() {
return parentColumn;
}
public String getKeyColumnCamelCase() {
return keyColumnCamelCase;
}
public String getLabelColumnCamelCase() {
return labelColumnCamelCase;
}
public String getParentColumnCamelCase() {
return parentColumnCamelCase;
}
public String getOrderBy() {
return orderBy;
}
public boolean isQueryStatus() {
return queryStatus;
}
}

View File

@@ -0,0 +1,87 @@
package tech.easyflow.common.dict.loader;
import tech.easyflow.common.dict.Dict;
import tech.easyflow.common.dict.DictItem;
import tech.easyflow.common.dict.DictLoader;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.StringUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class EnumDictLoader<E extends Enum<E>> implements DictLoader {
private final String code;
private final Dict dict;
public EnumDictLoader(String code, Class<E> enumClass, String keyField, String labelField) {
this(null, code, enumClass, keyField, labelField);
}
public EnumDictLoader(String name, String code, Class<E> enumClass, String keyField, String labelField) {
this.code = code;
E[] enums = enumClass.getEnumConstants();
this.dict = new Dict();
this.dict.setName(name);
this.dict.setCode(code);
Field keyProperty = ClassUtil.getFirstField(enumClass, field -> field.getName().equals(keyField));
String keyGetterMethodName = "get" + StringUtil.firstCharToUpperCase(keyField);
Method keyGetter = ClassUtil.getFirstMethod(enumClass, method -> {
String methodName = method.getName();
return methodName.equals(keyGetterMethodName) && Modifier.isPublic(method.getModifiers());
});
Field valueProperty = ClassUtil.getFirstField(enumClass, field -> field.getName().equals(keyField));
String valueGetterMethodName = "get" + StringUtil.firstCharToUpperCase(labelField);
Method valueGetter = ClassUtil.getFirstMethod(enumClass, method -> {
String methodName = method.getName();
return methodName.equals(valueGetterMethodName) && Modifier.isPublic(method.getModifiers());
});
List<DictItem> items = new ArrayList<>(enums.length);
for (E anEnum : enums) {
Object key = getByMethodOrField(anEnum, keyGetter, keyProperty);
Object value = getByMethodOrField(anEnum, valueGetter, valueProperty);
DictItem dictItem = new DictItem();
dictItem.setValue(key);
dictItem.setLabel(String.valueOf(value));
items.add(dictItem);
}
this.dict.setItems(items);
}
private Object getByMethodOrField(E anEnum, Method keyGetter, Field keyProperty) {
if (keyGetter != null) {
try {
return keyGetter.invoke(anEnum);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
try {
return keyProperty.get(anEnum);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
@Override
public String code() {
return code;
}
@Override
public Dict load(String keyword, Map<String, String[]> parameters) {
return dict;
}
}

View File

@@ -0,0 +1,138 @@
package tech.easyflow.common.domain;
import tech.easyflow.common.constant.enums.EnumRes;
import java.io.Serializable;
/**
* @author michael
*/
public class Result<T> implements Serializable {
private static final long serialVersionUID = -8744614420977483630L;
/**
* 返回状态码
*
* @mock 0
* @see EnumRes
*/
private Integer errorCode;
/**
* 提示消息
*
* @mock 成功
*/
private String message;
/**
* 返回的数据
*/
private T data;
public Result<T> message(String message) {
this.message = message;
return this;
}
public Result<T> data(T data) {
this.data = data;
return this;
}
public Result<T> success() {
this.errorCode = EnumRes.SUCCESS.getCode();
return this;
}
public Result<T> fail() {
this.errorCode = EnumRes.FAIL.getCode();
return this;
}
public Result<T> fail(int errorCode) {
this.errorCode = errorCode;
return this;
}
public static Result<Void> ok() {
Result<Void> Result = new Result<>();
Result.setErrorCode(EnumRes.SUCCESS.getCode());
Result.setMessage(EnumRes.SUCCESS.getMsg());
return Result;
}
// public static Result<Void> ok(String msg) {
// Result<Void> Result = new Result<>();
// Result.setErrorCode(EnumRes.SUCCESS.getCode());
// Result.setMessage(msg);
// return Result;
// }
public static <T> Result<T> ok(T data) {
Result<T> Result = new Result<>();
Result.setErrorCode(EnumRes.SUCCESS.getCode());
Result.setMessage(EnumRes.SUCCESS.getMsg());
Result.setData(data);
return Result;
}
public static <T> Result<T> ok(String msg, T data) {
Result<T> Result = new Result<>();
Result.setErrorCode(EnumRes.SUCCESS.getCode());
if (msg == null || msg.isEmpty()) {
Result.setMessage(EnumRes.SUCCESS.getMsg());
} else {
Result.setMessage(msg);
}
Result.setData(data);
return Result;
}
public static Result<Void> fail(String msg) {
Result<Void> Result = new Result<>();
Result.setErrorCode(EnumRes.FAIL.getCode());
Result.setMessage(msg);
return Result;
}
public static Result<Void> fail(int code, String msg) {
Result<Void> Result = new Result<>();
Result.setErrorCode(code);
Result.setMessage(msg);
return Result;
}
public static <T> Result<T> fail(String msg, T data) {
Result<T> Result = new Result<>();
Result.setErrorCode(EnumRes.FAIL.getCode());
Result.setMessage(msg);
Result.setData(data);
return Result;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View File

@@ -0,0 +1,45 @@
package tech.easyflow.common.entity;
import java.math.BigInteger;
public class DatacenterQuery {
private Long pageNumber;
private Long pageSize;
// 表ID
private BigInteger tableId;
// 工作流传过来的查询条件
private String where;
public Long getPageNumber() {
return pageNumber;
}
public void setPageNumber(Long pageNumber) {
this.pageNumber = pageNumber;
}
public Long getPageSize() {
return pageSize;
}
public void setPageSize(Long pageSize) {
this.pageSize = pageSize;
}
public BigInteger getTableId() {
return tableId;
}
public void setTableId(BigInteger tableId) {
this.tableId = tableId;
}
public String getWhere() {
return where;
}
public void setWhere(String where) {
this.where = where;
}
}

View File

@@ -0,0 +1,14 @@
package tech.easyflow.common.entity;
import java.util.Date;
public abstract class DateEntity {
public abstract Date getCreated();
public abstract void setCreated(Date created);
public abstract Date getModified();
public abstract void setModified(Date modified);
}

View File

@@ -0,0 +1,4 @@
package tech.easyflow.common.entity;
public class DateTreeEntity extends TreeEntity{
}

View File

@@ -0,0 +1,165 @@
package tech.easyflow.common.entity;
import java.io.Serializable;
import java.math.BigInteger;
public class LoginAccount implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private BigInteger id;
/**
* 部门ID
*/
private BigInteger deptId;
/**
* 租户ID
*/
private BigInteger tenantId;
/**
* 登录账号
*/
private String loginName;
/**
* 账户类型
*/
private Integer accountType;
/**
* 昵称
*/
private String nickname;
/**
* 手机电话
*/
private String mobile;
/**
* 邮件
*/
private String email;
/**
* 账户头像
*/
private String avatar;
/**
* 数据权限类型
*/
private Integer dataScope;
/**
* 自定义部门权限
*/
private String deptIdList;
/**
* 备注
*/
private String remark;
public BigInteger getId() {
return id;
}
public void setId(BigInteger id) {
this.id = id;
}
public BigInteger getDeptId() {
return deptId;
}
public void setDeptId(BigInteger deptId) {
this.deptId = deptId;
}
public BigInteger getTenantId() {
return tenantId;
}
public void setTenantId(BigInteger tenantId) {
this.tenantId = tenantId;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public Integer getAccountType() {
return accountType;
}
public void setAccountType(Integer accountType) {
this.accountType = accountType;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Integer getDataScope() {
return dataScope;
}
public void setDataScope(Integer dataScope) {
this.dataScope = dataScope;
}
public String getDeptIdList() {
return deptIdList;
}
public void setDeptIdList(String deptIdList) {
this.deptIdList = deptIdList;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@@ -0,0 +1,6 @@
package tech.easyflow.common.entity;
import tech.easyflow.common.tree.TreeNode;
public class TreeEntity extends TreeNode {
}

View File

@@ -0,0 +1,75 @@
package tech.easyflow.common.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import tech.easyflow.common.Consts;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class BaseApp implements ApplicationListener<WebServerInitializedEvent> {
private static Integer port;
private static ApplicationContext appContext;
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
appContext = event.getApplicationContext();
port = event.getWebServer().getPort();
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
new SpringApplication(primarySources).run(args);
printRunningAt(port);
return (ConfigurableApplicationContext) appContext;
}
public static Object getBean(String name) {
return appContext.getBean(name);
}
protected static void printRunningAt(int port) {
StringBuilder msg = new StringBuilder("\nEasyFlow(version: "+ Consts.VERSION +") running at:\n");
msg.append(" > Local : http://localhost:").append(port).append("\n");
List<String> ipList = getLocalIpList();
for (String ip : ipList) {
msg.append(" > Network: http://").append(ip).append(":").append(port).append("\n");
}
System.out.println(msg);
}
private static List<String> getLocalIpList() {
List<String> ipList = new ArrayList<>();
try {
for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
NetworkInterface networkInterface = e.nextElement();
if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) {
continue;
}
for (Enumeration<InetAddress> ele = networkInterface.getInetAddresses(); ele.hasMoreElements(); ) {
InetAddress ip = ele.nextElement();
if (ip instanceof Inet4Address) {
ipList.add(ip.getHostAddress());
}
}
}
return ipList;
} catch (Exception e) {
return ipList;
}
}
}

View File

@@ -0,0 +1,189 @@
package tech.easyflow.common.tree;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.StringUtil;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class Tree<T extends TreeNode> {
private List<T> root;
private final Method idGetter;
private final Method pidGetter;
public Tree(List<T> nodes, String idFieldName, String pidFieldName) {
//noinspection unchecked
this((Class<T>) nodes.get(0).getClass(), idFieldName, pidFieldName);
for (T node : nodes) {
this.addNode(node);
}
}
public Tree(Class<T> clazz, String idFieldName, String pidFieldName) {
String idGetterMethodName = "get" + StringUtil.firstCharToUpperCase(idFieldName);
this.idGetter = ClassUtil.getFirstMethod(clazz, method -> {
String methodName = method.getName();
return methodName.equals(idGetterMethodName) && Modifier.isPublic(method.getModifiers());
});
String pidGetterMethodName = "get" + StringUtil.firstCharToUpperCase(pidFieldName);
this.pidGetter = ClassUtil.getFirstMethod(clazz, method -> {
String methodName = method.getName();
return methodName.equals(pidGetterMethodName) && Modifier.isPublic(method.getModifiers());
});
if (this.idGetter == null || this.pidGetter == null) {
throw new IllegalStateException("Can not find method \"" + idGetterMethodName + "\" or \"" + pidGetterMethodName + "\" in class: " + clazz.getName());
}
}
public void addNode(T node) {
if (root == null) {
root = new ArrayList<>();
root.add(node);
} else {
addToTree(this.root, node);
}
}
public void print() {
doPrint(0, this.root);
}
private void doPrint(int layerNo, List<T> nodes) {
if (nodes != null && !nodes.isEmpty()) {
for (T node : nodes) {
System.out.println(getPrefix(layerNo) + node.toString());
//noinspection unchecked
doPrint(layerNo + 1, (List<T>) node.getChildren());
}
}
}
public static String getPrefix(int layerNo) {
if (layerNo == 0) {
return "";
} else if (layerNo == 1) {
return "|-";
} else {
StringBuilder sb = new StringBuilder("|-");
for (int i = 0; i < (layerNo - 1); i++) {
sb.append("--");
}
return sb.toString();
}
}
private void addToTree(List<T> root, T newNode) {
List<T> children = new ArrayList<>();
T parent = findParentAndChildren(root, newNode, children);
if (!children.isEmpty()) {
//noinspection unchecked
newNode.setChildren((List<TreeNode>) children);
}
if (parent == null) {
root.add(newNode);
} else {
parent.addChild(newNode);
}
}
private T findParentAndChildren(List<T> root, T newNode, List<T> children) {
T parent = null;
for (T node : root) {
if (children != null && equalsInString(getId(newNode), getPid(node))) {
children.add(node);
}
if (parent == null) {
if (equalsInString(getId(node), getPid(newNode))) {
parent = node;
} else if (node.getChildren() != null) {
//noinspection unchecked
parent = findParentAndChildren((List<T>) node.getChildren(), newNode, null);
}
}
}
if (children != null && !children.isEmpty()) {
root.removeAll(children);
}
return parent;
}
private Object getId(Object object) {
try {
return idGetter.invoke(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Object getPid(Object object) {
try {
return pidGetter.invoke(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public List<T> getRoot() {
return root;
}
public List<T> getRootAsLayer() {
List<T> all = new ArrayList<>();
addNodesToList(this.root, all);
return all;
}
private void addNodesToList(List<T> nodes, List<T> list) {
if (nodes != null && !nodes.isEmpty()) {
for (T node : nodes) {
list.add(node);
//noinspection unchecked
addNodesToList((List<T>) node.getChildren(), list);
}
}
}
public static <T, X extends TreeNode> List<T> tryToTree(List<T> list) {
return tryToTree(list, true);
}
@SuppressWarnings("unchecked")
public static <T, X extends TreeNode> List<T> tryToTree(List<T> list, Boolean condition) {
if (condition != null && condition && list != null && !list.isEmpty()) {
T data = list.get(0);
if (data != null && TreeNode.class.isAssignableFrom(data.getClass())) {
Tree<X> tree = new Tree<>((List<X>) list, "id", "pid");
list = (List<T>) tree.getRoot();
}
}
return list;
}
private static boolean equalsInString(Object o1, Object o2) {
if (o1 == o2) return true;
if (o1 == null || o2 == null) return false;
return o1.toString().equals(o2.toString());
}
@SuppressWarnings("unchecked")
public static <T, X extends TreeNode> List<T> tryToTree(List<T> list,String idFieldName,String pidFieldName) {
if (list != null && !list.isEmpty()) {
T data = list.get(0);
if (data != null && TreeNode.class.isAssignableFrom(data.getClass())) {
Tree<X> tree = new Tree<>((List<X>) list, idFieldName, pidFieldName);
list = (List<T>) tree.getRoot();
}
}
return list;
}
}

View File

@@ -0,0 +1,26 @@
package tech.easyflow.common.tree;
import com.mybatisflex.annotation.Column;
import java.util.ArrayList;
import java.util.List;
public class TreeNode {
@Column(ignore = true)
private List<TreeNode> children;
public List<TreeNode> getChildren() {
return children;
}
public void setChildren(List<TreeNode> children) {
this.children = children;
}
public void addChild(TreeNode newNode) {
if (children == null) {
children = new ArrayList<>();
}
children.add(newNode);
}
}

View File

@@ -0,0 +1,74 @@
package tech.easyflow.common.util;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class FileUtil {
public static String calcByte(Long sizeInBytes) {
if (sizeInBytes == null) {
return "";
}
String sizeFormatted;
if (sizeInBytes >= 1024 * 1024 * 1024) {
// Convert to GB
double sizeInGB = sizeInBytes / (1024.0 * 1024.0 * 1024.0);
sizeFormatted = String.format("%.2f GB", sizeInGB);
} else if (sizeInBytes >= 1024 * 1024) {
// Convert to MB
double sizeInMB = sizeInBytes / (1024.0 * 1024.0);
sizeFormatted = String.format("%.2f MB", sizeInMB);
} else if (sizeInBytes >= 1024) {
// Convert to KB
double sizeInKB = sizeInBytes / 1024.0;
sizeFormatted = String.format("%.2f KB", sizeInKB);
} else {
// Keep in bytes
sizeFormatted = sizeInBytes + " bytes";
}
return sizeFormatted;
}
public static String getFileTypeByExtension(String fileName) {
if (fileName.endsWith(".txt")) {
return "txt";
} else if (fileName.endsWith(".pdf")) {
return "pdf";
} else if (fileName.endsWith(".md")) {
return "md";
} else if (fileName.endsWith(".docx")) {
return "docx";
} else if (fileName.endsWith(".xlsx")) {
return "xlsx";
} else if (fileName.endsWith(".ppt")) {
return "ppt";
} else if (fileName.endsWith(".pptx")) {
return "pptx";
}
else {
return null;
}
}
/**
* url解编码
* @param url
* @return
*/
public static String getDecodedUrl(String url) {
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8)
.replace("+", "%20") // 空格转 %20
.replace("%2F", "/"); // 保留路径分隔符 /
try {
URI validUri = new URI(encodedUrl);
return validUri.toString();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,64 @@
package tech.easyflow.common.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.concurrent.ThreadLocalRandom;
public class HashUtil {
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
private static final char[] CHAR_ARRAY = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
public static String md5(String srcStr) {
return hash("MD5", srcStr);
}
public static String sha256(String srcStr) {
return hash("SHA-256", srcStr);
}
public static String macHha256(String srcStr, String secret) {
try {
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSHA256.init(secretKey);
byte[] bytes = hmacSHA256.doFinal(srcStr.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String hash(String algorithm, String srcStr) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] bytes = md.digest(srcStr.getBytes(StandardCharsets.UTF_8));
return bytesToHex(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String bytesToHex(byte[] bytes) {
StringBuilder ret = new StringBuilder(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
}
return ret.toString();
}
public static String generateSalt(int saltLength) {
StringBuilder salt = new StringBuilder(saltLength);
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < saltLength; i++) {
salt.append(CHAR_ARRAY[random.nextInt(CHAR_ARRAY.length)]);
}
return salt.toString();
}
}

View File

@@ -0,0 +1,58 @@
package tech.easyflow.common.util;
import okio.BufferedSink;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class IOUtil {
private static final int DEFAULT_BUFFER_SIZE = 8192;
public static void writeBytes(byte[] bytes, File toFile) {
try (FileOutputStream stream = new FileOutputStream(toFile)) {
stream.write(bytes);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static byte[] readBytes(File file) {
try (FileInputStream inputStream = new FileInputStream(file)) {
return readBytes(inputStream);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static byte[] readBytes(InputStream inputStream) {
try {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
copy(inputStream, outStream);
return outStream.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static void copy(InputStream inputStream, BufferedSink sink) throws IOException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
for (int len; (len = inputStream.read(buffer)) != -1; ) {
sink.write(buffer, 0, len);
}
}
public static void copy(InputStream inputStream, OutputStream outStream) throws IOException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
for (int len; (len = inputStream.read(buffer)) != -1; ) {
outStream.write(buffer, 0, len);
}
}
public static String readUtf8(InputStream inputStream) throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
copy(inputStream, outStream);
return new String(outStream.toByteArray(), StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,11 @@
package tech.easyflow.common.util;
import java.util.UUID;
public class IdUtil {
public static String generateUUID() {
UUID uuid = UUID.randomUUID();
return uuid.toString().replace("-", "").toLowerCase();
}
}

View File

@@ -0,0 +1,200 @@
package tech.easyflow.common.util;
import java.util.Map;
/**
* Map工具类提供从Map中安全获取指定类型值的方法。
*/
public class MapUtil {
/**
* 从Map中获取字符串类型的值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @return 对应键的字符串值如果不存在或转换失败则返回null
*/
public static String getString(Map<String, Object> map, String key) {
if (map == null) return null;
return toString(map.get(key));
}
/**
* 从Map中获取字符串类型的值并支持默认值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @param defaultValue 默认返回值
* @return 对应键的字符串值,如果不存在或转换失败则返回默认值
*/
public static String getString(Map<String, Object> map, String key, String defaultValue) {
if (map == null) return defaultValue;
String value = toString(map.get(key));
return value == null ? defaultValue : value;
}
/**
* 从Map中获取整数类型的值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @return 对应键的整数值如果不存在或转换失败则返回null
*/
public static Integer getInteger(Map<String, Object> map, String key) {
if (map == null) return null;
return toInteger(map.get(key));
}
/**
* 从Map中获取整数类型的值并支持默认值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @param defaultValue 默认返回值
* @return 对应键的整数值,如果不存在或转换失败则返回默认值
*/
public static Integer getInteger(Map<String, Object> map, String key, Integer defaultValue) {
if (map == null) return defaultValue;
Integer value = toInteger(map.get(key));
return value == null ? defaultValue : value;
}
/**
* 从Map中获取长整数类型的值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @return 对应键的长整数值如果不存在或转换失败则返回null
*/
public static Long getLong(Map<String, Object> map, String key) {
if (map == null) return null;
return toLong(map.get(key));
}
/**
* 从Map中获取长整数类型的值并支持默认值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @param defaultValue 默认返回值
* @return 对应键的长整数值,如果不存在或转换失败则返回默认值
*/
public static Long getLong(Map<String, Object> map, String key, Long defaultValue) {
if (map == null) return defaultValue;
Long value = toLong(map.get(key));
return value == null ? defaultValue : value;
}
/**
* 从Map中获取双精度浮点数类型的值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @return 对应键的双精度浮点数值如果不存在或转换失败则返回null
*/
public static Double getDouble(Map<String, Object> map, String key) {
if (map == null) return null;
return toDouble(map.get(key));
}
/**
* 从Map中获取双精度浮点数类型的值并支持默认值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @param defaultValue 默认返回值
* @return 对应键的双精度浮点数值,如果不存在或转换失败则返回默认值
*/
public static Double getDouble(Map<String, Object> map, String key, Double defaultValue) {
if (map == null) return defaultValue;
Double value = toDouble(map.get(key));
return value == null ? defaultValue : value;
}
/**
* 从Map中获取布尔类型的值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @return 对应键的布尔值如果不存在或转换失败则返回null
*/
public static Boolean getBoolean(Map<String, Object> map, String key) {
if (map == null) return null;
return toBoolean(map.get(key));
}
/**
* 从Map中获取布尔类型的值并支持默认值。
*
* @param map 包含键值对的Map对象
* @param key 要查找的键
* @param defaultValue 默认返回值
* @return 对应键的布尔值,如果不存在或转换失败则返回默认值
*/
public static Boolean getBoolean(Map<String, Object> map, String key, Boolean defaultValue) {
if (map == null) return defaultValue;
Boolean value = toBoolean(map.get(key));
return value == null ? defaultValue : value;
}
/**
* 将给定对象转换为字符串表示形式。
*
* @param obj 待转换的对象
* @return 字符串结果若原对象为null则返回null如果是String类型直接返回否则调用toString()
*/
private static String toString(Object obj) {
if (obj == null) return null;
if (obj instanceof String) return (String) obj;
return obj.toString();
}
/**
* 将给定对象转换为整数。
*
* @param obj 待转换的对象
* @return 整数结果若原对象为null则返回null如果是Number子类则取其int值否则尝试解析字符串
*/
private static Integer toInteger(Object obj) {
if (obj == null) return null;
if (obj instanceof Number) return ((Number) obj).intValue();
return Integer.parseInt(obj.toString());
}
/**
* 将给定对象转换为长整数。
*
* @param obj 待转换的对象
* @return 长整数结果若原对象为null则返回null如果是Number子类则取其long值否则尝试解析字符串
*/
private static Long toLong(Object obj) {
if (obj == null) return null;
if (obj instanceof Number) return ((Number) obj).longValue();
return Long.parseLong(obj.toString());
}
/**
* 将给定对象转换为双精度浮点数。
*
* @param obj 待转换的对象
* @return 双精度浮点数结果若原对象为null则返回null如果是Number子类则取其double值否则尝试解析字符串
*/
private static Double toDouble(Object obj) {
if (obj == null) return null;
if (obj instanceof Number) return ((Number) obj).doubleValue();
return Double.parseDouble(obj.toString());
}
/**
* 将给定对象转换为布尔值。
*
* @param obj 待转换的对象
* @return 布尔值结果若原对象为null则返回null如果是Boolean类型直接返回否则尝试解析字符串
*/
private static Boolean toBoolean(Object obj) {
if (obj == null) return null;
if (obj instanceof Boolean) return (Boolean) obj;
return Boolean.parseBoolean(obj.toString());
}
}

View File

@@ -0,0 +1,112 @@
package tech.easyflow.common.util;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.table.IdInfo;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.FieldWrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MapperUtil {
/**
* 同步 List 到数据库
*
* @param newModels 新的 Models
* @param mapper Mapper 查询
* @param existQueryWrapper 查询旧的 Wrapper
* @param getter 根据什么字段来对比进行同步
* @param <T> Entity 类
*/
public static <T> void syncList(List<T> newModels, BaseMapper<T> mapper, QueryWrapper existQueryWrapper,
Function<T, Object> getter) {
syncList(newModels, mapper, existQueryWrapper, getter, null);
}
/**
* 同步 List 到数据库
*
* @param newModels 新的 Models
* @param mapper Mapper 查询
* @param existQueryWrapper 查询旧的 Wrapper
* @param getter 根据什么字段来对比进行同步
* @param onSyncBefore 在同步到数据库之前,可能需要做的前置操作
* @param <T> Entity 类
*/
public static <T> void syncList(List<T> newModels, BaseMapper<T> mapper, QueryWrapper existQueryWrapper,
Function<T, Object> getter,
Consumer<T> onSyncBefore) {
List<T> existModels = mapper.selectListByQuery(existQueryWrapper);
List<T> needDeletes = new ArrayList<>();
List<T> saveOrUpdates = new ArrayList<>();
if (CollectionUtil.isNotEmpty(newModels)) {
if (CollectionUtil.isEmpty(existModels)) {
saveOrUpdates.addAll(newModels);
} else {
for (T existModel : existModels) {
boolean removed = true;
for (T newModel : newModels) {
if (Objects.equals(getter.apply(existModel), getter.apply(newModel))) {
removed = false;
break;
}
}
if (removed) {
needDeletes.add(existModel);
}
}
TableInfo tableInfo = TableInfoFactory.ofEntityClass(newModels.get(0).getClass());
List<IdInfo> primaryKeyList = tableInfo.getPrimaryKeyList();
List<FieldWrapper> fieldWrappers = primaryKeyList.stream().map(idInfo -> FieldWrapper.of(tableInfo.getEntityClass(), idInfo.getProperty()))
.collect(Collectors.toList());
for (T newModel : newModels) {
for (T existModel : existModels) {
if (Objects.equals(getter.apply(existModel), getter.apply(newModel))) {
//复制旧数据库的 ID 到新 model
for (FieldWrapper fieldWrapper : fieldWrappers) {
fieldWrapper.set(fieldWrapper.get(existModel), newModel);
}
break;
}
}
saveOrUpdates.add(newModel);
}
}
} else if (CollectionUtil.isNotEmpty(existModels)) {
needDeletes.addAll(existModels);
}
Db.tx(() -> {
for (T needDelete : needDeletes) {
mapper.delete(needDelete);
}
for (T saveOrUpdate : saveOrUpdates) {
if (onSyncBefore != null) {
onSyncBefore.accept(saveOrUpdate);
}
mapper.insertOrUpdate(saveOrUpdate);
}
return true;
});
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2022-2023, Agents-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tech.easyflow.common.util;
import com.alibaba.fastjson.JSON;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class Maps extends HashMap<String, Object> {
public static Maps of() {
return new Maps();
}
public static Maps of(String key, Object value) {
Maps maps = Maps.of();
maps.put(key, value);
return maps;
}
public static Maps ofNotNull(String key, Object value) {
return new Maps().setIfNotNull(key, value);
}
public static Maps ofNotEmpty(String key, Object value) {
return new Maps().setIfNotEmpty(key, value);
}
public static Maps ofNotEmpty(String key, Maps value) {
return new Maps().setIfNotEmpty(key, value);
}
public Maps set(String key, Object value) {
super.put(key, value);
return this;
}
public Maps setChild(String key, Object value) {
if (key.contains(".")) {
String[] keys = key.split("\\.");
Map<String, Object> currentMap = this;
for (int i = 0; i < keys.length; i++) {
String currentKey = keys[i].trim();
if (currentKey.isEmpty()) {
continue;
}
if (i == keys.length - 1) {
currentMap.put(currentKey, value);
} else {
//noinspection unchecked
currentMap = (Map<String, Object>) currentMap.computeIfAbsent(currentKey, k -> Maps.of());
}
}
} else {
super.put(key, value);
}
return this;
}
public Maps setOrDefault(String key, Object value, Object orDefault) {
if (isNullOrEmpty(value)) {
return this.set(key, orDefault);
} else {
return this.set(key, value);
}
}
public Maps setIf(boolean condition, String key, Object value) {
if (condition) put(key, value);
return this;
}
public Maps setIf(Function<Maps, Boolean> func, String key, Object value) {
if (func.apply(this)) put(key, value);
return this;
}
public Maps setIfNotNull(String key, Object value) {
if (value != null) put(key, value);
return this;
}
public Maps setIfNotEmpty(String key, Object value) {
if (!isNullOrEmpty(value)) {
put(key, value);
}
return this;
}
public Maps setIfContainsKey(String checkKey, String key, Object value) {
if (this.containsKey(checkKey)) {
this.put(key, value);
}
return this;
}
public Maps setIfNotContainsKey(String checkKey, String key, Object value) {
if (!this.containsKey(checkKey)) {
this.put(key, value);
}
return this;
}
public String toJSON() {
return JSON.toJSONString(this);
}
private static boolean isNullOrEmpty(Object value) {
if (value == null) {
return true;
}
if (value instanceof Collection && ((Collection<?>) value).isEmpty()) {
return true;
}
if (value instanceof Map && ((Map<?, ?>) value).isEmpty()) {
return true;
}
if (value.getClass().isArray() && Array.getLength(value) == 0) {
return true;
}
return value instanceof String && ((String) value).trim().isEmpty();
}
}

View File

@@ -0,0 +1,169 @@
package tech.easyflow.common.util;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;
public class OkHttpClientUtil {
private static final Logger log = LoggerFactory.getLogger(OkHttpClientUtil.class);
// 系统属性前缀
private static final String PREFIX = "okhttp.";
// 环境变量前缀(大写)
private static final String ENV_PREFIX = "OKHTTP_";
private static volatile OkHttpClient defaultClient;
private static volatile OkHttpClient.Builder customBuilder;
public static void setOkHttpClientBuilder(OkHttpClient.Builder builder) {
if (defaultClient != null) {
throw new IllegalStateException("OkHttpClient has already been initialized. " +
"Please set the builder before first usage.");
}
customBuilder = builder;
}
public static OkHttpClient buildDefaultClient() {
if (defaultClient == null) {
synchronized (OkHttpClientUtil.class) {
if (defaultClient == null) {
OkHttpClient.Builder builder = customBuilder != null
? customBuilder
: createDefaultBuilder();
defaultClient = builder.build();
log.debug("OkHttpClient initialized with config: connectTimeout={}s, readTimeout={}s, writeTimeout={}s, " +
"connectionPool(maxIdle={}, keepAlive={}min)",
getConnectTimeout(), getReadTimeout(), getWriteTimeout(),
getMaxIdleConnections(), getKeepAliveMinutes());
}
}
}
return defaultClient;
}
private static OkHttpClient.Builder createDefaultBuilder() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(getConnectTimeout(), TimeUnit.SECONDS)
.readTimeout(getReadTimeout(), TimeUnit.SECONDS)
.writeTimeout(getWriteTimeout(), TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(getMaxIdleConnections(), getKeepAliveMinutes(), TimeUnit.MINUTES));
configureProxy(builder);
return builder;
}
// ==================== 配置读取方法 ====================
private static int getConnectTimeout() {
return getIntConfig("connectTimeout", "CONNECT_TIMEOUT", 60);
}
private static int getReadTimeout() {
return getIntConfig("readTimeout", "READ_TIMEOUT", 300);
}
private static int getWriteTimeout() {
return getIntConfig("writeTimeout", "WRITE_TIMEOUT", 60);
}
private static int getMaxIdleConnections() {
return getIntConfig("connectionPool.maxIdleConnections", "CONNECTION_POOL_MAX_IDLE_CONNECTIONS", 5);
}
private static long getKeepAliveMinutes() {
return getLongConfig("connectionPool.keepAliveMinutes", "CONNECTION_POOL_KEEP_ALIVE_MINUTES", 10);
}
private static String getProxyHost() {
String host = getPropertyOrEnv("proxy.host", "PROXY_HOST", null);
if (StringUtil.hasText(host)) return host.trim();
// 兼容 Java 标准代理属性(作为 fallback
host = System.getProperty("https.proxyHost");
if (StringUtil.hasText(host)) return host.trim();
host = System.getProperty("http.proxyHost");
if (StringUtil.hasText(host)) return host.trim();
return null;
}
private static String getProxyPort() {
String port = getPropertyOrEnv("proxy.port", "PROXY_PORT", null);
if (StringUtil.hasText(port)) return port.trim();
// 兼容 Java 标准代理属性
port = System.getProperty("https.proxyPort");
if (StringUtil.hasText(port)) return port.trim();
port = System.getProperty("http.proxyPort");
if (StringUtil.hasText(port)) return port.trim();
return null;
}
// ==================== 工具方法 ====================
private static int getIntConfig(String sysPropKey, String envKey, int defaultValue) {
String value = getPropertyOrEnv(sysPropKey, envKey, null);
if (value == null) return defaultValue;
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
log.warn("Invalid integer value for '{}': '{}'. Using default: {}", fullSysPropKey(sysPropKey), value, defaultValue);
return defaultValue;
}
}
private static long getLongConfig(String sysPropKey, String envKey, long defaultValue) {
String value = getPropertyOrEnv(sysPropKey, envKey, null);
if (value == null) return defaultValue;
try {
return Long.parseLong(value.trim());
} catch (NumberFormatException e) {
log.warn("Invalid long value for '{}': '{}'. Using default: {}", fullSysPropKey(sysPropKey), value, defaultValue);
return defaultValue;
}
}
private static String getPropertyOrEnv(String sysPropKey, String envKey, String defaultValue) {
// 1. 系统属性优先
String value = System.getProperty(fullSysPropKey(sysPropKey));
if (value != null) return value;
// 2. 环境变量
value = System.getenv(ENV_PREFIX + envKey);
if (value != null) return value;
return defaultValue;
}
private static String fullSysPropKey(String key) {
return PREFIX + key;
}
// ==================== 代理配置 ====================
private static void configureProxy(OkHttpClient.Builder builder) {
String proxyHost = getProxyHost();
String proxyPort = getProxyPort();
if (StringUtil.hasText(proxyHost) && StringUtil.hasText(proxyPort)) {
try {
int port = Integer.parseInt(proxyPort);
InetSocketAddress address = new InetSocketAddress(proxyHost, port);
builder.proxy(new Proxy(Proxy.Type.HTTP, address));
log.debug("HTTP proxy configured via config: {}:{}", proxyHost, port);
} catch (NumberFormatException e) {
log.warn("Invalid proxy port '{}'. Proxy will be ignored.", proxyPort, e);
}
}
}
}

View File

@@ -0,0 +1,269 @@
package tech.easyflow.common.util;
import okhttp3.*;
import okio.BufferedSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Map;
public class OkHttpUtil {
private static final Logger LOG = LoggerFactory.getLogger(OkHttpUtil.class);
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private static OkHttpClient getOkHttpClient() {
return OkHttpClientUtil.buildDefaultClient();
}
public static String get(String url) {
return executeString(url, "GET", null, null);
}
/**
* 获取远程URL资源的文件大小字节数
* 支持分块传输Transfer-Encoding: chunked的大文件兼容普通文件
* @param url 远程资源URL
* @return 资源字节大小,失败/无有效大小返回 0L
*/
public static long getFileSize(String url) {
Request request = new Request.Builder()
.url(url)
.get()
.build();
Response response = null;
InputStream in = null;
try {
response = getOkHttpClient().newCall(request).execute();
if (!response.isSuccessful()) {
LOG.error("Failed to get file size, HTTP response code: {} for url: {}",
response.code(), url);
return 0L;
}
ResponseBody body = response.body();
if (body == null) {
LOG.warn("Response body is null for url: {}", url);
return 0L;
}
in = body.byteStream();
byte[] buffer = new byte[1024 * 8];
long totalBytes = 0L;
int len;
while ((len = in.read(buffer)) != -1) {
totalBytes += len;
}
LOG.info("Success to get file size for url: {}, size: {} bytes (≈ {} M)",
url, totalBytes, String.format("%.2f", totalBytes / 1024.0 / 1024.0));
return totalBytes;
} catch (IOException e) {
LOG.error("IO exception when getting file size for url: {}", url, e);
return 0L;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
LOG.warn("Failed to close InputStream when getting file size", e);
}
}
if (response != null) {
response.close();
}
}
}
public static byte[] getBytes(String url) {
return executeBytes(url, "GET", null, null);
}
public static String get(String url, Map<String, String> headers) {
return executeString(url, "GET", headers, null);
}
public static InputStream getInputStream(String url) {
try (Response response = getOkHttpClient().newCall(new Request.Builder().url(url).build()).execute();
ResponseBody body = response.body();
InputStream in = body != null ? body.byteStream() : null;
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
if (!response.isSuccessful() || in == null) {
LOG.error("HTTP request failed with code: {} for url: {}", response.code(), url);
return null;
}
byte[] buffer = new byte[1024 * 4];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
return new ByteArrayInputStream(out.toByteArray());
} catch (IOException ioe) {
LOG.error("HTTP getInputStream failed: " + url, ioe);
} catch (Exception e) {
LOG.error(e.toString(), e);
throw e;
}
return null;
}
public static String post(String url, Map<String, String> headers, String payload) {
return executeString(url, "POST", headers, payload);
}
public static byte[] postBytes(String url, Map<String, String> headers, String payload) {
return executeBytes(url, "POST", headers, payload);
}
public static String put(String url, Map<String, String> headers, String payload) {
return executeString(url, "PUT", headers, payload);
}
public static String delete(String url, Map<String, String> headers, String payload) {
return executeString(url, "DELETE", headers, payload);
}
public static String multipartString(String url, Map<String, String> headers, Map<String, Object> payload) {
try (Response response = multipart(url, headers, payload);
ResponseBody body = response.body()) {
if (body != null) {
return body.string();
}
} catch (IOException ioe) {
LOG.error("HTTP multipartString failed: " + url, ioe);
} catch (Exception e) {
LOG.error(e.toString(), e);
throw e;
}
return null;
}
public static byte[] multipartBytes(String url, Map<String, String> headers, Map<String, Object> payload) {
try (Response response = multipart(url, headers, payload);
ResponseBody body = response.body()) {
if (body != null) {
return body.bytes();
}
} catch (IOException ioe) {
LOG.error("HTTP multipartBytes failed: " + url, ioe);
} catch (Exception e) {
LOG.error(e.toString(), e);
throw e;
}
return null;
}
public static String executeString(String url, String method, Map<String, String> headers, Object payload) {
try (Response response = execute0(url, method, headers, payload);
ResponseBody body = response.body()) {
if (body != null) {
return body.string();
}
} catch (IOException ioe) {
LOG.error("HTTP executeString failed: " + url, ioe);
} catch (Exception e) {
LOG.error(e.toString(), e);
throw e;
}
return null;
}
public static byte[] executeBytes(String url, String method, Map<String, String> headers, Object payload) {
try (Response response = execute0(url, method, headers, payload);
ResponseBody body = response.body()) {
if (body != null) {
return body.bytes();
}
} catch (IOException ioe) {
LOG.error("HTTP executeBytes failed: " + url, ioe);
} catch (Exception e) {
LOG.error(e.toString(), e);
throw e;
}
return null;
}
private static Response execute0(String url, String method, Map<String, String> headers, Object payload) throws IOException {
Request.Builder builder = new Request.Builder().url(url);
if (headers != null && !headers.isEmpty()) {
headers.forEach(builder::addHeader);
}
Request request;
if ("GET".equalsIgnoreCase(method)) {
request = builder.build();
} else {
RequestBody body = RequestBody.create(payload == null ? "" : payload.toString(), JSON_TYPE);
request = builder.method(method, body).build();
}
return getOkHttpClient().newCall(request).execute();
}
public static Response multipart(String url, Map<String, String> headers, Map<String, Object> payload) throws IOException {
Request.Builder builder = new Request.Builder().url(url);
if (headers != null && !headers.isEmpty()) {
headers.forEach(builder::addHeader);
}
MultipartBody.Builder mbBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
payload.forEach((key, value) -> {
if (value instanceof File) {
File file = (File) value;
RequestBody body = RequestBody.create(file, MediaType.parse("application/octet-stream"));
mbBuilder.addFormDataPart(key, file.getName(), body);
} else if (value instanceof InputStream) {
RequestBody body = new InputStreamRequestBody(MediaType.parse("application/octet-stream"), (InputStream) value);
mbBuilder.addFormDataPart(key, key, body);
} else if (value instanceof byte[]) {
mbBuilder.addFormDataPart(key, key, RequestBody.create((byte[]) value));
} else {
mbBuilder.addFormDataPart(key, String.valueOf(value));
}
});
MultipartBody multipartBody = mbBuilder.build();
Request request = builder.post(multipartBody).build();
return getOkHttpClient().newCall(request).execute();
}
public static class InputStreamRequestBody extends RequestBody {
private final InputStream inputStream;
private final MediaType contentType;
public InputStreamRequestBody(MediaType contentType, InputStream inputStream) {
if (inputStream == null) throw new NullPointerException("inputStream == null");
this.contentType = contentType;
this.inputStream = inputStream;
}
@Override
public MediaType contentType() {
return contentType;
}
@Override
public long contentLength() throws IOException {
return inputStream.available() == 0 ? -1 : inputStream.available();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
IOUtil.copy(inputStream, sink);
}
}
}

View File

@@ -0,0 +1,72 @@
package tech.easyflow.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.Properties;
public class PropertiesUtil {
private static final Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);
public static Properties textToProperties(String text) {
Properties prop = new Properties();
try (StringReader reader = new StringReader(text)) {
prop.load(reader);
} catch (Exception e) {
logger.error(e.toString(), e);
}
return prop;
}
/**
* 将Properties对象转换为指定类型的实体对象。
*
* @param properties 包含配置信息的Properties对象
* @param entityClass 目标实体类的Class对象
* @param <T> 目标实体类的泛型类型
* @return 转换后的实体对象
*/
public static <T> T propertiesToEntity(Properties properties, Class<T> entityClass) {
try {
T entity = entityClass.getDeclaredConstructor().newInstance();
for (Field field : entityClass.getDeclaredFields()) {
String fieldName = field.getName();
String propertyValue = properties.getProperty(fieldName);
if (propertyValue != null) {
field.setAccessible(true);
Class<?> fieldType = field.getType();
if (fieldType.equals(String.class)) {
field.set(entity, propertyValue);
} else if (fieldType.equals(int.class) || fieldType.equals(Integer.class)) {
field.set(entity, Integer.parseInt(propertyValue));
} else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) {
field.set(entity, Long.parseLong(propertyValue));
} else if (fieldType.equals(boolean.class) || fieldType.equals(Boolean.class)) {
field.set(entity, Boolean.parseBoolean(propertyValue));
} else if (fieldType.equals(double.class) || fieldType.equals(Double.class)) {
field.set(entity, Double.parseDouble(propertyValue));
} else if (fieldType.equals(float.class) || fieldType.equals(Float.class)) {
field.set(entity, Float.parseFloat(propertyValue));
} else {
// 处理其他类型,例如自定义对象
// 这里可以根据需要扩展
}
}
}
return entity;
} catch (Exception e) {
throw new RuntimeException("Failed to convert properties to entity", e);
}
}
public static <T> T propertiesTextToEntity(String propertiesText, Class<T> entityClass) {
Properties properties = textToProperties(propertiesText);
return propertiesToEntity(properties, entityClass);
}
}

View File

@@ -0,0 +1,181 @@
package tech.easyflow.common.util;
import com.alibaba.fastjson.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.Map;
public class RequestUtil {
private static final Logger LOG = LoggerFactory.getLogger(RequestUtil.class);
private static final String jsonCacheKey = "__$JSONObjectOrArray";
public static Object readJsonObjectOrArray(HttpServletRequest request) {
Object jsonObjectOrArray = request.getAttribute(jsonCacheKey);
if (jsonObjectOrArray == null) {
String body = readBodyString(request);
jsonObjectOrArray = JSON.parse(body);
request.setAttribute(jsonCacheKey, jsonObjectOrArray);
}
return jsonObjectOrArray;
}
public static String readBodyString(HttpServletRequest request) {
String ce = request.getCharacterEncoding();
if (request instanceof ContentCachingRequestWrapper) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
byte[] contentAsByteArray = wrapper.getContentAsByteArray();
if (contentAsByteArray.length != 0) {
try {
return new String(contentAsByteArray, ce != null ? ce : "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
try {
InputStreamReader reader = new InputStreamReader(request.getInputStream(), ce != null ? ce : "UTF-8");
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
for (int num; (num = reader.read(buf, 0, buf.length)) != -1; ) {
sb.append(buf, 0, num);
}
return sb.toString();
} catch (IOException e) {
LOG.error(e.toString(), e);
}
return null;
}
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-requested-For");
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip != null && ip.contains(",")) {
String[] ips = ip.split(",");
for (String strIp : ips) {
if (!("unknown".equalsIgnoreCase(strIp))) {
ip = strIp;
break;
}
}
}
return ip;
}
public static String getUserAgent(HttpServletRequest request) {
return request.getHeader("User-Agent");
}
public static String getReferer(HttpServletRequest request) {
return request.getHeader("Referer");
}
public static Boolean getParamAsBoolean(Map<String, String[]> parameters, String key) {
if (parameters == null || parameters.isEmpty()) {
return null;
}
String[] strings = parameters.get(key);
if (strings == null || strings.length == 0) {
return null;
}
return "true".equalsIgnoreCase(strings[0]);
}
public static String getParamAsString(String key) {
return getParamAsString(getRequest().getParameterMap(), key);
}
public static String getParamAsString(Map<String, String[]> parameters, String key) {
if (parameters == null || parameters.isEmpty()) {
return null;
}
String[] strings = parameters.get(key);
if (strings == null || strings.length == 0) {
return null;
}
String trimmed = strings[0].trim();
if (trimmed.isEmpty()) {
return null;
}
return trimmed;
}
public static BigInteger getParamAsBigInteger(Map<String, String[]> parameters, String key) {
if (parameters == null || parameters.isEmpty()) {
return null;
}
String[] strings = parameters.get(key);
if (strings == null || strings.length == 0) {
return null;
}
return new BigInteger(strings[0]);
}
public static BigInteger getParamAsBigInteger(String key) {
return getParamAsBigInteger(getRequest().getParameterMap(), key);
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 获取 HttpServletRequest
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取 HttpServletResponse
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
}

View File

@@ -0,0 +1,23 @@
package tech.easyflow.common.util;
import com.alibaba.fastjson.JSON;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseUtil {
public static void renderJson(HttpServletResponse response, Object object) {
String json = JSON.toJSONString(object);
renderJson(response, json);
}
public static void renderJson(HttpServletResponse response, String jsonString) {
response.setContentType("application/json; charset=utf-8");
try {
response.getWriter().write(jsonString);
} catch (IOException e) {
//ignore
}
}
}

View File

@@ -0,0 +1,22 @@
package tech.easyflow.common.util;
import com.alibaba.fastjson2.JSON;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
public class SSEUtil {
public static SseEmitter sseEmitterForContent(String content) {
SseEmitter emitter = new SseEmitter((long) (1000 * 60 * 2));
try {
String jsonString = JSON.toJSONString(Maps.of("content", content));
emitter.send(jsonString);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
emitter.complete();
}
return emitter;
}
}

View File

@@ -0,0 +1,84 @@
package tech.easyflow.common.util;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
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.core.io.Resource;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.util.Map;
@Component
public class SpringContextUtil implements BeanFactoryPostProcessor, ApplicationContextAware {
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringContextUtil.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static ListableBeanFactory getBeanFactory() {
return null == beanFactory ? applicationContext : beanFactory;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) getBeanFactory().getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return getBeanFactory().getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getBeanFactory().getBean(name, clazz);
}
public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
return getBeanFactory().getBeansWithAnnotation(annotationType);
}
public static Resource getResource(String location){
return getApplicationContext().getResource(location);
}
public static String getProperty(String key) {
if (null == applicationContext) {
return null;
}
return applicationContext.getEnvironment().getProperty(key);
}
public static String getProperty(String key, String defaultValue) {
if (null == applicationContext) {
return null;
}
return applicationContext.getEnvironment().getProperty(key, defaultValue);
}
public static <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
if (null == applicationContext) {
return null;
}
return applicationContext.getEnvironment().getProperty(key, targetType, defaultValue);
}
}

View File

@@ -0,0 +1,34 @@
package tech.easyflow.common.util;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.core.constant.SqlOperator;
import com.mybatisflex.core.query.SqlOperators;
import com.mybatisflex.core.util.ClassUtil;
import org.apache.ibatis.util.MapUtil;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SqlOperatorsUtil {
private static Map<Class<?>, SqlOperators> sqlOperatorsMap = new ConcurrentHashMap<>();
public static SqlOperators build(Class<?> entityClass) {
return new SqlOperators(MapUtil.computeIfAbsent(sqlOperatorsMap, entityClass, aClass -> {
SqlOperators sqlOperators = new SqlOperators();
List<Field> allFields = ClassUtil.getAllFields(entityClass);
allFields.forEach(field -> {
if (field.getType() == String.class) {
Column column = field.getAnnotation(Column.class);
if (column != null && column.ignore()) {
return;
}
sqlOperators.set(field.getName(), SqlOperator.LIKE);
}
});
return sqlOperators;
}));
}
}

View File

@@ -0,0 +1,28 @@
package tech.easyflow.common.util;
public class SqlUtil {
public static String buildOrderBy(String sortKey, String sortType) {
return buildOrderBy(sortKey, sortType, "");
}
public static String buildOrderBy(String sortKey, String sortType, String defaultOrderBy) {
if (StringUtil.noText(sortKey)) {
return defaultOrderBy;
}
sortKey = sortKey.trim();
if (StringUtil.noText(sortType)) {
return sortKey;
}
sortType = sortType.toLowerCase().trim();
if (!"asc".equals(sortType) && !"desc".equals(sortType)) {
throw new IllegalArgumentException("sortType only support asc or desc");
}
com.mybatisflex.core.util.SqlUtil.keepOrderBySqlSafely(sortKey);
return sortKey + " " + sortType;
}
}

View File

@@ -0,0 +1,99 @@
package tech.easyflow.common.util;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
public class StringUtil extends StringUtils {
public static boolean areHasText(String... strings) {
if (strings == null || strings.length == 0) {
return false;
}
for (String string : strings) {
if (!hasText(string)) {
return false;
}
}
return true;
}
public static boolean noText(String string) {
return !hasText(string);
}
public static boolean isEmail(String email) {
return StringUtils.hasText(email)
&& email.matches("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
}
public static boolean isMobileNumber(String str) {
return hasText(str) && str.length() == 11 && str.startsWith("1") && isNumeric(str);
}
public static boolean isNumeric(String str) {
if (noText(str)) {
return false;
}
for (int i = str.length(); --i >= 0; ) {
int chr = str.charAt(i);
if (chr < 48 || chr > 57) {
return false;
}
}
return true;
}
public static String getHasTextOne(String... strings) {
for (String string : strings) {
if (hasText(string)) {
return string;
}
}
return null;
}
public static Set<String> splitToSet(String src, String regex) {
if (src == null) {
return Collections.emptySet();
}
String[] strings = src.split(regex);
Set<String> set = new LinkedHashSet<>();
for (String s : strings) {
if (hasText(s)) {
set.add(s.trim());
}
}
return set;
}
public static Set<String> splitToSetByComma(String src) {
return splitToSet(src, ",");
}
/**
* 删除文件名的后缀。
*
* @param fileName 完整的文件名,包括后缀
* @return 不带后缀的文件名
*/
public static String removeFileExtension(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return fileName;
}
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex == -1) {
return fileName; // 没有后缀
}
return fileName.substring(0, dotIndex);
}
}

View File

@@ -0,0 +1,115 @@
package tech.easyflow.common.util;
import java.net.IDN;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UrlEncoderUtil {
// 匹配URL的「协议+域名」部分(如 http://localhost:8080 或 https://www.baidu.商店)
private static final Pattern URL_DOMAIN_PATTERN = Pattern.compile("^((http|https)://[^/]+)(/.*)?$");
// 匹配连续的斜杠(用于清理多余/
private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("/+");
/**
* 完整URL编码兼容IDN域名 + 路径/文件名URL编码 + 自动清理多余斜杠
* @param url 完整URLhttp://localhost:8080//attachment/host 副本.txt
* @return 编码后URLhttp://localhost:8080/attachment/host%20%E5%89%AF%E6%9C%AC.txt
*/
public static String getEncodedUrl(String url) {
if (url == null || url.isEmpty()) {
return "";
}
// 第一步:先清理所有连续的斜杠(// → /),保留协议后的//(如 http://
String cleanUrl = cleanMultipleSlashes(url);
Matcher matcher = URL_DOMAIN_PATTERN.matcher(cleanUrl);
String domainPart = ""; // 协议+域名部分(如 http://localhost:8080
String pathPart = ""; // 路径+文件名部分(如 /attachment/host 副本.txt
// 1. 拆分URL为「域名部分」和「路径部分」
if (matcher.matches()) {
domainPart = matcher.group(1);
pathPart = matcher.group(3) == null ? "" : matcher.group(3);
} else {
// 无路径的纯域名(如 http://www.baidu.商店)
domainPart = cleanUrl;
}
// 2. 处理域名部分IDN域名转Punycode编码如 商店 → xn--3ds443g
String encodedDomain = encodeDomain(domainPart);
// 3. 处理路径部分URL编码保留/,编码空格/中文)
String encodedPath = encodePath(pathPart);
// 4. 拼接完整URL再次清理可能的多余斜杠
String finalUrl = encodedDomain + encodedPath;
return cleanMultipleSlashes(finalUrl);
}
/**
* 清理URL中多余的连续斜杠保留协议后的//,如 http://
*/
private static String cleanMultipleSlashes(String url) {
if (url.startsWith("http://")) {
return "http://" + MULTIPLE_SLASH_PATTERN.matcher(url.substring(7)).replaceAll("/");
} else if (url.startsWith("https://")) {
return "https://" + MULTIPLE_SLASH_PATTERN.matcher(url.substring(8)).replaceAll("/");
} else {
// 非HTTP/HTTPS URL直接替换所有连续斜杠
return MULTIPLE_SLASH_PATTERN.matcher(url).replaceAll("/");
}
}
/**
* 编码域名IDN域名转Punycode处理中文/特殊字符域名)
*/
private static String encodeDomain(String domain) {
if (domain.isEmpty()) {
return "";
}
// 拆分协议和域名(如 http:// + www.baidu.商店)
String protocol = "";
String pureDomain = domain;
if (domain.startsWith("http://")) {
protocol = "http://";
pureDomain = domain.substring(7);
} else if (domain.startsWith("https://")) {
protocol = "https://";
pureDomain = domain.substring(8);
}
// IDN域名转Punycode核心处理中文后缀如「商店」
String punycodeDomain = IDN.toASCII(pureDomain);
return protocol + punycodeDomain;
}
/**
* 编码路径:仅编码路径/文件名中的特殊字符,保留/
*/
private static String encodePath(String path) {
if (path.isEmpty()) {
return "";
}
// 按/拆分路径段,逐个编码后拼接(避免/被编码)
String[] pathSegments = path.split("/");
StringBuilder encodedPath = new StringBuilder();
for (String segment : pathSegments) {
if (!segment.isEmpty()) {
String encodedSegment = URLEncoder.encode(segment, StandardCharsets.UTF_8)
.replace("+", "%20") // 空格转%20
.replace("%2F", "/"); // 保留段内的/(如有)
encodedPath.append("/").append(encodedSegment);
} else {
encodedPath.append("/"); // 保留空段(如开头的/
}
}
// 处理末尾的/(避免多拼接)
String result = encodedPath.length() > 0 ? encodedPath.toString() : path;
// 清理路径中的多余斜杠
return MULTIPLE_SLASH_PATTERN.matcher(result).replaceAll("/");
}
}

View File

@@ -0,0 +1,94 @@
package tech.easyflow.common.vo;
import tech.easyflow.common.tree.TreeNode;
import java.math.BigInteger;
public class MenuVo extends TreeNode {
private BigInteger id;
private BigInteger parentId;
private MetaVo meta;
private String name;
private String path;
private String component;
public BigInteger getId() {
return id;
}
public void setId(BigInteger id) {
this.id = id;
}
public BigInteger getParentId() {
return parentId;
}
public void setParentId(BigInteger parentId) {
this.parentId = parentId;
}
public MetaVo getMeta() {
return meta;
}
public void setMeta(MetaVo meta) {
this.meta = meta;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public static class MetaVo {
private String title;
private String icon;
private Integer order;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
this.order = order;
}
}
}

View File

@@ -0,0 +1,18 @@
package tech.easyflow.common.vo;
public class PkVo {
public PkVo(Object[] id) {
this.id = id;
}
private Object[] id;
public Object[] getId() {
return id;
}
public void setId(Object[] id) {
this.id = id;
}
}

View File

@@ -0,0 +1,17 @@
package tech.easyflow.common.vo;
public class UploadResVo {
/**
* 访问路径
*/
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}

View File

@@ -0,0 +1,49 @@
package tech.easyflow.common.tree.test;
import tech.easyflow.common.tree.TreeNode;
public class TreeObj extends TreeNode {
private String id;
private String pid;
private String name;
public TreeObj(String id, String pid, String name) {
this.id = id;
this.pid = pid;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "TreeObj{" +
"id='" + id + '\'' +
", pid='" + pid + '\'' +
", name='" + name + '\'' +
'}';
}
}

View File

@@ -0,0 +1,44 @@
package tech.easyflow.common.tree.test;
import tech.easyflow.common.tree.Tree;
public class TreeTest {
public static void main(String[] args) {
Tree<TreeObj> tree = new Tree<>(TreeObj.class, "id", "pid");
tree.addNode(new TreeObj("1", "0", "11"));
tree.addNode(new TreeObj("2", "0", "22"));
tree.addNode(new TreeObj("3", "1", "33"));
tree.print();
System.out.println("\n---------------------------");
tree.addNode(new TreeObj("4", "2", "44"));
tree.addNode(new TreeObj("5", "2", "12"));
tree.print();
System.out.println("\n---------------------------");
tree.addNode(new TreeObj("6", "4", "12"));
tree.addNode(new TreeObj("7", "5", "12"));
tree.print();
System.out.println("\n---------------------------");
tree.addNode(new TreeObj("8", "3", "12"));
tree.addNode(new TreeObj("9", "3", "12"));
tree.print();
System.out.println("\n---------------------------");
tree.addNode(new TreeObj("8", "0", "12"));
tree.addNode(new TreeObj("9", "0", "12"));
tree.print();
System.out.println("\n---------------------------");
tree.addNode(new TreeObj("20", "8", "12"));
tree.addNode(new TreeObj("21", "9", "12"));
tree.print();
}
}