初始化
This commit is contained in:
128
easyflow-commons/easyflow-common-ai/pom.xml
Normal file
128
easyflow-commons/easyflow-common-ai/pom.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easyflow-common-ai</name>
|
||||
<artifactId>easyflow-common-ai</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>4.9</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-bom</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>ooxml-schemas</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-base</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-options</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,55 @@
|
||||
package tech.easyflow.common.ai;
|
||||
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 聊天专用SSE发射器
|
||||
*/
|
||||
public class ChatSseEmitter extends SseEmitter {
|
||||
|
||||
// 默认超时时间:30分钟
|
||||
public static final long DEFAULT_TIMEOUT = 5 * 60 * 1000L;
|
||||
|
||||
public ChatSseEmitter() {
|
||||
super(DEFAULT_TIMEOUT);
|
||||
// 注册基础的超时/异常回调(避免连接泄漏)
|
||||
registerBasicCallbacks();
|
||||
}
|
||||
|
||||
public ChatSseEmitter(long timeout) {
|
||||
super(timeout);
|
||||
registerBasicCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础回调:仅做简单的连接清理
|
||||
*/
|
||||
private void registerBasicCallbacks() {
|
||||
// 超时回调
|
||||
onTimeout(this::complete);
|
||||
// 异常回调
|
||||
onError(e -> complete());
|
||||
}
|
||||
|
||||
public static ChatSseEmitter create() {
|
||||
return new ChatSseEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送普通消息(最常用的核心方法)
|
||||
* @param message 消息内容
|
||||
* @throws IOException 发送失败时抛出IO异常
|
||||
*/
|
||||
public void sendMessage(String message) throws IOException {
|
||||
// 封装标准的SSE消息格式
|
||||
send(SseEmitter.event().name("message").data(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建发射器
|
||||
*/
|
||||
public ChatSseEmitter createChatSseEmitter() {
|
||||
return new ChatSseEmitter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package tech.easyflow.common.ai.inteceptor;
|
||||
|
||||
import com.easyagents.core.model.chat.tool.GlobalToolInterceptors;
|
||||
import com.easyagents.core.model.chat.tool.ToolChain;
|
||||
import com.easyagents.core.model.chat.tool.ToolContext;
|
||||
import com.easyagents.core.model.chat.tool.ToolInterceptor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Map;
|
||||
@Component
|
||||
public class ToolLoggingInterceptor implements ToolInterceptor {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
GlobalToolInterceptors.addInterceptor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(ToolContext context, ToolChain chain) throws Exception {
|
||||
String toolName = context.getTool().getName();
|
||||
Map<String, Object> args = context.getArgsMap();
|
||||
|
||||
System.out.println("▶ 调用工具: " + toolName + ", 参数: " + args);
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
Object result = chain.proceed(context);
|
||||
System.out.println("✅ 工具返回: " + result);
|
||||
return result;
|
||||
} finally {
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
System.out.println("⏱️ 耗时: " + duration + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package tech.easyflow.common.ai.plugin;
|
||||
import java.util.*;
|
||||
|
||||
public class NestedParamConverter {
|
||||
|
||||
public static Map<String, Object> convertToNestedParamMap(List<PluginParam> pluginParams) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
if (pluginParams == null || pluginParams.isEmpty()) return result;
|
||||
|
||||
for (PluginParam param : pluginParams) {
|
||||
if (!param.isEnabled()) continue;
|
||||
result.put(param.getName(), buildValue(param));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object buildValue(PluginParam param) {
|
||||
if ("String".equalsIgnoreCase(param.getType())) {
|
||||
return param.getDefaultValue();
|
||||
} else if ("Object".equalsIgnoreCase(param.getType())) {
|
||||
Map<String, Object> objMap = new LinkedHashMap<>();
|
||||
if (param.getChildren() != null && !param.getChildren().isEmpty()) {
|
||||
for (PluginParam child : param.getChildren()) {
|
||||
objMap.put(child.getName(), buildValue(child));
|
||||
}
|
||||
}
|
||||
return objMap;
|
||||
} else if ("Array".equalsIgnoreCase(param.getType())) {
|
||||
if (param.getChildren() != null && !param.getChildren().isEmpty()) {
|
||||
PluginParam arrayItemTemplate = param.getChildren().get(0);
|
||||
if ("Array[Object]".equalsIgnoreCase(arrayItemTemplate.getType())) {
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
for (PluginParam child : arrayItemTemplate.getChildren()) {
|
||||
item.put(child.getName(), buildValue(child));
|
||||
}
|
||||
|
||||
list.add(item); // 示例中只添加一个对象
|
||||
return list;
|
||||
}
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package tech.easyflow.common.ai.plugin;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.*;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class PluginHttpClient {
|
||||
|
||||
private static final int TIMEOUT = 10_000;
|
||||
|
||||
public static JSONObject sendRequest(String url, String method,
|
||||
Map<String, Object> headers,
|
||||
List<PluginParam> pluginParams) {
|
||||
// 1. 处理路径参数
|
||||
String processedUrl = replacePathVariables(url, pluginParams);
|
||||
|
||||
// 2. 初始化请求
|
||||
Method httpMethod = Method.valueOf(method.toUpperCase());
|
||||
HttpRequest request = HttpRequest.of(processedUrl)
|
||||
.method(httpMethod)
|
||||
.timeout(TIMEOUT);
|
||||
// 3. 处理请求头(合并默认头和参数头)
|
||||
processHeaders(request, headers, pluginParams);
|
||||
|
||||
// 4. 处理查询参数和请求体
|
||||
processQueryAndBodyParams(request, httpMethod, pluginParams);
|
||||
|
||||
// 5. 执行请求
|
||||
HttpResponse response = request.execute();
|
||||
return JSONUtil.parseObj(response.body());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求头(合并默认头和参数头)
|
||||
*/
|
||||
private static void processHeaders(HttpRequest request,
|
||||
Map<String, Object> defaultHeaders,
|
||||
List<PluginParam> params) {
|
||||
// 添加默认头
|
||||
if (ObjectUtil.isNotEmpty(defaultHeaders)) {
|
||||
defaultHeaders.forEach((k, v) -> request.header(k, v.toString()));
|
||||
}
|
||||
|
||||
// 添加参数中指定的头
|
||||
params.stream()
|
||||
.filter(p -> "header".equalsIgnoreCase(p.getMethod()) && p.isEnabled())
|
||||
.forEach(p -> request.header(p.getName(), p.getDefaultValue().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理查询参数和请求体
|
||||
*/
|
||||
/**
|
||||
* 处理查询参数和请求体(新增文件参数支持)
|
||||
*/
|
||||
private static void processQueryAndBodyParams(HttpRequest request,
|
||||
Method httpMethod,
|
||||
List<PluginParam> params) {
|
||||
Map<String, Object> queryParams = new HashMap<>();
|
||||
Map<String, Object> bodyParams = new HashMap<>();
|
||||
// 标记是否包含文件参数
|
||||
AtomicBoolean hasMultipartFile = new AtomicBoolean(false);
|
||||
|
||||
// 分类参数(同时检测是否有文件)
|
||||
params.stream()
|
||||
.filter(PluginParam::isEnabled)
|
||||
.forEach(p -> {
|
||||
String methodType = p.getMethod().toLowerCase();
|
||||
Object paramValue = buildNestedParamValue(p);
|
||||
|
||||
// 检测是否为文件参数(MultipartFile 类型)
|
||||
if (paramValue instanceof org.springframework.web.multipart.MultipartFile) {
|
||||
hasMultipartFile.set(true);
|
||||
}
|
||||
|
||||
switch (methodType) {
|
||||
case "query":
|
||||
queryParams.put(p.getName(), paramValue);
|
||||
break;
|
||||
case "body":
|
||||
bodyParams.put(p.getName(), paramValue);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// 1. 设置查询参数(原有逻辑不变)
|
||||
if (!queryParams.isEmpty()) {
|
||||
request.form(queryParams);
|
||||
}
|
||||
|
||||
// 2. 设置请求体(分两种情况:有文件 vs 无文件)
|
||||
if (!bodyParams.isEmpty() && (httpMethod == Method.POST || httpMethod == Method.PUT)) {
|
||||
if (hasMultipartFile.get()) {
|
||||
// 2.1 包含文件参数 → 用 multipart/form-data 格式
|
||||
processMultipartBody(request, bodyParams);
|
||||
} else {
|
||||
// 2.2 无文件参数 → 保持原有 JSON 格式
|
||||
request.body(JSONUtil.toJsonStr(bodyParams))
|
||||
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归构建嵌套参数值
|
||||
* @param param 当前参数
|
||||
* @return 如果是 Object 类型,返回 Map;否则返回 defaultValue
|
||||
*/
|
||||
private static Object buildNestedParamValue(PluginParam param) {
|
||||
// 如果不是 Object 类型,直接返回默认值
|
||||
if (!"Object".equalsIgnoreCase(param.getType())) {
|
||||
return param.getDefaultValue();
|
||||
}
|
||||
|
||||
// 如果是 Object 类型,递归处理子参数
|
||||
Map<String, Object> nestedParams = new HashMap<>();
|
||||
if (param.getChildren() != null) {
|
||||
param.getChildren().stream()
|
||||
.filter(PluginParam::isEnabled)
|
||||
.forEach(child -> {
|
||||
Object childValue = buildNestedParamValue(child); // 递归处理子参数
|
||||
nestedParams.put(child.getName(), childValue);
|
||||
});
|
||||
}
|
||||
return nestedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换URL中的路径变量 {xxx}
|
||||
*/
|
||||
private static String replacePathVariables(String url, List<PluginParam> params) {
|
||||
String result = url;
|
||||
|
||||
// 收集路径参数
|
||||
Map<String, Object> pathParams = new HashMap<>();
|
||||
params.stream()
|
||||
.filter(p -> "path".equalsIgnoreCase(p.getMethod()) && p.isEnabled())
|
||||
.forEach(p -> pathParams.put(p.getName(), p.getDefaultValue()));
|
||||
|
||||
// 替换变量
|
||||
for (Map.Entry<String, Object> entry : pathParams.entrySet()) {
|
||||
result = result.replaceAll("\\{" + entry.getKey() + "\\}",
|
||||
entry.getValue().toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void processMultipartBody(HttpRequest request, Map<String, Object> bodyParams) {
|
||||
// 手动设置 Content-Type 为 multipart/form-data
|
||||
request.header(Header.CONTENT_TYPE, "multipart/form-data");
|
||||
for (Map.Entry<String, Object> entry : bodyParams.entrySet()) {
|
||||
String paramName = entry.getKey();
|
||||
Object paramValue = entry.getValue();
|
||||
|
||||
if (paramValue instanceof MultipartFile) {
|
||||
MultipartFile file = (MultipartFile) paramValue;
|
||||
try {
|
||||
request.form(paramName, file.getBytes(), file.getOriginalFilename());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format("文件参数处理失败:参数名=%s,文件名=%s",
|
||||
paramName, file.getOriginalFilename()), e);
|
||||
}
|
||||
} else {
|
||||
// 处理普通参数
|
||||
String valueStr;
|
||||
if (paramValue instanceof String || paramValue instanceof Number || paramValue instanceof Boolean) {
|
||||
valueStr = paramValue.toString();
|
||||
} else {
|
||||
valueStr = JSONUtil.toJsonStr(paramValue);
|
||||
}
|
||||
request.form(paramName, valueStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package tech.easyflow.common.ai.plugin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PluginParam {
|
||||
private String name;
|
||||
private String description;
|
||||
private String type;
|
||||
private String method; // Query / Body / Header / PathVariable 等
|
||||
private Object defaultValue;
|
||||
private boolean required;
|
||||
private boolean enabled;
|
||||
private String key;
|
||||
private List<PluginParam> children;
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void setDefaultValue(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(boolean required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public List<PluginParam> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<PluginParam> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package tech.easyflow.common.ai.plugin;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PluginParamConverter {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 将JSON字符串转换为PluginParam对象列表
|
||||
* @param jsonStr 数据库中的JSON字符串
|
||||
* @return PluginParam对象列表
|
||||
*/
|
||||
public static List<PluginParam> convertFromJson(String jsonStr) {
|
||||
try {
|
||||
// 将JSON字符串解析为List<Map>结构
|
||||
List<Map<String, Object>> paramMaps = objectMapper.readValue(
|
||||
jsonStr,
|
||||
new TypeReference<List<Map<String, Object>>>(){}
|
||||
);
|
||||
|
||||
List<PluginParam> result = new ArrayList<>();
|
||||
for (Map<String, Object> paramMap : paramMaps) {
|
||||
result.add(convertMapToPluginParam(paramMap));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to convert JSON to PluginParam", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归将Map转换为PluginParam对象
|
||||
*/
|
||||
private static PluginParam convertMapToPluginParam(Map<String, Object> map) {
|
||||
PluginParam param = new PluginParam();
|
||||
param.setKey(getStringValue(map, "key"));
|
||||
param.setName(getStringValue(map, "name"));
|
||||
param.setDescription(getStringValue(map, "description"));
|
||||
param.setType(getStringValue(map, "type"));
|
||||
param.setMethod(getStringValue(map, "method"));
|
||||
param.setDefaultValue(map.get("defaultValue"));
|
||||
param.setRequired(getBooleanValue(map, "required"));
|
||||
param.setEnabled(getBooleanValue(map, "enabled"));
|
||||
|
||||
// 处理子节点
|
||||
if (map.containsKey("children")) {
|
||||
Object childrenObj = map.get("children");
|
||||
if (childrenObj instanceof List) {
|
||||
List<Map<String, Object>> childrenMaps = (List<Map<String, Object>>) childrenObj;
|
||||
List<PluginParam> children = new ArrayList<>();
|
||||
for (Map<String, Object> childMap : childrenMaps) {
|
||||
children.add(convertMapToPluginParam(childMap));
|
||||
}
|
||||
param.setChildren(children);
|
||||
}
|
||||
}
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
private static String getStringValue(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
private static boolean getBooleanValue(Map<String, Object> map, String key) {
|
||||
Object value = map.get(key);
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package tech.easyflow.common.ai.rag;
|
||||
|
||||
import com.easyagents.core.document.Document;
|
||||
import com.easyagents.core.document.DocumentSplitter;
|
||||
import com.easyagents.core.document.id.DocumentIdGenerator;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ExcelDocumentSplitter implements DocumentSplitter {
|
||||
private int rowsPerChunk;
|
||||
private boolean includeHeader;
|
||||
|
||||
public ExcelDocumentSplitter(int rowsPerChunk) {
|
||||
if (rowsPerChunk <= 0) {
|
||||
throw new IllegalArgumentException("rows must be greater than 0");
|
||||
}
|
||||
this.rowsPerChunk = rowsPerChunk;
|
||||
}
|
||||
|
||||
public ExcelDocumentSplitter(int rowsPerChunk, boolean includeHeader) {
|
||||
if (rowsPerChunk <= 0) {
|
||||
throw new IllegalArgumentException("rows must be greater than 0");
|
||||
}
|
||||
this.rowsPerChunk = rowsPerChunk;
|
||||
this.includeHeader = includeHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Document> split(Document document, DocumentIdGenerator idGenerator) {
|
||||
if (document == null || document.getContent() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 解析JSON数据为表格结构
|
||||
List<List<String>> tableData = JSON.parseObject(document.getContent(),
|
||||
new TypeReference<List<List<String>>>() {});
|
||||
|
||||
if (tableData == null || tableData.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Document> chunks = new ArrayList<>();
|
||||
List<String> headers = includeHeader ? tableData.get(0) : null;
|
||||
int startRow = includeHeader ? 1 : 0;
|
||||
|
||||
// 按照指定行数分割数据
|
||||
for (int i = startRow; i < tableData.size(); i += rowsPerChunk) {
|
||||
int endRow = Math.min(i + rowsPerChunk, tableData.size());
|
||||
|
||||
// 构建当前分块的Markdown表格
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 添加表头(如果包含)
|
||||
if (headers != null) {
|
||||
sb.append("| ").append(String.join(" | ", headers)).append(" |\n");
|
||||
sb.append("|");
|
||||
for (int j = 0; j < headers.size(); j++) {
|
||||
sb.append(" --- |");
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
// 添加数据行
|
||||
for (int j = i; j < endRow; j++) {
|
||||
List<String> row = tableData.get(j);
|
||||
sb.append("| ").append(String.join(" | ", row)).append(" |\n");
|
||||
}
|
||||
|
||||
// 创建新文档
|
||||
Document newDocument = new Document();
|
||||
newDocument.addMetadata(document.getMetadataMap());
|
||||
newDocument.setContent(sb.toString());
|
||||
|
||||
if (idGenerator != null) {
|
||||
newDocument.setId(idGenerator.generateId(newDocument));
|
||||
}
|
||||
|
||||
chunks.add(newDocument);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
public int getRowsPerChunk() {
|
||||
return rowsPerChunk;
|
||||
}
|
||||
|
||||
public void setRowsPerChunk(int rowsPerChunk) {
|
||||
this.rowsPerChunk = rowsPerChunk;
|
||||
}
|
||||
|
||||
public boolean isIncludeHeader() {
|
||||
return includeHeader;
|
||||
}
|
||||
|
||||
public void setIncludeHeader(boolean includeHeader) {
|
||||
this.includeHeader = includeHeader;
|
||||
}
|
||||
}
|
||||
44
easyflow-commons/easyflow-common-all/pom.xml
Normal file
44
easyflow-commons/easyflow-common-all/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easyflow-common-all</name>
|
||||
<artifactId>easyflow-common-all</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-base</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-file-storage</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-options</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-captcha</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
63
easyflow-commons/easyflow-common-audio/pom.xml
Normal file
63
easyflow-commons/easyflow-common-audio/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>easyflow-common-audio</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nls</groupId>
|
||||
<artifactId>nls-sdk-tts</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nls</groupId>
|
||||
<artifactId>nls-sdk-common</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-base</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
<artifactId>Java-WebSocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-satoken</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,36 @@
|
||||
package tech.easyflow.common.audio;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import tech.easyflow.common.audio.core.AudioServiceManager;
|
||||
import tech.easyflow.common.web.controller.BaseController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@SaIgnore
|
||||
@RequestMapping("/tts")
|
||||
@RestController
|
||||
public class TestAudioController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private AudioServiceManager manager;
|
||||
|
||||
@GetMapping("/test2")
|
||||
public String test2() throws Exception {
|
||||
return "2";
|
||||
}
|
||||
|
||||
@GetMapping("/test1")
|
||||
public String test1() throws Exception {
|
||||
|
||||
return "1";
|
||||
}
|
||||
|
||||
@GetMapping("/test")
|
||||
public String test() throws Exception {
|
||||
|
||||
return "hello world";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package tech.easyflow.common.audio.config;
|
||||
|
||||
import com.alibaba.nls.client.AccessToken;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "easyflow.audio.ali")
|
||||
public class AliConfig {
|
||||
|
||||
private String accessKeyId;
|
||||
private String accessKeySecret;
|
||||
private String appKey;
|
||||
private String voice;
|
||||
|
||||
public String getAccessKeyId() {
|
||||
return accessKeyId;
|
||||
}
|
||||
|
||||
public void setAccessKeyId(String accessKeyId) {
|
||||
this.accessKeyId = accessKeyId;
|
||||
}
|
||||
|
||||
public String getAccessKeySecret() {
|
||||
return accessKeySecret;
|
||||
}
|
||||
|
||||
public void setAccessKeySecret(String accessKeySecret) {
|
||||
this.accessKeySecret = accessKeySecret;
|
||||
}
|
||||
|
||||
public String getAppKey() {
|
||||
return appKey;
|
||||
}
|
||||
|
||||
public void setAppKey(String appKey) {
|
||||
this.appKey = appKey;
|
||||
}
|
||||
|
||||
public String getVoice() {
|
||||
return voice;
|
||||
}
|
||||
|
||||
public void setVoice(String voice) {
|
||||
this.voice = voice;
|
||||
}
|
||||
|
||||
public String createToken() {
|
||||
AccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret);
|
||||
try {
|
||||
accessToken.apply();
|
||||
return accessToken.getToken();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package tech.easyflow.common.audio.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.easyflow.common.util.SpringContextUtil;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "easyflow.audio")
|
||||
public class AudioConfig {
|
||||
|
||||
private String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static AudioConfig getInstance() {
|
||||
return SpringContextUtil.getBean(AudioConfig.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package tech.easyflow.common.audio.core;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface AudioService {
|
||||
|
||||
/**
|
||||
* 文字转语音 - 流式
|
||||
*/
|
||||
void textToVoiceStream(BaseAudioClient client, String sessionId, String messageId, String text);
|
||||
|
||||
/**
|
||||
* 语音转文字 - 同步
|
||||
*
|
||||
* @param is 输入流
|
||||
* @return 文本
|
||||
*/
|
||||
String audioToText(InputStream is);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tech.easyflow.common.audio.core;
|
||||
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.common.audio.config.AliConfig;
|
||||
import tech.easyflow.common.audio.config.AudioConfig;
|
||||
import tech.easyflow.common.audio.impl.ali.AliAudioClient;
|
||||
import tech.easyflow.common.util.SpringContextUtil;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Component
|
||||
public class AudioServiceManager implements AudioService {
|
||||
|
||||
@Resource
|
||||
private AliConfig aliConfig;
|
||||
@Resource(name = "taskScheduler")
|
||||
private TaskScheduler scheduler;
|
||||
|
||||
@Override
|
||||
public void textToVoiceStream(BaseAudioClient client, String sessionId,String messageId, String text) {
|
||||
getService().textToVoiceStream(client, sessionId,messageId, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String audioToText(InputStream is) {
|
||||
return getService().audioToText(is);
|
||||
}
|
||||
|
||||
public BaseAudioClient getClient() {
|
||||
String type = AudioConfig.getInstance().getType();
|
||||
if ("aliAudioService".equals(type)) {
|
||||
return new AliAudioClient(aliConfig);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AudioService getService() {
|
||||
String type = AudioConfig.getInstance().getType();
|
||||
return SpringContextUtil.getBean(type);
|
||||
}
|
||||
|
||||
public AliConfig getAliConfig() {
|
||||
return aliConfig;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package tech.easyflow.common.audio.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import tech.easyflow.common.audio.listener.AudioMessageListener;
|
||||
|
||||
public abstract class BaseAudioClient {
|
||||
|
||||
protected Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public abstract void beforeSend(String sessionId, String messageId);
|
||||
|
||||
public abstract void send(String sessionId, String messageId, String text);
|
||||
|
||||
public abstract void afterSend(String sessionId, String messageId);
|
||||
|
||||
public abstract void addListener(String sessionId, String messageId, AudioMessageListener listener);
|
||||
|
||||
public abstract void close();
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package tech.easyflow.common.audio.impl.ali;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.nls.client.protocol.NlsClient;
|
||||
import com.alibaba.nls.client.protocol.OutputFormatEnum;
|
||||
import com.alibaba.nls.client.protocol.SampleRateEnum;
|
||||
import com.alibaba.nls.client.protocol.SpeechReqProtocol;
|
||||
import com.alibaba.nls.client.protocol.tts.FlowingSpeechSynthesizer;
|
||||
import tech.easyflow.common.audio.config.AliConfig;
|
||||
import tech.easyflow.common.audio.core.BaseAudioClient;
|
||||
import tech.easyflow.common.audio.listener.AudioMessageListener;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AliAudioClient extends BaseAudioClient {
|
||||
|
||||
private final AliConfig aliConfig;
|
||||
private NlsClient nlsClient;
|
||||
|
||||
private final Map<String, FlowingSpeechSynthesizer> execClients = new ConcurrentHashMap<>();
|
||||
|
||||
public AliAudioClient(AliConfig aliConfig) {
|
||||
this.aliConfig = aliConfig;
|
||||
initClient();
|
||||
}
|
||||
|
||||
private void initClient() {
|
||||
String token = aliConfig.createToken();
|
||||
this.nlsClient = new NlsClient(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSend(String sessionId, String messageId) {
|
||||
FlowingSpeechSynthesizer execClient = getExecClient(sessionId, messageId);
|
||||
try {
|
||||
execClient.start();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String sessionId, String messageId, String text) {
|
||||
FlowingSpeechSynthesizer execClient = getExecClient(sessionId, messageId);
|
||||
if (StrUtil.isNotEmpty(text)) {
|
||||
execClient.send(text);
|
||||
} else {
|
||||
execClient.getConnection().sendPing();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSend(String sessionId, String messageId) {
|
||||
FlowingSpeechSynthesizer execClient = getExecClient(sessionId, messageId);
|
||||
try {
|
||||
execClient.stop();
|
||||
} catch (Exception e) {
|
||||
log.error("【阿里云nls】客户端结束时发生异常:{}", e.getMessage());
|
||||
} finally {
|
||||
execClients.remove(sessionId + messageId);
|
||||
try {
|
||||
execClient.close();
|
||||
} catch (Exception e) {
|
||||
log.error("【阿里云nls】关闭客户端时发生异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(String sessionId, String messageId, AudioMessageListener listener) {
|
||||
FlowingSpeechSynthesizer execClient = getExecClient(sessionId, messageId);
|
||||
AliListener aliListener = (AliListener) execClient.getStreamTTSListener();
|
||||
aliListener.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
execClients.forEach((sessionIdMessageId, execClient) -> {
|
||||
try {
|
||||
execClient.stop();
|
||||
SpeechReqProtocol.State state = execClient.getState();
|
||||
} catch (Exception e) {
|
||||
log.error("【阿里云nls】客户端结束时发生异常:{}", e.getMessage());
|
||||
} finally {
|
||||
execClient.close();
|
||||
}
|
||||
});
|
||||
execClients.clear();
|
||||
nlsClient.shutdown();
|
||||
}
|
||||
|
||||
private FlowingSpeechSynthesizer getExecClient(String sessionId, String messageId) {
|
||||
FlowingSpeechSynthesizer synthesizer = execClients.get(sessionId + messageId);
|
||||
if (synthesizer == null) {
|
||||
//创建实例,建立连接。
|
||||
try {
|
||||
synthesizer = new FlowingSpeechSynthesizer(nlsClient, new AliListener(sessionId, messageId));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
synthesizer.setAppKey(aliConfig.getAppKey());
|
||||
//设置返回音频的编码格式。
|
||||
synthesizer.setFormat(OutputFormatEnum.MP3);
|
||||
//设置返回音频的采样率。
|
||||
synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
|
||||
//发音人。注意Java SDK不支持调用超高清场景对应的发音人(例如"zhiqi"),如需调用请使用restfulAPI方式。
|
||||
synthesizer.setVoice(aliConfig.getVoice());
|
||||
//音量,范围是0~100,可选,默认50。
|
||||
synthesizer.setVolume(50);
|
||||
//语调,范围是-500~500,可选,默认是0。
|
||||
synthesizer.setPitchRate(0);
|
||||
//语速,范围是-500~500,默认是0。
|
||||
synthesizer.setSpeechRate(0);
|
||||
execClients.put(sessionId + messageId, synthesizer);
|
||||
}
|
||||
return synthesizer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package tech.easyflow.common.audio.impl.ali;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.common.audio.config.AliConfig;
|
||||
import tech.easyflow.common.audio.core.AudioService;
|
||||
import tech.easyflow.common.audio.core.BaseAudioClient;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 阿里云
|
||||
*/
|
||||
@Component("aliAudioService")
|
||||
public class AliAudioService implements AudioService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AliAudioService.class);
|
||||
@Resource
|
||||
private AliConfig aliConfig;
|
||||
|
||||
@Override
|
||||
public void textToVoiceStream(BaseAudioClient client, String sessionId, String messageId, String text) {
|
||||
client.send(sessionId, messageId, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String audioToText(InputStream is) {
|
||||
|
||||
String token = aliConfig.createToken();
|
||||
|
||||
/**
|
||||
* 设置HTTPS REST POST请求
|
||||
* 1.使用http协议
|
||||
* 2.语音识别服务域名:nls-gateway-cn-shanghai.aliyuncs.com
|
||||
* 3.语音识别接口请求路径:/stream/v1/FlashRecognizer
|
||||
* 4.设置必须请求参数:appkey、token、format、sample_rate
|
||||
*/
|
||||
String url = "https://nls-gateway-cn-shanghai.aliyuncs.com/stream/v1/FlashRecognizer";
|
||||
String request = url;
|
||||
request = request + "?appkey=" + aliConfig.getAppKey();
|
||||
request = request + "&token=" + token;
|
||||
request = request + "&format=" + "MP3";
|
||||
request = request + "&sample_rate=" + 16000;
|
||||
|
||||
/**
|
||||
* 设置HTTPS头部字段
|
||||
* 1.Content-Type:application/octet-stream
|
||||
*/
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Content-Type", "application/octet-stream");
|
||||
|
||||
HttpRequest post = HttpUtil.createPost(request);
|
||||
post.headerMap(headers, true);
|
||||
|
||||
byte[] bytes = IoUtil.readBytes(is);
|
||||
post.body(bytes);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (HttpResponse execute = post.execute()) {
|
||||
String body = execute.body();
|
||||
JSONObject obj = JSON.parseObject(body);
|
||||
Integer status = obj.getInteger("status");
|
||||
String message = obj.getString("message");
|
||||
if (20000000 != status) {
|
||||
log.error("语音识别失败:{}", obj);
|
||||
throw new BusinessException(message);
|
||||
}
|
||||
JSONArray sentences = obj.getJSONObject("flash_result")
|
||||
.getJSONArray("sentences");
|
||||
|
||||
for (Object sentence : sentences) {
|
||||
JSONObject json = (JSONObject) sentence;
|
||||
String text = json.getString("text");
|
||||
sb.append(text);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package tech.easyflow.common.audio.impl.ali;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.nls.client.protocol.tts.FlowingSpeechSynthesizerListener;
|
||||
import com.alibaba.nls.client.protocol.tts.FlowingSpeechSynthesizerResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import tech.easyflow.common.audio.listener.AudioMessageListener;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AliListener extends FlowingSpeechSynthesizerListener {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(AliListener.class);
|
||||
|
||||
private final String sessionId;
|
||||
private final String messageId;
|
||||
|
||||
private final List<AudioMessageListener> listeners = new ArrayList<>();
|
||||
|
||||
private boolean firstRecvBinary = true;
|
||||
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
public AliListener(String sessionId, String messageId) {
|
||||
this.sessionId = sessionId;
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public void addListener(AudioMessageListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
//流入语音合成开始
|
||||
public void onSynthesisStart(FlowingSpeechSynthesizerResponse response) {
|
||||
//log.info("【阿里云nls】流入语音合成开始 ---> name: {},status: {}", response.getName(), response.getStatus());
|
||||
}
|
||||
|
||||
//服务端检测到了一句话的开始
|
||||
public void onSentenceBegin(FlowingSpeechSynthesizerResponse response) {
|
||||
//log.info("【阿里云nls】服务端检测到了一句话的开始 ---> name: {},status: {}", response.getName(), response.getStatus());
|
||||
}
|
||||
|
||||
//服务端检测到了一句话的结束,获得这句话的起止位置和所有时间戳
|
||||
public void onSentenceEnd(FlowingSpeechSynthesizerResponse response) {
|
||||
//log.info("【阿里云nls】服务端检测到了一句话的结束 ---> name: {},status: {},subtitles: {}", response.getName(), response.getStatus(), response.getObject("subtitles"));
|
||||
}
|
||||
|
||||
//流入语音合成结束
|
||||
@Override
|
||||
public void onSynthesisComplete(FlowingSpeechSynthesizerResponse response) {
|
||||
// 调用onSynthesisComplete时,表示所有TTS数据已经接收完成,所有文本都已经合成音频并返回。
|
||||
//log.info("【阿里云nls】流入语音合成结束 ---> name: {},status: {}", response.getName(), response.getStatus());
|
||||
for (AudioMessageListener listener : listeners) {
|
||||
listener.onFinished(sessionId, messageId, outputStream.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
//收到语音合成的语音二进制数据
|
||||
@Override
|
||||
public void onAudioData(ByteBuffer message) {
|
||||
//log.info("【阿里云nls】收到语音合成的语音二进制数据。");
|
||||
if (firstRecvBinary) {
|
||||
// 此处计算首包语音流的延迟,收到第一包语音流时,即可以进行语音播放,以提升响应速度(特别是实时交互场景下)。
|
||||
firstRecvBinary = false;
|
||||
}
|
||||
byte[] bytesArray = new byte[message.remaining()];
|
||||
message.get(bytesArray, 0, bytesArray.length);
|
||||
try {
|
||||
outputStream.write(bytesArray);
|
||||
} catch (IOException e) {
|
||||
log.error("【阿里云nls】写入二进制数据失败", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (AudioMessageListener listener : listeners) {
|
||||
listener.onMessageReceived(sessionId, messageId, bytesArray);
|
||||
}
|
||||
}
|
||||
|
||||
//收到语音合成的增量音频时间戳
|
||||
@Override
|
||||
public void onSentenceSynthesis(FlowingSpeechSynthesizerResponse response) {
|
||||
//log.info("【阿里云nls】收到语音合成的增量音频时间戳 ---> name: {},status: {},subtitles: {}", response.getName(), response.getStatus(), response.getObject("subtitles"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(FlowingSpeechSynthesizerResponse response) {
|
||||
// task_id是调用方和服务端通信的唯一标识,当遇到问题时,需要提供此task_id以便排查。
|
||||
int status = response.getStatus();
|
||||
if (status != 40000004) {
|
||||
log.error("【阿里云nls】合成失败 ---> 会话id:{},消息id:{} session_id: {},task_id: {},status: {},status_text: {}",
|
||||
sessionId,
|
||||
messageId,
|
||||
getFlowingSpeechSynthesizer().getCurrentSessionId(),
|
||||
response.getTaskId(),
|
||||
status,
|
||||
response.getStatusText());
|
||||
for (AudioMessageListener listener : listeners) {
|
||||
listener.onError(sessionId, messageId, new Exception(JSON.toJSONString(response)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package tech.easyflow.common.audio.impl.tencent;
|
||||
|
||||
import tech.easyflow.common.audio.core.AudioService;
|
||||
import tech.easyflow.common.audio.core.BaseAudioClient;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 腾讯云
|
||||
*/
|
||||
public class TencentAudioService implements AudioService {
|
||||
|
||||
@Override
|
||||
public void textToVoiceStream(BaseAudioClient client, String sessionId, String messageId, String text) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String audioToText(InputStream is) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package tech.easyflow.common.audio.impl.volc;
|
||||
|
||||
import tech.easyflow.common.audio.core.AudioService;
|
||||
import tech.easyflow.common.audio.core.BaseAudioClient;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 火山引擎
|
||||
*/
|
||||
public class VolcanoEngineAudioService implements AudioService {
|
||||
|
||||
@Override
|
||||
public void textToVoiceStream(BaseAudioClient client, String sessionId, String messageId, String text) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String audioToText(InputStream is) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package tech.easyflow.common.audio.listener;
|
||||
|
||||
public interface AudioMessageListener {
|
||||
|
||||
void onMessageReceived(String sessionId, String messageId, byte[] message);
|
||||
|
||||
void onFinished(String sessionId, String messageId, byte[] fullMessage);
|
||||
|
||||
void onError(String sessionId, String messageId, Exception e);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package tech.easyflow.common.audio.socket;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import tech.easyflow.common.audio.core.AudioServiceManager;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class AudioSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Resource
|
||||
private AudioServiceManager manager;
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
|
||||
AudioSocketHandler handler = new AudioSocketHandler(manager);
|
||||
registry.addHandler(handler, "/api/v1/bot/ws/audio")
|
||||
.addInterceptors(new AudioSocketInterceptor())
|
||||
.setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package tech.easyflow.common.audio.socket;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
import tech.easyflow.common.audio.core.AudioServiceManager;
|
||||
import tech.easyflow.common.audio.core.BaseAudioClient;
|
||||
import tech.easyflow.common.audio.listener.AudioMessageListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AudioSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
public static final String SESSION_ID = "sessionId";
|
||||
public static final String MESSAGE_ID = "messageId";
|
||||
public static final String TYPE = "type";
|
||||
public static final String CONTENT = "content";
|
||||
public static final String DATA = "_data_";
|
||||
public static final String END = "_end_";
|
||||
public static final String ERROR = "_error_";
|
||||
public static final String START = "_start_";
|
||||
|
||||
public static Map<String, SocketEntity> sessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final AudioServiceManager manager;
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(AudioSocketHandler.class);
|
||||
|
||||
public AudioSocketHandler(AudioServiceManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
String sessionId = session.getAttributes().get(SESSION_ID).toString();
|
||||
log.info("连接建立:{}", sessionId);
|
||||
BaseAudioClient client = manager.getClient();
|
||||
SocketEntity entity = new SocketEntity();
|
||||
entity.setSessionId(sessionId);
|
||||
entity.setSession(session);
|
||||
entity.setClient(client);
|
||||
sessionMap.put(sessionId, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
String sessionId = session.getAttributes().get(SESSION_ID).toString();
|
||||
SocketEntity entity = getSocketEntity(sessionId);
|
||||
BaseAudioClient client = entity.getClient();
|
||||
String msg = message.getPayload();
|
||||
|
||||
JSONObject obj = JSON.parseObject(msg);
|
||||
String messageId = obj.getString(MESSAGE_ID);
|
||||
String type = obj.getString(TYPE);
|
||||
String content = obj.getString(CONTENT);
|
||||
|
||||
if (START.equals(type)) {
|
||||
log.info("文本转语音开始:sessionId:{},messageId:{}", sessionId, messageId);
|
||||
handleStartMessage(entity, client, messageId);
|
||||
}
|
||||
if (END.equals(type)) {
|
||||
log.info("文本转语音结束:sessionId:{},messageId:{}", sessionId, messageId);
|
||||
handleEndMessage(entity, client, messageId);
|
||||
}
|
||||
if (DATA.equals(type)) {
|
||||
handleDataMessage(entity, client, messageId, content);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStartMessage(SocketEntity entity, BaseAudioClient client, String messageId) {
|
||||
client.addListener(entity.getSessionId(), messageId, new AudioMessageListener() {
|
||||
@Override
|
||||
public void onMessageReceived(String sessionId, String messageId, byte[] message) {
|
||||
String encode = Base64.encode(message);
|
||||
AudioSocketHandler.sendJsonVoiceMessage(sessionId, messageId, DATA, encode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(String sessionId, String messageId, byte[] fullMessage) {
|
||||
String encode = Base64.encode(fullMessage);
|
||||
/*long l = System.currentTimeMillis();
|
||||
FileUtil.writeBytes(fullMessage, "D:\\system\\desktop\\"+l+".mp3");*/
|
||||
AudioSocketHandler.sendJsonVoiceMessage(sessionId, messageId, END, encode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String sessionId, String messageId, Exception e) {
|
||||
AudioSocketHandler.sendJsonVoiceMessage(sessionId, messageId, ERROR, e.getLocalizedMessage());
|
||||
}
|
||||
});
|
||||
client.beforeSend(entity.getSessionId(), messageId);
|
||||
AudioSocketHandler.sendJsonVoiceMessage(entity.getSessionId(), messageId, START, "");
|
||||
}
|
||||
|
||||
private void handleEndMessage(SocketEntity entity, BaseAudioClient client, String messageId) {
|
||||
client.afterSend(entity.getSessionId(), messageId);
|
||||
}
|
||||
|
||||
private void handleDataMessage(SocketEntity entity, BaseAudioClient client, String messageId, String content) {
|
||||
client.send(entity.getSessionId(), messageId, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
|
||||
String sessionId = session.getAttributes().get(SESSION_ID).toString();
|
||||
log.info("{} -> 断开连接:{},{}", sessionId, status.getCode(), status.getReason());
|
||||
SocketEntity entity = getSocketEntity(sessionId);
|
||||
entity.getClient().close();
|
||||
}
|
||||
|
||||
public static void sendJsonVoiceMessage(String sessionId, String messageId, String msgType, String content) {
|
||||
WebSocketSession session = getSocketEntity(sessionId).getSession();
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put(MESSAGE_ID, messageId);
|
||||
obj.put(TYPE, msgType);
|
||||
obj.put(CONTENT, content);
|
||||
String msg = obj.toJSONString();
|
||||
session.sendMessage(new TextMessage(msg));
|
||||
} catch (IOException e) {
|
||||
log.error("发送语音消息失败", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SocketEntity getSocketEntity(String sessionId) {
|
||||
SocketEntity socket = sessionMap.get(sessionId);
|
||||
if (socket == null || !socket.getSession().isOpen()) {
|
||||
log.error("获取Socket失败:WebSocket 连接为空或连接已关闭");
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package tech.easyflow.common.audio.socket;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AudioSocketInterceptor implements HandshakeInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AudioSocketInterceptor.class);
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
|
||||
if (request instanceof ServletServerHttpRequest) {
|
||||
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
|
||||
String sessionId = servletRequest.getServletRequest().getParameter("sessionId");
|
||||
String token = servletRequest.getServletRequest().getParameter("token");
|
||||
Object loginIdByToken = StpUtil.getLoginIdByToken(token);
|
||||
if (loginIdByToken == null) {
|
||||
response.setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.hasLength(sessionId)) {
|
||||
return false;
|
||||
}
|
||||
attributes.put("sessionId", sessionId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package tech.easyflow.common.audio.socket;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class SchedulingConfig {
|
||||
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(10);
|
||||
scheduler.setThreadNamePrefix("scheduled-task-");
|
||||
scheduler.setDaemon(true);
|
||||
scheduler.initialize();
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package tech.easyflow.common.audio.socket;
|
||||
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import tech.easyflow.common.audio.core.BaseAudioClient;
|
||||
|
||||
public class SocketEntity {
|
||||
|
||||
private String sessionId;
|
||||
private WebSocketSession session;
|
||||
private BaseAudioClient client;
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public WebSocketSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(WebSocketSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public BaseAudioClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(BaseAudioClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
}
|
||||
46
easyflow-commons/easyflow-common-base/pom.xml
Normal file
46
easyflow-commons/easyflow-common-base/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easyflow-common-base</name>
|
||||
<artifactId>easyflow-common-base</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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:";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package tech.easyflow.common.entity;
|
||||
|
||||
public class DateTreeEntity extends TreeEntity{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package tech.easyflow.common.entity;
|
||||
|
||||
import tech.easyflow.common.tree.TreeNode;
|
||||
|
||||
public class TreeEntity extends TreeNode {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 完整URL(如:http://localhost:8080//attachment/host 副本.txt)
|
||||
* @return 编码后URL(如:http://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("/");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
42
easyflow-commons/easyflow-common-cache/pom.xml
Normal file
42
easyflow-commons/easyflow-common-cache/pom.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easyflow-common-cache</name>
|
||||
<artifactId>easyflow-common-cache</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
<artifactId>jetcache-starter-redis</artifactId>
|
||||
<version>2.7.7</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
42
easyflow-commons/easyflow-common-cache/src/main/java/tech/easyflow/common/cache/CacheConfig.java
vendored
Normal file
42
easyflow-commons/easyflow-common-cache/src/main/java/tech/easyflow/common/cache/CacheConfig.java
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package tech.easyflow.common.cache;
|
||||
|
||||
import com.alicp.jetcache.Cache;
|
||||
import com.alicp.jetcache.CacheManager;
|
||||
import com.alicp.jetcache.anno.CacheType;
|
||||
import com.alicp.jetcache.template.QuickConfig;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class CacheConfig {
|
||||
|
||||
@Autowired
|
||||
private CacheManager cacheManager;
|
||||
@Value("${jetcache.cacheType}")
|
||||
private String cacheType;
|
||||
|
||||
private Cache<String, Object> defaultCache;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
CacheType type = CacheType.LOCAL;
|
||||
if ("remote".equals(cacheType)) {
|
||||
type = CacheType.REMOTE;
|
||||
}
|
||||
if ("both".equals(cacheType)) {
|
||||
type = CacheType.BOTH;
|
||||
}
|
||||
QuickConfig quickConfig = QuickConfig.newBuilder("")
|
||||
.cacheType(type)
|
||||
.build();
|
||||
defaultCache = cacheManager.getOrCreateCache(quickConfig);
|
||||
}
|
||||
|
||||
@Bean("defaultCache")
|
||||
public Cache<String, Object> getDefaultCache() {
|
||||
return defaultCache;
|
||||
}
|
||||
}
|
||||
50
easyflow-commons/easyflow-common-captcha/pom.xml
Normal file
50
easyflow-commons/easyflow-common-captcha/pom.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easyflow-common-captcha</name>
|
||||
<artifactId>easyflow-common-captcha</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java</artifactId>
|
||||
<version>3.1.933</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-common-base</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cloud.tianai.captcha</groupId>
|
||||
<artifactId>tianai-captcha-springboot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,56 @@
|
||||
package tech.easyflow.common.captcha.tainai;
|
||||
|
||||
import cloud.tianai.captcha.cache.CacheStore;
|
||||
import cloud.tianai.captcha.cache.impl.LocalCacheStore;
|
||||
import cloud.tianai.captcha.spring.store.impl.RedisCacheStore;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
@Configuration
|
||||
public class CaptchaCacheAutoConfig {
|
||||
|
||||
@Bean
|
||||
public CacheStore cacheStore(
|
||||
@org.springframework.beans.factory.annotation.Autowired(required = false)
|
||||
StringRedisTemplate stringRedisTemplate,
|
||||
Environment environment) {
|
||||
|
||||
// 检查是否有Redis配置
|
||||
boolean hasRedisConfig = hasRedisConfiguration(environment);
|
||||
|
||||
// 如果有Redis配置且RedisTemplate可用,使用Redis
|
||||
if (hasRedisConfig && stringRedisTemplate != null) {
|
||||
try {
|
||||
// 测试Redis连接
|
||||
stringRedisTemplate.getConnectionFactory().getConnection().ping();
|
||||
return new RedisCacheStore(stringRedisTemplate);
|
||||
} catch (Exception e) {
|
||||
// Redis不可用,使用本地缓存
|
||||
return new LocalCacheStore();
|
||||
}
|
||||
}
|
||||
|
||||
// 没有Redis配置,使用本地缓存
|
||||
return new LocalCacheStore();
|
||||
}
|
||||
|
||||
private boolean hasRedisConfiguration(Environment environment) {
|
||||
// 检查常见的Redis配置属性
|
||||
String[] redisProperties = {
|
||||
"spring.redis.host",
|
||||
"spring.redis.port",
|
||||
"spring.data.redis.host",
|
||||
"spring.data.redis.port"
|
||||
};
|
||||
|
||||
for (String property : redisProperties) {
|
||||
if (environment.containsProperty(property)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package tech.easyflow.common.captcha.tainai;
|
||||
|
||||
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
|
||||
import cloud.tianai.captcha.resource.CrudResourceStore;
|
||||
import cloud.tianai.captcha.resource.ResourceStore;
|
||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CaptchaConfig {
|
||||
|
||||
private final ResourceStore resourceStore;
|
||||
|
||||
public CaptchaConfig(ResourceStore resourceStore) {
|
||||
this.resourceStore = resourceStore;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
||||
CrudResourceStore resourceStore = (CrudResourceStore) this.resourceStore;
|
||||
// 添加自定义背景图片
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/1.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/2.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/3.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/4.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/5.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/6.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/7.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/8.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/9.jpg", "default"));
|
||||
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "captcha-images/10.jpg", "default"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package tech.easyflow.common.captcha.tainai;
|
||||
|
||||
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
|
||||
|
||||
public class CaptchaData {
|
||||
|
||||
// 验证码id
|
||||
private String id;
|
||||
// 验证码数据
|
||||
private ImageCaptchaTrack data;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ImageCaptchaTrack getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(ImageCaptchaTrack data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package tech.easyflow.common.captcha.tainai;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class CaptchaMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Resource
|
||||
private CaptchaValidInterceptor interceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(interceptor)
|
||||
.order(1)
|
||||
.addPathPatterns("/api/v1/auth/login")
|
||||
.addPathPatterns("/userCenter/auth/login");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user